def parseRequest()

in http-core/src/main/scala/org/apache/pekko/http/impl/engine/http2/RequestParsing.scala [41:201]


  def parseRequest(httpHeaderParser: HttpHeaderParser, serverSettings: ServerSettings, streamAttributes: Attributes)
      : Http2SubStream => HttpRequest = {

    val remoteAddressHeader: Option[`Remote-Address`] =
      if (serverSettings.remoteAddressHeader) {
        streamAttributes.get[HttpAttributes.RemoteAddress].map(remote =>
          model.headers.`Remote-Address`(RemoteAddress(remote.address)))
        // in order to avoid searching all the time for the attribute, we need to guard it with the setting condition
      } else None // no need to emit the remote address header

    val remoteAddressAttribute: Option[RemoteAddress] =
      if (serverSettings.remoteAddressAttribute) {
        streamAttributes.get[HttpAttributes.RemoteAddress].map(remote => RemoteAddress(remote.address))
      } else None

    val tlsSessionInfoHeader: Option[`Tls-Session-Info`] =
      if (serverSettings.parserSettings.includeTlsSessionInfoHeader) {
        streamAttributes.get[HttpAttributes.TLSSessionInfo].map(sslSessionInfo =>
          model.headers.`Tls-Session-Info`(sslSessionInfo.session))
      } else None

    val sslSessionAttribute: Option[SSLSession] =
      if (serverSettings.parserSettings.includeSslSessionAttribute)
        streamAttributes.get[HttpAttributes.TLSSessionInfo].map(_.session)
      else
        None

    val baseAttributes = {
      var map = Map.empty[AttributeKey[_], Any]
      map = sslSessionAttribute match {
        case Some(sslSession) => map.updated(AttributeKeys.sslSession, SslSessionInfo(sslSession))
        case None             => map
      }
      map = remoteAddressAttribute match {
        case Some(remoteAddress) => map.updated(AttributeKeys.remoteAddress, remoteAddress)
        case None                => map
      }
      map
    }

    { subStream =>
      def createRequest(
          method: HttpMethod,
          scheme: String,
          authority: Uri.Authority,
          pathAndRawQuery: (Uri.Path, Option[String]),
          contentType: OptionVal[ContentType],
          contentLength: Long,
          cookies: StringBuilder,
          headers: VectorBuilder[HttpHeader]): HttpRequest = {
        // https://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.3: these pseudo header fields are mandatory for a request
        checkRequiredPseudoHeader(":scheme", scheme)
        checkRequiredPseudoHeader(":method", method)
        checkRequiredPseudoHeader(":path", pathAndRawQuery)

        if (cookies != null) {
          // Compress 'cookie' headers if present
          headers += parseHeaderPair(httpHeaderParser, "cookie", cookies.toString)
        }
        if (remoteAddressHeader.isDefined) headers += remoteAddressHeader.get

        if (tlsSessionInfoHeader.isDefined) headers += tlsSessionInfoHeader.get

        val entity = subStream.createEntity(contentLength, contentType)

        val (path, rawQueryString) = pathAndRawQuery
        val authorityOrDefault: Uri.Authority = if (authority == null) Uri.Authority.Empty else authority
        val uri = Uri(scheme, authorityOrDefault, path, rawQueryString)
        val attributes = baseAttributes.updated(Http2.streamId, subStream.streamId)

        new HttpRequest(method, uri, headers.result(), attributes, entity, HttpProtocols.`HTTP/2.0`)
      }

      @tailrec
      def rec(
          incomingHeaders: IndexedSeq[(String, AnyRef)],
          offset: Int,
          method: HttpMethod = null,
          scheme: String = null,
          authority: Uri.Authority = null,
          pathAndRawQuery: (Uri.Path, Option[String]) = null,
          contentType: OptionVal[ContentType] = OptionVal.None,
          contentLength: Long = -1,
          cookies: StringBuilder = null,
          seenRegularHeader: Boolean = false,
          headers: VectorBuilder[HttpHeader] = new VectorBuilder[HttpHeader]): HttpRequest =
        if (offset == incomingHeaders.size)
          createRequest(method, scheme, authority, pathAndRawQuery, contentType, contentLength, cookies, headers)
        else {
          import hpack.Http2HeaderParsing._
          val (name, value) = incomingHeaders(offset)
          name match {
            case ":scheme" =>
              checkUniquePseudoHeader(":scheme", scheme)
              checkNoRegularHeadersBeforePseudoHeader(":scheme", seenRegularHeader)
              rec(incomingHeaders, offset + 1, method, Scheme.get(value), authority, pathAndRawQuery, contentType,
                contentLength, cookies, seenRegularHeader, headers)

            case ":method" =>
              checkUniquePseudoHeader(":method", method)
              checkNoRegularHeadersBeforePseudoHeader(":method", seenRegularHeader)

              rec(incomingHeaders, offset + 1, Method.get(value), scheme, authority, pathAndRawQuery, contentType,
                contentLength, cookies, seenRegularHeader, headers)

            case ":path" =>
              checkUniquePseudoHeader(":path", pathAndRawQuery)
              checkNoRegularHeadersBeforePseudoHeader(":path", seenRegularHeader)
              rec(incomingHeaders, offset + 1, method, scheme, authority, PathAndQuery.get(value), contentType,
                contentLength, cookies, seenRegularHeader, headers)

            case ":authority" =>
              checkUniquePseudoHeader(":authority", authority)
              checkNoRegularHeadersBeforePseudoHeader(":authority", seenRegularHeader)

              rec(incomingHeaders, offset + 1, method, scheme, Authority.get(value), pathAndRawQuery, contentType,
                contentLength, cookies, seenRegularHeader, headers)

            case "content-type" =>
              if (contentType.isEmpty)
                rec(incomingHeaders, offset + 1, method, scheme, authority, pathAndRawQuery,
                  OptionVal.Some(ContentType.get(value)), contentLength, cookies, true, headers)
              else
                malformedRequest("HTTP message must not contain more than one content-type header")

            case ":status" =>
              malformedRequest("Pseudo-header ':status' is for responses only; it cannot appear in a request")

            case "content-length" =>
              if (contentLength == -1) {
                val contentLengthValue = ContentLength.get(value).toLong
                if (contentLengthValue < 0)
                  malformedRequest("HTTP message must not contain a negative content-length header")
                rec(incomingHeaders, offset + 1, method, scheme, authority, pathAndRawQuery, contentType,
                  contentLengthValue, cookies, true, headers)
              } else malformedRequest("HTTP message must not contain more than one content-length header")

            case "cookie" =>
              // Compress cookie headers as described here https://tools.ietf.org/html/rfc7540#section-8.1.2.5
              val cookiesBuilder = if (cookies == null) {
                new StringBuilder
              } else {
                cookies.append("; ") // Append octets as required by the spec
              }
              cookiesBuilder.append(Cookie.get(value))
              rec(incomingHeaders, offset + 1, method, scheme, authority, pathAndRawQuery, contentType, contentLength,
                cookiesBuilder, true, headers)

            case _ =>
              rec(incomingHeaders, offset + 1, method, scheme, authority, pathAndRawQuery, contentType, contentLength,
                cookies, true, headers += OtherHeader.get(value))
          }
        }

      val incomingHeaders = subStream.initialHeaders.keyValuePairs.toIndexedSeq
      if (incomingHeaders.size > serverSettings.parserSettings.maxHeaderCount)
        malformedRequest(
          s"HTTP message contains more than the configured limit of ${serverSettings.parserSettings.maxHeaderCount} headers")
      else rec(incomingHeaders, 0)
    }
  }