/** @file
 *
 *  A brief file description
 *
 *  @section license License
 *
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

#include "proxy/hdrs/HeaderValidator.h"
#include "proxy/hdrs/HTTP.h"

bool
HeaderValidator::is_h2_h3_header_valid(const HTTPHdr &hdr, bool is_response, bool is_trailing_header)
{
  const MIMEField *field     = nullptr;
  const char      *name      = nullptr;
  int              name_len  = 0;
  const char      *value     = nullptr;
  int              value_len = 0;
  MIMEFieldIter    iter;
  auto             method_field       = hdr.field_find(PSEUDO_HEADER_METHOD.data(), PSEUDO_HEADER_METHOD.size());
  bool             has_connect_method = false;
  if (method_field) {
    int         method_len;
    const char *method_value = method_field->value_get(&method_len);
    has_connect_method       = method_len == HTTP_LEN_CONNECT && strncmp(HTTP_METHOD_CONNECT, method_value, HTTP_LEN_CONNECT) == 0;
  }
  unsigned int expected_pseudo_header_count = is_response ? 1 : has_connect_method ? 2 : 4;
  unsigned int pseudo_header_count          = 0;

  if (is_trailing_header) {
    expected_pseudo_header_count = 0;
  }
  for (auto &field : hdr) {
    name = field.name_get(&name_len);
    // Pseudo headers must appear before regular headers
    if (name_len && name[0] == ':') {
      ++pseudo_header_count;
      if (pseudo_header_count > expected_pseudo_header_count) {
        return false;
      }
    } else if (name_len <= 0) {
      return false;
    } else {
      if (pseudo_header_count != expected_pseudo_header_count) {
        return false;
      }
    }
  }

  // rfc7540,sec8.1.2.2 and rfc9114,sec4.2: Any message containing
  // connection-specific header fields MUST be treated as malformed.
  if (hdr.field_find(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION) != nullptr ||
      hdr.field_find(MIME_FIELD_KEEP_ALIVE, MIME_LEN_KEEP_ALIVE) != nullptr ||
      hdr.field_find(MIME_FIELD_PROXY_CONNECTION, MIME_LEN_PROXY_CONNECTION) != nullptr ||
      hdr.field_find(MIME_FIELD_UPGRADE, MIME_LEN_UPGRADE) != nullptr) {
    return false;
  }

  // :path pseudo header MUST NOT empty for http or https URIs
  field = hdr.field_find(PSEUDO_HEADER_PATH.data(), PSEUDO_HEADER_PATH.size());
  if (field) {
    field->value_get(&value_len);
    if (value_len == 0) {
      return false;
    }
  }

  // when The TE header field is received, it MUST NOT contain any
  // value other than "trailers".
  field = hdr.field_find(MIME_FIELD_TE, MIME_LEN_TE);
  if (field) {
    value = field->value_get(&value_len);
    if (!(value_len == 8 && memcmp(value, "trailers", 8) == 0)) {
      return false;
    }
  }

  if (is_trailing_header) {
    // Done with validation for trailing headers, which doesn't have any pseudo headers.
    return true;
  }
  // Check pseudo headers
  if (is_response) {
    if (hdr.fields_count() >= 1) {
      if (hdr.field_find(PSEUDO_HEADER_STATUS.data(), PSEUDO_HEADER_STATUS.size()) == nullptr) {
        return false;
      }
    } else {
      // There should at least be :status pseudo header.
      return false;
    }
  } else {
    // This is a request.
    if (!has_connect_method && hdr.fields_count() >= 4) {
      if (hdr.field_find(PSEUDO_HEADER_SCHEME.data(), PSEUDO_HEADER_SCHEME.size()) == nullptr ||
          hdr.field_find(PSEUDO_HEADER_METHOD.data(), PSEUDO_HEADER_METHOD.size()) == nullptr ||
          hdr.field_find(PSEUDO_HEADER_PATH.data(), PSEUDO_HEADER_PATH.size()) == nullptr ||
          hdr.field_find(PSEUDO_HEADER_AUTHORITY.data(), PSEUDO_HEADER_AUTHORITY.size()) == nullptr ||
          hdr.field_find(PSEUDO_HEADER_STATUS.data(), PSEUDO_HEADER_STATUS.size()) != nullptr) {
        // Decoded header field is invalid
        return false;
      }
    } else if (has_connect_method && hdr.fields_count() >= 2) {
      if (hdr.field_find(PSEUDO_HEADER_SCHEME.data(), PSEUDO_HEADER_SCHEME.size()) != nullptr ||
          hdr.field_find(PSEUDO_HEADER_METHOD.data(), PSEUDO_HEADER_METHOD.size()) == nullptr ||
          hdr.field_find(PSEUDO_HEADER_PATH.data(), PSEUDO_HEADER_PATH.size()) != nullptr ||
          hdr.field_find(PSEUDO_HEADER_AUTHORITY.data(), PSEUDO_HEADER_AUTHORITY.size()) == nullptr ||
          hdr.field_find(PSEUDO_HEADER_STATUS.data(), PSEUDO_HEADER_STATUS.size()) != nullptr) {
        // Decoded header field is invalid
        return false;
      }

    } else {
      // Pseudo headers is insufficient
      return false;
    }
  }
  return true;
}
