/** @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"

using namespace std::literals;

bool
HeaderValidator::is_h2_h3_header_valid(const HTTPHdr &hdr, bool is_response, bool is_trailing_header)
{
  const MIMEField *field = nullptr;
  MIMEFieldIter    iter;
  auto             method_field       = hdr.field_find(PSEUDO_HEADER_METHOD);
  bool             has_connect_method = false;
  if (method_field) {
    auto method{method_field->value_get()};
    has_connect_method = method == static_cast<std::string_view>(HTTP_METHOD_CONNECT);
  }
  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) {
    auto name{field.name_get()};
    // Pseudo headers must appear before regular headers
    if (name.length() && name[0] == ':') {
      ++pseudo_header_count;
      if (pseudo_header_count > expected_pseudo_header_count) {
        return false;
      }
    } else if (name.length() == 0) {
      return false;
    } else {
      if (pseudo_header_count != expected_pseudo_header_count) {
        return false;
      }
    }
  }

  // rfc9113,sec8.2.2 and rfc9114,sec4.2: Any message containing
  // connection-specific header fields MUST be treated as malformed.
  if (hdr.field_find(static_cast<std::string_view>(MIME_FIELD_CONNECTION)) != nullptr ||
      hdr.field_find(static_cast<std::string_view>(MIME_FIELD_KEEP_ALIVE)) != nullptr ||
      hdr.field_find(static_cast<std::string_view>(MIME_FIELD_PROXY_CONNECTION)) != nullptr ||
      hdr.field_find(static_cast<std::string_view>(MIME_FIELD_TRANSFER_ENCODING)) != nullptr ||
      hdr.field_find(static_cast<std::string_view>(MIME_FIELD_UPGRADE)) != nullptr) {
    return false;
  }

  // :path pseudo header MUST NOT empty for http or https URIs
  field = hdr.field_find(PSEUDO_HEADER_PATH);
  if (field) {
    auto value{field->value_get()};
    if (value.length() == 0) {
      return false;
    }
  }

  // when The TE header field is received, it MUST NOT contain any
  // value other than "trailers".
  field = hdr.field_find(static_cast<std::string_view>(MIME_FIELD_TE));
  if (field) {
    auto value{field->value_get()};
    if (value != "trailers"sv) {
      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) == 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) == nullptr || hdr.field_find(PSEUDO_HEADER_METHOD) == nullptr ||
          hdr.field_find(PSEUDO_HEADER_PATH) == nullptr || hdr.field_find(PSEUDO_HEADER_AUTHORITY) == nullptr ||
          hdr.field_find(PSEUDO_HEADER_STATUS) != nullptr) {
        // Decoded header field is invalid
        return false;
      }
    } else if (has_connect_method && hdr.fields_count() >= 2) {
      if (hdr.field_find(PSEUDO_HEADER_SCHEME) != nullptr || hdr.field_find(PSEUDO_HEADER_METHOD) == nullptr ||
          hdr.field_find(PSEUDO_HEADER_PATH) != nullptr || hdr.field_find(PSEUDO_HEADER_AUTHORITY) == nullptr ||
          hdr.field_find(PSEUDO_HEADER_STATUS) != nullptr) {
        // Decoded header field is invalid
        return false;
      }

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