/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// HttpLog.h should generally be included first
#include "HttpLog.h"

#include "nsHttpHeaderArray.h"
#include "nsURLHelper.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsHttpHandler.h"

namespace mozilla {
namespace net {

//-----------------------------------------------------------------------------
// nsHttpHeaderArray <public>
//-----------------------------------------------------------------------------

nsresult nsHttpHeaderArray::SetHeader(
    const nsACString& headerName, const nsACString& value, bool merge,
    nsHttpHeaderArray::HeaderVariety variety) {
  nsHttpAtom header = nsHttp::ResolveAtom(headerName);
  if (!header) {
    NS_WARNING("failed to resolve atom");
    return NS_ERROR_NOT_AVAILABLE;
  }
  return SetHeader(header, headerName, value, merge, variety);
}

nsresult nsHttpHeaderArray::SetHeader(
    const nsHttpAtom& header, const nsACString& value, bool merge,
    nsHttpHeaderArray::HeaderVariety variety) {
  return SetHeader(header, ""_ns, value, merge, variety);
}

nsresult nsHttpHeaderArray::SetHeader(
    const nsHttpAtom& header, const nsACString& headerName,
    const nsACString& value, bool merge,
    nsHttpHeaderArray::HeaderVariety variety) {
  MOZ_ASSERT(
      (variety == eVarietyResponse) || (variety == eVarietyRequestDefault) ||
          (variety == eVarietyRequestOverride) ||
          (variety == eVarietyResponseOverride) ||
          (variety == eVarietyRequestEnforceDefault),
      "Net original headers can only be set using SetHeader_internal().");

  nsEntry* entry = nullptr;
  int32_t index = LookupEntry(header, &entry);

  // If an empty value is received and we aren't merging headers discard it,
  // unless we're overriding
  if (value.IsEmpty() && header != nsHttp::X_Frame_Options &&
      variety != eVarietyResponseOverride) {
    if (!merge && entry) {
      if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
        MOZ_ASSERT(variety == eVarietyResponse);
        entry->variety = eVarietyResponseNetOriginal;
      } else {
        mHeaders.RemoveElementAt(index);
      }
    }
    return NS_OK;
  }

  MOZ_ASSERT((variety == eVarietyRequestEnforceDefault) ||
                 (!entry || variety != eVarietyRequestDefault),
             "Cannot set default entry which overrides existing entry!");

  // Set the variety to default if we are enforcing it.
  if (variety == eVarietyRequestEnforceDefault) {
    variety = eVarietyRequestDefault;
  }
  if (!entry) {
    return SetHeader_internal(header, headerName, value, variety);
  }
  if (merge && !IsSingletonHeader(header)) {
    return MergeHeader(header, entry, value, variety);
  }
  if (!IsIgnoreMultipleHeader(header)) {
    // Replace the existing string with the new value
    if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
      MOZ_ASSERT(variety == eVarietyResponse ||
                 variety == eVarietyResponseOverride);
      entry->variety = eVarietyResponseNetOriginal;
      return SetHeader_internal(header, headerName, value, variety);
    }
    entry->value = value;
    entry->variety = variety;
  }

  return NS_OK;
}

nsresult nsHttpHeaderArray::SetHeader_internal(
    const nsHttpAtom& header, const nsACString& headerName,
    const nsACString& value, nsHttpHeaderArray::HeaderVariety variety) {
  nsEntry* entry = mHeaders.AppendElement();
  if (!entry) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  entry->header = header;
  // Only save original form of a header if it is different than the header
  // atom string.
  if (!headerName.Equals(header.get())) {
    entry->headerNameOriginal = headerName;
  }
  entry->value = value;
  entry->variety = variety;
  return NS_OK;
}

nsresult nsHttpHeaderArray::SetEmptyHeader(const nsACString& headerName,
                                           HeaderVariety variety) {
  nsHttpAtom header = nsHttp::ResolveAtom(headerName);
  if (!header) {
    NS_WARNING("failed to resolve atom");
    return NS_ERROR_NOT_AVAILABLE;
  }

  MOZ_ASSERT((variety == eVarietyResponse) ||
                 (variety == eVarietyRequestDefault) ||
                 (variety == eVarietyRequestOverride),
             "Original headers can only be set using SetHeader_internal().");
  nsEntry* entry = nullptr;

  LookupEntry(header, &entry);

  if (entry && entry->variety != eVarietyResponseNetOriginalAndResponse) {
    entry->value.Truncate();
    return NS_OK;
  }
  if (entry) {
    MOZ_ASSERT(variety == eVarietyResponse);
    entry->variety = eVarietyResponseNetOriginal;
  }

  return SetHeader_internal(header, headerName, ""_ns, variety);
}

