<commit_before>package org.http4s
package client
package middleware

import cats.effect._
import cats.implicits._
import fs2._
import org.http4s.Method._
import org.http4s.headers._
import org.http4s.util.CaseInsensitiveString
import _root_.io.chrisdavenport.vault._

/**
  * Client middleware to follow redirect responses.
  *
  * A 301 or 302 response is followed by:
  * - a GET if the request was GET or POST
  * - a HEAD if the request was a HEAD
  * - the original request method and body if the body had no effects
  * - the redirect is not followed otherwise
  *
  * A 303 response is followed by:
  * - a HEAD if the request was a HEAD
  * - a GET for all other methods
  *
  * A 307 or 308 response is followed by:
  * - the original request method and body, if the body had no effects
  * - the redirect is not followed otherwise
  *
  * Whenever we follow with a GET or HEAD, an empty body is sent, and
  * all payload headers defined in https://tools.ietf.org/html/rfc7231#section-3.3
  * are stripped.
  *
  * If the response does not contain a valid Location header, the redirect is
  * not followed.
  *
  * Headers whose names match `sensitiveHeaderFilter` are not exposed when
  * redirecting to a different authority.
  */
object FollowRedirect {
  def apply[F[_]](
      maxRedirects: Int,
      sensitiveHeaderFilter: CaseInsensitiveString => Boolean = Headers.SensitiveHeaders)(
      client: Client[F])(implicit F: Bracket[F, Throwable]): Client[F] = {
    def prepareLoop(req: Request[F], redirects: Int): Resource[F, Response[F]] =
      client.run(req).flatMap { resp =>
        def redirectUri =
          resp.headers.get(Location).map { loc =>
            val uri = loc.uri
            // https://tools.ietf.org/html/rfc7231#section-7.1.2
            uri.copy(
              scheme = uri.scheme.orElse(req.uri.scheme),
              authority = uri.authority.orElse(req.uri.authority),
              fragment = uri.fragment.orElse(req.uri.fragment)
            )
          }

        def pureBody: Option[Stream[F, Byte]] = Some(req.body)

        def dontRedirect: Resource[F, Response[F]] = resp.pure[Resource[F, ?]]

        def stripSensitiveHeaders(nextUri: Uri): Request[F] =
          if (req.uri.authority != nextUri.authority)
            req.transformHeaders(_.filterNot(h => sensitiveHeaderFilter(h.name)))
          else
            req

        def propagateCookies(req: Request[F]): Request[F] =
          resp.cookies.foldLeft(req) {
            case (nextReq, cookie) => nextReq.addCookie(cookie.name, cookie.content)
          }

        def nextRequest(method: Method, nextUri: Uri, bodyOpt: Option[Stream[F, Byte]])
          : Request[F] =
          bodyOpt match {
            case Some(body) =>
              propagateCookies(stripSensitiveHeaders(nextUri))
                .withMethod(method)
                .withUri(nextUri)
                .withBodyStream(body)
            case None =>
              propagateCookies(stripSensitiveHeaders(nextUri))
                .withMethod(method)
                .withUri(nextUri)
                .withEmptyBody
          }

        def doRedirect(method: Method): Resource[F, Response[F]] =
          if (redirects < maxRedirects) {
            // If we get a redirect response without a location, then there is
            // nothing to redirect.
            redirectUri.fold(dontRedirect) { nextUri =>
              // We can only redirect safely if there is no body or if we've
              // verified that the body is pure.
              val nextReq: Option[Request[F]] = method match {
                case GET | HEAD =>
                  Option(nextRequest(method, nextUri, None))
                case _ =>
                  pureBody.map(body => nextRequest(method, nextUri, Some(body)))
              }
              nextReq.fold(dontRedirect)(req =>
                prepareLoop(req, redirects + 1)
                  .map(response => {
                    val redirectUris = getRedirectUris(response)
                    response<commit_msg>Propagate cookies in FollowRedirect client. Only in same authority.<commit_after>package org.http4s
package client
package middleware

import cats.effect._
import cats.implicits._
import fs2._
import org.http4s.Method._
import org.http4s.headers._
import org.http4s.util.CaseInsensitiveString
import _root_.io.chrisdavenport.vault._

/**
  * Client middleware to follow redirect responses.
  *
  * A 301 or 302 response is followed by:
  * - a GET if the request was GET or POST
  * - a HEAD if the request was a HEAD
  * - the original request method and body if the body had no effects
  * - the redirect is not followed otherwise
  *
  * A 303 response is followed by:
  * - a HEAD if the request was a HEAD
  * - a GET for all other methods
  *
  * A 307 or 308 response is followed by:
  * - the original request method and body, if the body had no effects
  * - the redirect is not followed otherwise
  *
  * Whenever we follow with a GET or HEAD, an empty body is sent, and
  * all payload headers defined in https://tools.ietf.org/html/rfc7231#section-3.3
  * are stripped.
  *
  * If the response does not contain a valid Location header, the redirect is
  * not followed.
  *
  * Headers whose names match `sensitiveHeaderFilter` are not exposed when
  * redirecting to a different authority.
  */
object FollowRedirect {
  def apply[F[_]](
      maxRedirects: Int,
      sensitiveHeaderFilter: CaseInsensitiveString => Boolean = Headers.SensitiveHeaders)(
      client: Client[F])(implicit F: Bracket[F, Throwable]): Client[F] = {
    def prepareLoop(req: Request[F], redirects: Int): Resource[F, Response[F]] =
      client.run(req).flatMap { resp =>
        def redirectUri =
          resp.headers.get(Location).map { loc =>
            val uri = loc.uri
            // https://tools.ietf.org/html/rfc7231#section-7.1.2
            uri.copy(
              scheme = uri.scheme.orElse(req.uri.scheme),
              authority = uri.authority.orElse(req.uri.authority),
              fragment = uri.fragment.orElse(req.uri.fragment)
            )
          }

        def pureBody: Option[Stream[F, Byte]] = Some(req.body)

        def dontRedirect: Resource[F, Response[F]] = resp.pure[Resource[F, ?]]

        def stripSensitiveHeaders(nextUri: Uri): Request[F] =
          if (req.uri.authority != nextUri.authority)
            req.transformHeaders(_.filterNot(h => sensitiveHeaderFilter(h.name)))
          else
            req

        def propagateCookies(req: Request[F], nextUri: Uri): Request[F] =
          if (req.uri.authority == nextUri.authority)
            resp.cookies.foldLeft(req) {
              case (nextReq, cookie) => nextReq.addCookie(cookie.name, cookie.content)
            }
          else
            req

        def nextRequest(method: Method, nextUri: Uri, bodyOpt: Option[Stream[F, Byte]]): Request[F] =
          bodyOpt match {
            case Some(body) =>
              propagateCookies(stripSensitiveHeaders(nextUri), nextUri)
                .withMethod(method)
                .withUri(nextUri)
                .withBodyStream(body)
            case None =>
              propagateCookies(stripSensitiveHeaders(nextUri), nextUri)
                .withMethod(method)
                .withUri(nextUri)
                .withEmptyBody
          }

        def doRedirect(method: Method): Resource[F, Response[F]] =
          if (redirects < maxRedirects) {
            // If we get a redirect response without a location, then there is
            // nothing to redirect.
            redirectUri.fold(dontRedirect) { nextUri =>
              // We can only redirect safely if there is no body or if we've
              // verified that the body is pure.
              val nextReq: Option[Request[F]] = method match {
                case GET | HEAD =>
                  Option(nextRequest(method, nextUri, None))
                case _ =>
                  pureBody.map(body => nextRequest(method, nextUri, Some(body)))
              }
              nextReq.fold(dontRedirect)(req =>
                prepareLoop(req, redirects + 1)
                  .map(response => {
                    val redirectUris = getRedirectUris(response)
                    response
{'fraction_non_alphanumeric': 0.09556686046511628, 'fraction_numerical': 0.008236434108527133, 'mean_word_length': 2.133586337760911, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 29, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5760090', 'n_tokens_mistral': 2375, 'n_tokens_neox': 2255, 'n_words': 674}