All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twitter.finagle.netty4.http.handler.HeaderValidatorHandler.scala Maven / Gradle / Ivy

There is a newer version: 24.2.0
Show newest version
package com.twitter.finagle.netty4.http.handler

import com.twitter.finagle.http.headers.Rfc7230HeaderValidation
import com.twitter.finagle.http.headers.Rfc7230HeaderValidation.{
  ObsFoldDetected,
  ValidationFailure,
  ValidationSuccess
}
import com.twitter.util.{Return, Throw, Try}
import io.netty.channel.ChannelHandler.Sharable
import io.netty.channel.{ChannelHandlerContext, ChannelInboundHandlerAdapter}
import io.netty.handler.codec.DecoderResult
import io.netty.handler.codec.http.{
  FullHttpMessage,
  HttpHeaders,
  HttpMessage,
  HttpObject,
  LastHttpContent
}

/**
 * HTTP headers validation in Finagle is done in two different places, depending on whether it's
 * inbound or outbound traffic. All inbound headers are validated in the Netty pipeline so we can
 * reject malformed requests earlier, before they enter the Finagle land. This pipeline handler
 * does exactly this. Similar to other Netty components, it sets HTTP object's `DecoderResult` to a
 * failure so the next handler(s) can take an appropriate action (reject if it's a server; convert
 * to an exception if it's a client).
 */
@Sharable
private[netty4] object HeaderValidatorHandler extends ChannelInboundHandlerAdapter {

  val HandlerName: String = "headerValidationHandler"

  override def channelRead(ctx: ChannelHandlerContext, msg: scala.Any): Unit = {
    msg match {
      case msg: FullHttpMessage =>
        // HeaderValidationHandler is installed after stream-aggregating handlers in our pipelines,
        // which means we can see full messages here. Full messages have both headers and trailers.
        validateObject(ctx, msg, msg.headers)
        validateObject(ctx, msg, msg.trailingHeaders)

      case msg: HttpMessage =>
        validateObject(ctx, msg, msg.headers)

      case msg: LastHttpContent =>
        validateObject(ctx, msg, msg.trailingHeaders)

      case _ => ()
    }

    ctx.fireChannelRead(msg)
  }

  private[this] def validateObject(
    ctx: ChannelHandlerContext,
    obj: HttpObject,
    headers: HttpHeaders
  ): Unit = validateHeaders(headers) match {
    case Return(_) => () // nop
    case Throw(ex) =>
      // At least one header is invalid. Since we didn't check them all we clear all of them
      // just in case there are multiple invalid headers. The message shouldn't be used
      // so clearing the header map is just to be super safe.
      headers.clear()
      obj.setDecoderResult(DecoderResult.failure(ex))
  }

  private[this] def validateHeaders(headers: HttpHeaders): Try[Unit] = {
    val it = headers.iteratorCharSequence()
    while (it.hasNext) {
      val header = it.next()
      val name = header.getKey
      val value = header.getValue
      Rfc7230HeaderValidation.validateName(name) match {
        case ValidationFailure(e) => return Throw(e)
        case ValidationSuccess => // () nop
      }

      Rfc7230HeaderValidation.validateValue(name, value) match {
        case ValidationFailure(e) => return Throw(e)
        case ObsFoldDetected =>
          header.setValue(Rfc7230HeaderValidation.replaceObsFold(value))
        case ValidationSuccess => () // nop
      }
    }

    // If we get here everything was validated.
    Try.Unit
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy