Skip to content

Commit

Permalink
Merge pull request #2388 from daddykotex/dfrancoeur/2363
Browse files Browse the repository at this point in the history
fix: do not follow redirect when checking urls
  • Loading branch information
fthomas committed Dec 19, 2021
2 parents fbdbad3 + e0ecea1 commit cd590e2
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 10 deletions.
1 change: 1 addition & 0 deletions build.sbt
Expand Up @@ -114,6 +114,7 @@ lazy val core = myCrossProject("core")
Dependencies.circeLiteral % Test,
Dependencies.disciplineMunit % Test,
Dependencies.http4sDsl % Test,
Dependencies.http4sBlazeServer % Test,
Dependencies.munit % Test,
Dependencies.munitCatsEffect % Test,
Dependencies.munitScalacheck % Test,
Expand Down
Expand Up @@ -22,11 +22,11 @@ import cats.syntax.all._
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.headers.`User-Agent`
import org.http4s.okhttp.client.OkHttpBuilder
import org.scalasteward.core.buildtool.BuildToolDispatcher
import org.scalasteward.core.buildtool.maven.MavenAlg
import org.scalasteward.core.buildtool.mill.MillAlg
import org.scalasteward.core.buildtool.sbt.SbtAlg
import org.scalasteward.core.client.ClientConfiguration
import org.scalasteward.core.coursier.{CoursierAlg, VersionsCache}
import org.scalasteward.core.edit.EditAlg
import org.scalasteward.core.edit.hooks.HookExecutor
Expand Down Expand Up @@ -83,18 +83,34 @@ object Context {
config = Config.from(args)
_ <- Resource.eval(F.delay(System.setProperty("http.agent", userAgentString)))
userAgent <- Resource.eval(F.fromEither(`User-Agent`.parse(userAgentString)))
client <- OkHttpBuilder
.withDefaultClient[F]
.flatMap(_.resource)
.map(c => Client[F](req => c.run(req.putHeaders(userAgent))))
userAgentMiddleware = ClientConfiguration.setUserAgent[F](userAgent)
defaultClient <- ClientConfiguration.build(
ClientConfiguration.BuilderMiddleware.default,
userAgentMiddleware
)
urlCheckerClient <- ClientConfiguration.build(
ClientConfiguration.disableFollowRedirect[F],
userAgentMiddleware
)
fileAlg = FileAlg.create(logger, F)
processAlg = ProcessAlg.create(config.processCfg)(logger, F)
workspaceAlg = WorkspaceAlg.create(config)(fileAlg, logger, F)
context <- Resource.eval(step1(config)(client, fileAlg, logger, processAlg, workspaceAlg, F))
context <- Resource.eval(
step1(config)(
defaultClient,
UrlCheckerClient(urlCheckerClient),
fileAlg,
logger,
processAlg,
workspaceAlg,
F
)
)
} yield context

