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)
}
}