nsresult nsHttpHeaderArray::SetHeaderFromNet(
    const nsHttpAtom& header, const nsACString& headerNameOriginal,
    const nsACString& value, bool response) {
  // mHeader holds the consolidated (merged or updated) headers.
  // mHeader for response header will keep the original heades as well.
  nsEntry* entry = nullptr;

  LookupEntry(header, &entry);

  if (!entry) {
    HeaderVariety variety = eVarietyRequestOverride;
    if (response) {
      variety = eVarietyResponseNetOriginalAndResponse;
    }
    return SetHeader_internal(header, headerNameOriginal, value, variety);
  }
  if (!IsSingletonHeader(header)) {
    HeaderVariety variety = eVarietyRequestOverride;
    if (response) {
      variety = eVarietyResponse;
    }
    nsresult rv = MergeHeader(header, entry, value, variety);
    if (NS_FAILED(rv)) {
      return rv;
    }
    if (response) {
      rv = SetHeader_internal(header, headerNameOriginal, value,
                              eVarietyResponseNetOriginal);
    }
    return rv;
  }
  if (!IsIgnoreMultipleHeader(header)) {
    // Multiple instances of non-mergeable header received from network
    // - ignore if same value
    if (header == nsHttp::Content_Length) {
      // Content length header needs special handling.
      // For e.g. for CL all the below headers evaluates to value of X
      // Content-Length: X
      // Content-Length: X, X, X
      // Content-Length: X \n\r Content-Length: X
      // remove duplicate values from the header-values for comparison

      nsAutoCString headerValue;
      RemoveDuplicateHeaderValues(value, headerValue);

      nsAutoCString entryValue;
      RemoveDuplicateHeaderValues(entry->value, entryValue);
      if (entryValue != headerValue) {
        // reply may be corrupt/hacked (ex: CLRF injection attacks)
        return NS_ERROR_CORRUPTED_CONTENT;
      }
    } else if (!entry->value.Equals(value)) {  // compare remaining headers
      if (IsSuspectDuplicateHeader(header)) {
        // reply may be corrupt/hacked (ex: CLRF injection attacks)
        return NS_ERROR_CORRUPTED_CONTENT;
      }  // else silently drop value: keep value from 1st header seen
      LOG(("Header %s silently dropped as non mergeable header\n",
           header.get()));
    }

    if (response) {
      return SetHeader_internal(header, headerNameOriginal, value,
                                eVarietyResponseNetOriginal);
    }
  }

  return NS_OK;
}

nsresult nsHttpHeaderArray::SetResponseHeaderFromCache(
    const nsHttpAtom& header, const nsACString& headerNameOriginal,
    const nsACString& value, nsHttpHeaderArray::HeaderVariety variety) {
  MOZ_ASSERT(
      (variety == eVarietyResponse) || (variety == eVarietyResponseNetOriginal),
      "Headers from cache can only be eVarietyResponse and "
      "eVarietyResponseNetOriginal");

  if (variety == eVarietyResponseNetOriginal) {
    return SetHeader_internal(header, headerNameOriginal, value,
                              eVarietyResponseNetOriginal);
  }
  nsTArray<nsEntry>::index_type index = 0;
  do {
    index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
    if (index !=
        CopyableTArray<mozilla::net::nsHttpHeaderArray::nsEntry>::NoIndex) {
      nsEntry& entry = mHeaders[index];
      if (value.Equals(entry.value)) {
        MOZ_ASSERT(
            (entry.variety == eVarietyResponseNetOriginal) ||
                (entry.variety == eVarietyResponseNetOriginalAndResponse),
            "This array must contain only eVarietyResponseNetOriginal"
            " and eVarietyResponseNetOriginalAndRespons headers!");
        entry.variety = eVarietyResponseNetOriginalAndResponse;
        return NS_OK;
      }
      index++;
    }
  } while (index !=
           CopyableTArray<mozilla::net::nsHttpHeaderArray::nsEntry>::NoIndex);
  // If we are here, we have not found an entry so add a new one.
  return SetHeader_internal(header, headerNameOriginal, value,
                            eVarietyResponse);
}

