diff --git a/core/src/main/scala/com.snowplowanalytics.snowplow.collector.core/Routes.scala b/core/src/main/scala/com.snowplowanalytics.snowplow.collector.core/Routes.scala
index 8160b285e..0e1e7f2ef 100644
--- a/core/src/main/scala/com.snowplowanalytics.snowplow.collector.core/Routes.scala
+++ b/core/src/main/scala/com.snowplowanalytics.snowplow.collector.core/Routes.scala
@@ -15,8 +15,12 @@ import org.http4s.dsl.Http4sDsl
import org.http4s.implicits._
import com.comcast.ip4s.Dns
-class Routes[F[_]: Sync](enableDefaultRedirect: Boolean, enableRootResponse: Boolean, service: IService[F])
- extends Http4sDsl[F] {
+class Routes[F[_]: Sync](
+ enableDefaultRedirect: Boolean,
+ enableRootResponse: Boolean,
+ enableCrossdomainTracking: Boolean,
+ service: IService[F]
+) extends Http4sDsl[F] {
implicit val dns: Dns[F] = Dns.forSync[F]
@@ -83,8 +87,13 @@ class Routes[F[_]: Sync](enableDefaultRedirect: Boolean, enableRootResponse: Boo
service.rootResponse
}
+ private val crossdomainRoute = HttpRoutes.of[F] {
+ case GET -> Root / "crossdomain.xml" if enableCrossdomainTracking =>
+ service.crossdomainResponse
+ }
+
val value: HttpApp[F] = {
- val routes = healthRoutes <+> corsRoute <+> cookieRoutes <+> rootRoute
+ val routes = healthRoutes <+> corsRoute <+> cookieRoutes <+> rootRoute <+> crossdomainRoute
val res = if (enableDefaultRedirect) routes else rejectRedirect <+> routes
res.orNotFound
}
diff --git a/core/src/main/scala/com.snowplowanalytics.snowplow.collector.core/Run.scala b/core/src/main/scala/com.snowplowanalytics.snowplow.collector.core/Run.scala
index 6a3ec65c7..e8f1fe4db 100644
--- a/core/src/main/scala/com.snowplowanalytics.snowplow.collector.core/Run.scala
+++ b/core/src/main/scala/com.snowplowanalytics.snowplow.collector.core/Run.scala
@@ -79,7 +79,7 @@ object Run {
appInfo
)
httpServer = HttpServer.build[F](
- new Routes[F](config.enableDefaultRedirect, config.rootResponse.enabled, collectorService).value,
+ new Routes[F](config.enableDefaultRedirect, config.rootResponse.enabled, config.crossDomain.enabled, collectorService).value,
if (config.ssl.enable) config.ssl.port else config.port,
config.ssl.enable,
config.networking
diff --git a/core/src/main/scala/com.snowplowanalytics.snowplow.collector.core/Service.scala b/core/src/main/scala/com.snowplowanalytics.snowplow.collector.core/Service.scala
index 52b8f232c..0e62b4b0f 100644
--- a/core/src/main/scala/com.snowplowanalytics.snowplow.collector.core/Service.scala
+++ b/core/src/main/scala/com.snowplowanalytics.snowplow.collector.core/Service.scala
@@ -44,12 +44,12 @@ trait IService[F[_]] {
def determinePath(vendor: String, version: String): String
def sinksHealthy: F[Boolean]
def rootResponse: F[Response[F]]
+ def crossdomainResponse: F[Response[F]]
}
object Service {
// Contains an invisible pixel to return for `/i` requests.
- val pixel = Base64.decodeBase64("R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==")
-
+ val pixel = Base64.decodeBase64("R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==")
val spAnonymousNuid = "00000000-0000-0000-0000-000000000000"
}
@@ -155,6 +155,26 @@ class Service[F[_]: Sync](
)
}
+ def crossdomainResponse: F[Response[F]] = Sync[F].pure {
+ val policy =
+ config
+ .crossDomain
+ .domains
+ .map(d => s"""""")
+ .mkString("\n")
+
+ val xml = s"""
+ |
+ |${policy}
+ |""".stripMargin
+
+ Response[F](
+ status = Ok,
+ body = Stream.emit(xml).through(fs2.text.utf8.encode),
+ headers = Headers(`Content-Type`(MediaType.text.xml))
+ )
+ }
+
def extractHeader(req: Request[F], headerName: String): Option[String] =
req.headers.get(CIString(headerName)).map(_.head.value)
diff --git a/core/src/test/scala/com.snowplowanalytics.snowplow.collector.core/RoutesSpec.scala b/core/src/test/scala/com.snowplowanalytics.snowplow.collector.core/RoutesSpec.scala
index ffc6de9d0..af2f04e0e 100644
--- a/core/src/test/scala/com.snowplowanalytics.snowplow.collector.core/RoutesSpec.scala
+++ b/core/src/test/scala/com.snowplowanalytics.snowplow.collector.core/RoutesSpec.scala
@@ -37,6 +37,9 @@ class RoutesSpec extends Specification {
override def rootResponse: IO[Response[IO]] =
IO.pure(Response(status = Ok, body = Stream.emit("root").through(text.utf8.encode)))
+ override def crossdomainResponse: IO[Response[IO]] =
+ IO.pure(Response(status = Ok, body = Stream.empty))
+
override def cookie(
body: IO[Option[String]],
path: String,
@@ -62,9 +65,13 @@ class RoutesSpec extends Specification {
override def sinksHealthy: IO[Boolean] = IO.pure(true)
}
- def createTestServices(enabledDefaultRedirect: Boolean = true, enableRootResponse: Boolean = false) = {
+ def createTestServices(
+ enabledDefaultRedirect: Boolean = true,
+ enableRootResponse: Boolean = false,
+ enableCrossdomainTracking: Boolean = false
+ ) = {
val service = new TestService()
- val routes = new Routes(enabledDefaultRedirect, enableRootResponse, service).value
+ val routes = new Routes(enabledDefaultRedirect, enableRootResponse, enableCrossdomainTracking, service).value
(service, routes)
}
@@ -244,7 +251,7 @@ class RoutesSpec extends Specification {
test(Method.POST)
}
- "respond to the root route" in {
+ "respond to root route" in {
"enabled return the response" in {
val (_, routes) = createTestServices(enableRootResponse = true)
val request = Request[IO](method = Method.GET, uri = uri"/")
@@ -261,6 +268,24 @@ class RoutesSpec extends Specification {
response.status must beEqualTo(Status.NotFound)
}
}
+
+ "respond to crossdomain route" in {
+ "enabled return the response" in {
+ val (_, routes) = createTestServices(enableCrossdomainTracking = true)
+ val request = Request[IO](method = Method.GET, uri = uri"/crossdomain.xml")
+ val response = routes.run(request).unsafeRunSync()
+
+ response.status must beEqualTo(Status.Ok)
+ }
+ "disabled return NotFound" in {
+ val (_, routes) = createTestServices(enableCrossdomainTracking = false)
+ val request = Request[IO](method = Method.GET, uri = uri"/crossdomain.xml")
+ val response = routes.run(request).unsafeRunSync()
+
+ response.status must beEqualTo(Status.NotFound)
+ }
+ }
+
}
}
diff --git a/core/src/test/scala/com.snowplowanalytics.snowplow.collector.core/ServiceSpec.scala b/core/src/test/scala/com.snowplowanalytics.snowplow.collector.core/ServiceSpec.scala
index c431eb75f..d3024294c 100644
--- a/core/src/test/scala/com.snowplowanalytics.snowplow.collector.core/ServiceSpec.scala
+++ b/core/src/test/scala/com.snowplowanalytics.snowplow.collector.core/ServiceSpec.scala
@@ -1016,5 +1016,13 @@ class ServiceSpec extends Specification {
service.determinePath(vendor, version3) shouldEqual expected3
}
}
+
+ "crossdomainResponse" in {
+ val response = service.crossdomainResponse.unsafeRunSync()
+ val body = response.body.compile.toList.unsafeRunSync().map(_.toChar).mkString
+ body must startWith("""""")
+ body must contain("")
+ body must endWith("")
+ }
}
}