def step1[F[_]](config: Config)(implicit
client: Client[F],
urlCheckerClient: UrlCheckerClient[F],
fileAlg: FileAlg[F],
logger: Logger[F],
processAlg: ProcessAlg[F],
Expand Down
@@ -0,0 +1,44 @@
/*
* Copyright 2018-2021 Scala Steward contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.scalasteward.core.client

import org.http4s.client._
import org.http4s.headers.`User-Agent`
import cats.effect._
import org.http4s.okhttp.client.OkHttpBuilder

object ClientConfiguration {
type BuilderMiddleware[F[_]] = OkHttpBuilder[F] => OkHttpBuilder[F]
object BuilderMiddleware {
def default[F[_]]: BuilderMiddleware[F] = bmw => bmw
}
def build[F[_]: Async](bmw: BuilderMiddleware[F], cmw: Middleware[F]): Resource[F, Client[F]] =
OkHttpBuilder
.withDefaultClient[F]
.map(bmw)
.flatMap(_.resource)
.map(cmw)

def setUserAgent[F[_]: MonadCancelThrow](userAgent: `User-Agent`): Middleware[F] = { client =>
Client[F](req => client.run(req.putHeaders(userAgent)))
}

def disableFollowRedirect[F[_]](builder: OkHttpBuilder[F]): OkHttpBuilder[F] =
builder.withOkHttpClient(
builder.okHttpClient.newBuilder().followRedirects(false).build()
)
}
Expand Up @@ -30,6 +30,8 @@ trait UrlChecker[F[_]] {
def exists(url: Uri): F[Boolean]
}

final case class UrlCheckerClient[F[_]](client: Client[F]) extends AnyVal

object UrlChecker {
private def buildCache[F[_]](config: Config)(implicit F: Sync[F]): F[CaffeineCache[F, Status]] =
F.delay {
Expand All @@ -42,7 +44,7 @@ object UrlChecker {
}

def create[F[_]](config: Config)(implicit
client: Client[F],
urlCheckerClient: UrlCheckerClient[F],
logger: Logger[F],
F: Sync[F]
): F[UrlChecker[F]] =
Expand All @@ -55,7 +57,7 @@ object UrlChecker {

private def status(url: Uri): F[Status] =
statusCache.cachingForMemoizeF(url.renderString)(None) {
client.status(Request[F](method = Method.HEAD, uri = url))
urlCheckerClient.client.status(Request[F](method = Method.HEAD, uri = url))
}
}
}
Expand Down
@@ -0,0 +1,86 @@
package org.scalasteward.core.client

import org.http4s.client._
import org.http4s.headers.`User-Agent`
import cats.effect._
import cats.implicits._
import org.http4s.implicits._
import org.typelevel.ci._
import munit.CatsEffectSuite
import org.http4s.HttpRoutes
import org.http4s.headers.Location

class ClientConfigurationTest extends CatsEffectSuite {
private val userAgentValue = "my-user-agent"
private val dummyUserAgent =
`User-Agent`.parse(userAgentValue).getOrElse(fail("unable to create user agent"))

private val routes: HttpRoutes[IO] = {
import org.http4s.dsl.io._
HttpRoutes.of[IO] {
case req @ GET -> Root / "user-agent" =>
req.headers.get(ci"user-agent") match {
case Some(value) =>
if (value.head.value == userAgentValue) Ok("success")
else
IO.raiseError(
new RuntimeException(s"expected $userAgentValue but got ${value.head.value}")
)
case None =>
BadRequest("No user-agent")
}

case GET -> Root / "redirect" =>
Found(Location(uri"/redirected"))

case GET -> Root / "redirected" =>
BadRequest("Got redirected")
}
}

test("setUserAgent add a specific user agent to requests") {
import org.http4s.client.dsl.io._
import org.http4s.Method._

val initialClient = Client.fromHttpApp[IO](routes.orNotFound)
val setUserAgent = ClientConfiguration.setUserAgent[IO](dummyUserAgent)
val newClient = setUserAgent(initialClient)

newClient
.run(GET(uri"/user-agent"))
.use(r => r.status.code.pure[IO])
.assertEquals(200)

initialClient
.run(GET(uri"/user-agent"))
.use(r => r.status.code.pure[IO])
.assertEquals(400)
}

test("disableFollowRedirect does not follow redirect") {
import org.http4s.blaze.server._
import org.http4s.client.dsl.io._
import org.http4s.Method._

val regularClient = ClientConfiguration.build[IO](
ClientConfiguration.BuilderMiddleware.default,
ClientConfiguration.setUserAgent(dummyUserAgent)
)
val disabledClient = ClientConfiguration.build[IO](
ClientConfiguration.disableFollowRedirect,
ClientConfiguration.setUserAgent(dummyUserAgent)
)
val getServer =
BlazeServerBuilder[IO].bindAny("localhost").withHttpApp(routes.orNotFound).resource

val test = (regularClient, disabledClient, getServer).tupled.use {
case (regClient, disClient, s) =>
val fullUri = s.baseUri / "redirect"
(
regClient.run(GET(fullUri)).use(_.status.code.pure[IO]),
disClient.run(GET(fullUri)).use(_.status.code.pure[IO])
).tupled
}
test.assertEquals((400, 302))
}
}
Expand Up @@ -10,10 +10,12 @@ import org.scalasteward.core.io._
import org.scalasteward.core.mock.MockConfig.config
import org.scalasteward.core.repoconfig.RepoConfigLoader
import org.scalasteward.core.update.artifact.ArtifactMigrationsLoader
import org.scalasteward.core.util.UrlCheckerClient
import org.typelevel.log4cats.Logger

object MockContext {
implicit private val client: Client[MockEff] = Client.fromHttpApp(HttpApp.notFound)
implicit private val urlCheckerClient: UrlCheckerClient[MockEff] = UrlCheckerClient(client)
implicit private val fileAlg: FileAlg[MockEff] = new MockFileAlg
implicit private val logger: Logger[MockEff] = new MockLogger
implicit private val processAlg: ProcessAlg[MockEff] = MockProcessAlg.create(config.processCfg)
Expand Down
Expand Up @@ -12,7 +12,7 @@ import org.scalasteward.core.TestSyntax._
import org.scalasteward.core.application.Config.VCSCfg
import org.scalasteward.core.data.ReleaseRelatedUrl
import org.scalasteward.core.mock.MockConfig
import org.scalasteward.core.util.UrlChecker
import org.scalasteward.core.util._

class VCSExtraAlgTest extends FunSuite {
val routes: HttpRoutes[IO] =
Expand All @@ -22,7 +22,8 @@ class VCSExtraAlgTest extends FunSuite {
case _ => NotFound()
}

implicit val client: Client[IO] = Client.fromHttpApp[IO](routes.orNotFound)
implicit val client: UrlCheckerClient[IO] =
UrlCheckerClient[IO](Client.fromHttpApp[IO](routes.orNotFound))
implicit val urlChecker: UrlChecker[IO] =
UrlChecker.create[IO](MockConfig.config).unsafeRunSync()

Expand Down
1 change: 1 addition & 0 deletions project/Dependencies.scala
Expand Up @@ -26,6 +26,7 @@ object Dependencies {
val http4sCirce = "org.http4s" %% "http4s-circe" % http4sCore.revision
val http4sClient = "org.http4s" %% "http4s-client" % http4sCore.revision
val http4sDsl = "org.http4s" %% "http4s-dsl" % http4sCore.revision
val http4sBlazeServer = "org.http4s" %% "http4s-blaze-server" % http4sCore.revision
val http4sOkhttpClient = "org.http4s" %% "http4s-okhttp-client" % http4sCore.revision
val log4catsSlf4j = "org.typelevel" %% "log4cats-slf4j" % "2.1.1"
val logbackClassic = "ch.qos.logback" % "logback-classic" % "1.2.9"
Expand Down

0 comments on commit cd590e2

Please sign in to comment.