void nsHttpHeaderArray::ClearHeader(const nsHttpAtom& header) {
  nsEntry* entry = nullptr;
  int32_t index = LookupEntry(header, &entry);
  if (entry) {
    if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
      entry->variety = eVarietyResponseNetOriginal;
    } else {
      mHeaders.RemoveElementAt(index);
    }
  }
}

const char* nsHttpHeaderArray::PeekHeader(const nsHttpAtom& header) const {
  const nsEntry* entry = nullptr;
  LookupEntry(header, &entry);
  return entry ? entry->value.get() : nullptr;
}

nsresult nsHttpHeaderArray::GetHeader(const nsHttpAtom& header,
                                      nsACString& result) const {
  const nsEntry* entry = nullptr;
  LookupEntry(header, &entry);
  if (!entry) return NS_ERROR_NOT_AVAILABLE;
  result = entry->value;
  return NS_OK;
}

nsresult nsHttpHeaderArray::GetOriginalHeader(const nsHttpAtom& aHeader,
                                              nsIHttpHeaderVisitor* aVisitor) {
  NS_ENSURE_ARG_POINTER(aVisitor);
  uint32_t index = 0;
  nsresult rv = NS_ERROR_NOT_AVAILABLE;
  while (true) {
    index = mHeaders.IndexOf(aHeader, index, nsEntry::MatchHeader());
    if (index != UINT32_MAX) {
      const nsEntry& entry = mHeaders[index];

      MOZ_ASSERT((entry.variety == eVarietyResponseNetOriginalAndResponse) ||
                     (entry.variety == eVarietyResponseNetOriginal) ||
                     (entry.variety == eVarietyResponse) ||
                     (entry.variety == eVarietyResponseOverride),
                 "This must be a response header.");
      index++;
      if (entry.variety == eVarietyResponse ||
          entry.variety == eVarietyResponseOverride) {
        continue;
      }

      const nsCString& hdr = entry.headerNameOriginal.IsEmpty()
                                 ? entry.header.val()
                                 : entry.headerNameOriginal;

      rv = NS_OK;
      if (NS_FAILED(aVisitor->VisitHeader(hdr, entry.value))) {
        break;
      }
    } else {
      // if there is no such a header, it will return
      // NS_ERROR_NOT_AVAILABLE or NS_OK otherwise.
      return rv;
    }
  }
  return NS_OK;
}

bool nsHttpHeaderArray::HasHeader(const nsHttpAtom& header) const {
  const nsEntry* entry = nullptr;
  LookupEntry(header, &entry);
  return entry;
}

nsresult nsHttpHeaderArray::VisitHeaders(
    nsIHttpHeaderVisitor* visitor, nsHttpHeaderArray::VisitorFilter filter) {
  NS_ENSURE_ARG_POINTER(visitor);
  nsresult rv;

  uint32_t i, count = mHeaders.Length();
  for (i = 0; i < count; ++i) {
    const nsEntry& entry = mHeaders[i];
    if (filter == eFilterSkipDefault &&
        entry.variety == eVarietyRequestDefault) {
      continue;
    }
    if (filter == eFilterResponse &&
        entry.variety == eVarietyResponseNetOriginal) {
      continue;
    }
    if (filter == eFilterResponseOriginal &&
        entry.variety == eVarietyResponse) {
      continue;
    }

    const nsCString& hdr = entry.headerNameOriginal.IsEmpty()
                               ? entry.header.val()
                               : entry.headerNameOriginal;
    rv = visitor->VisitHeader(hdr, entry.value);
    if (NS_FAILED(rv)) {
      return rv;
    }
  }
  return NS_OK;
}

/*static*/
nsresult nsHttpHeaderArray::ParseHeaderLine(const nsACString& line,
                                            nsHttpAtom* hdr,
                                            nsACString* headerName,
                                            nsACString* val) {
  //
  // BNF from section 4.2 of RFC 2616:
  //
  //   message-header = field-name ":" [ field-value ]
  //   field-name     = token
  //   field-value    = *( field-content | LWS )
  //   field-content  = <the OCTETs making up the field-value
  //                     and consisting of either *TEXT or combinations
  //                     of token, separators, and quoted-string>
  //

  // We skip over mal-formed headers in the hope that we'll still be able to
  // do something useful with the response.
  int32_t split = line.FindChar(':');

  if (split == kNotFound) {
    LOG(("malformed header [%s]: no colon\n", PromiseFlatCString(line).get()));
    return NS_ERROR_FAILURE;
  }

  const nsACString& sub = Substring(line, 0, split);
  const nsACString& sub2 =
      Substring(line, split + 1, line.Length() - split - 1);

  // make sure we have a valid token for the field-name
  if (!nsHttp::IsValidToken(sub)) {
    LOG(("malformed header [%s]: field-name not a token\n",
         PromiseFlatCString(line).get()));
    return NS_ERROR_FAILURE;
  }

  nsHttpAtom atom = nsHttp::ResolveAtom(sub);
  if (!atom) {
    LOG(("failed to resolve atom [%s]\n", PromiseFlatCString(line).get()));
    return NS_ERROR_FAILURE;
  }

  // skip over whitespace
  char* p =
      net_FindCharNotInSet(sub2.BeginReading(), sub2.EndReading(), HTTP_LWS);

  // trim trailing whitespace - bug 86608
  char* p2 = net_RFindCharNotInSet(p, sub2.EndReading(), HTTP_LWS);

  // assign return values
  if (hdr) *hdr = atom;
  if (val) val->Assign(p, p2 - p + 1);
  if (headerName) headerName->Assign(sub);

  return NS_OK;
}

void nsHttpHeaderArray::Flatten(nsACString& buf, bool pruneProxyHeaders,
                                bool pruneTransients) const {
  auto shouldInclude = [&](const nsEntry& entry) {
    if (entry.variety == eVarietyResponseNetOriginal) return false;
    if (pruneProxyHeaders && (entry.header == nsHttp::Proxy_Authorization ||
                              entry.header == nsHttp::Proxy_Connection)) {
      return false;
    }
    if (pruneTransients &&
        (entry.value.IsEmpty() || entry.header == nsHttp::Connection ||
         entry.header == nsHttp::Proxy_Connection ||
         entry.header == nsHttp::Keep_Alive ||
         entry.header == nsHttp::WWW_Authenticate ||
         entry.header == nsHttp::Proxy_Authenticate ||
         entry.header == nsHttp::Trailer ||
         entry.header == nsHttp::Transfer_Encoding ||
         entry.header == nsHttp::Upgrade ||
         // XXX this will cause problems when we start honoring
         // Cache-Control: no-cache="set-cookie", what to do?
         entry.header == nsHttp::Set_Cookie)) {
      return false;
    }
    return true;
  };

  uint32_t totalSize = 0;
  for (const auto& entry : mHeaders) {
    if (!shouldInclude(entry)) continue;
    totalSize += (entry.headerNameOriginal.IsEmpty()
                      ? entry.header.val().Length()
                      : entry.headerNameOriginal.Length()) +
                 2 + entry.value.Length() + 2;
  }
  buf.SetCapacity(buf.Length() + totalSize);

  for (const auto& entry : mHeaders) {
    if (!shouldInclude(entry)) continue;
    if (entry.headerNameOriginal.IsEmpty()) {
      buf.Append(entry.header.val());
    } else {
      buf.Append(entry.headerNameOriginal);
    }
    buf.AppendLiteral(": ");
    buf.Append(entry.value);
    buf.AppendLiteral("\r\n");
  }
}

void nsHttpHeaderArray::FlattenOriginalHeader(nsACString& buf) {
  uint32_t totalSize = 0;
  for (const auto& entry : mHeaders) {
    if (entry.variety == eVarietyResponse) continue;
    totalSize += (entry.headerNameOriginal.IsEmpty()
                      ? entry.header.val().Length()
                      : entry.headerNameOriginal.Length()) +
                 2 + entry.value.Length() + 2;
  }
  buf.SetCapacity(buf.Length() + totalSize);

  for (const auto& entry : mHeaders) {
    if (entry.variety == eVarietyResponse) continue;
    if (entry.headerNameOriginal.IsEmpty()) {
      buf.Append(entry.header.val());
    } else {
      buf.Append(entry.headerNameOriginal);
    }
    buf.AppendLiteral(": ");
    buf.Append(entry.value);
    buf.AppendLiteral("\r\n");
  }
}

const char* nsHttpHeaderArray::PeekHeaderAt(
    uint32_t index, nsHttpAtom& header, nsACString& headerNameOriginal) const {
  const nsEntry& entry = mHeaders[index];

  header = entry.header;
  headerNameOriginal = entry.headerNameOriginal;
  return entry.value.get();
}

void nsHttpHeaderArray::Clear() { mHeaders.Clear(); }

}  // namespace net
}  // namespace mozilla
