/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set sw=4 ts=8 et tw=80 : */
/* 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/. */

#ifndef nsHttpHeaderArray_h__
#define nsHttpHeaderArray_h__

#include "nsHttp.h"
#include "nsTArray.h"
#include "nsString.h"

class nsIHttpHeaderVisitor;

// This needs to be forward declared here so we can include only this header
// without also including PHttpChannelParams.h
namespace IPC {
    template <typename> struct ParamTraits;
} // namespace IPC

namespace mozilla { namespace net {

class nsHttpHeaderArray
{
public:
    const char *PeekHeader(nsHttpAtom header) const;

    // For nsHttpResponseHead nsHttpHeaderArray will keep track of the original
    // headers as they come from the network and the parse headers used in
    // firefox.
    // If the original and the firefox header are the same, we will keep just
    // one copy and marked it as eVarietyResponseNetOriginalAndResponse.
    // If firefox header representation changes a header coming from the
    // network (e.g. merged it) or a eVarietyResponseNetOriginalAndResponse
    // header has been changed by SetHeader method, we will keep the original
    // header as eVarietyResponseNetOriginal and make a copy for the new header
    // and mark it as eVarietyResponse.
    enum HeaderVariety
    {
        eVarietyUnknown,
        // Used only for request header.
        eVarietyRequestOverride,
        eVarietyRequestDefault,
        // Used only for response header.
        eVarietyResponseNetOriginalAndResponse,
        eVarietyResponseNetOriginal,
        eVarietyResponse
    };

    // Used by internal setters: to set header from network use SetHeaderFromNet
    MOZ_MUST_USE nsresult SetHeader(const nsACString &headerName,
                                    const nsACString &value,
                                    bool merge, HeaderVariety variety);
    MOZ_MUST_USE nsresult SetHeader(nsHttpAtom header, const nsACString &value,
                                    bool merge, HeaderVariety variety);
    MOZ_MUST_USE nsresult SetHeader(nsHttpAtom header,
                                    const nsACString &headerName,
                                    const nsACString &value,
                                    bool merge, HeaderVariety variety);

    // Used by internal setters to set an empty header
    MOZ_MUST_USE nsresult SetEmptyHeader(const nsACString &headerName,
                                         HeaderVariety variety);

    // Merges supported headers. For other duplicate values, determines if error
    // needs to be thrown or 1st value kept.
    // For the response header we keep the original headers as well.
    MOZ_MUST_USE nsresult SetHeaderFromNet(nsHttpAtom header,
                                           const nsACString &headerNameOriginal,
                                           const nsACString &value,
                                           bool response);

    MOZ_MUST_USE nsresult SetResponseHeaderFromCache(nsHttpAtom header,
                                                     const nsACString &headerNameOriginal,
                                                     const nsACString &value,
                                                     HeaderVariety variety);

    MOZ_MUST_USE nsresult GetHeader(nsHttpAtom header, nsACString &value) const;
    MOZ_MUST_USE nsresult GetOriginalHeader(nsHttpAtom aHeader,
                                            nsIHttpHeaderVisitor *aVisitor);
    void     ClearHeader(nsHttpAtom h);

    // Find the location of the given header value, or null if none exists.
    const char *FindHeaderValue(nsHttpAtom header, const char *value) const
    {
        return nsHttp::FindToken(PeekHeader(header), value,
                                 HTTP_HEADER_VALUE_SEPS);
    }

    // Determine if the given header value exists.
    bool HasHeaderValue(nsHttpAtom header, const char *value) const
    {
        return FindHeaderValue(header, value) != nullptr;
    }

    bool HasHeader(nsHttpAtom header) const;

    enum VisitorFilter
    {
        eFilterAll,
        eFilterSkipDefault,
        eFilterResponse,
        eFilterResponseOriginal
    };

    MOZ_MUST_USE nsresult VisitHeaders(nsIHttpHeaderVisitor *visitor,
                                       VisitorFilter filter = eFilterAll);

    // parse a header line, return the header atom and a pointer to the
    // header value (the substring of the header line -- do not free).
    static MOZ_MUST_USE nsresult ParseHeaderLine(const nsACString& line,
                                                 nsHttpAtom *header = nullptr,
                                                 nsACString *headerNameOriginal = nullptr,
                                                 nsACString *value = nullptr);

    void Flatten(nsACString &, bool pruneProxyHeaders, bool pruneTransients);
    void FlattenOriginalHeader(nsACString &);

    uint32_t Count() const { return mHeaders.Length(); }

    const char *PeekHeaderAt(uint32_t i, nsHttpAtom &header,
                             nsACString &headerNameOriginal) const;

    void Clear();

    // Must be copy-constructable and assignable
    struct nsEntry
    {
        nsHttpAtom header;
        nsCString headerNameOriginal;
        nsCString value;
        HeaderVariety variety = eVarietyUnknown;

        struct MatchHeader {
          bool Equals(const nsEntry &aEntry, const nsHttpAtom &aHeader) const {
            return aEntry.header == aHeader;
          }
        };

        bool operator==(const nsEntry& aOther) const
        {
            return header == aOther.header && value == aOther.value;
        }
    };

    bool operator==(const nsHttpHeaderArray& aOther) const
    {
        return mHeaders == aOther.mHeaders;
    }

private:
    // LookupEntry function will never return eVarietyResponseNetOriginal.
    // It will ignore original headers from the network.
    int32_t LookupEntry(nsHttpAtom header, const nsEntry **) const;
    int32_t LookupEntry(nsHttpAtom header, nsEntry **);
    MOZ_MUST_USE nsresult MergeHeader(nsHttpAtom header, nsEntry *entry,
                                      const nsACString &value,
                                      HeaderVariety variety);
    MOZ_MUST_USE nsresult SetHeader_internal(nsHttpAtom header,
                                             const nsACString &headeName,
                                             const nsACString &value,
                                             HeaderVariety variety);

    // Header cannot be merged: only one value possible
    bool    IsSingletonHeader(nsHttpAtom header);
    // Header cannot be merged, and subsequent values should be ignored
    bool    IsIgnoreMultipleHeader(nsHttpAtom header);
    // For some headers we want to track empty values to prevent them being
    // combined with non-empty ones as a CRLF attack vector
    bool    TrackEmptyHeader(nsHttpAtom header);

    // Subset of singleton headers: should never see multiple, different
    // instances of these, else something fishy may be going on (like CLRF
    // injection)
    bool    IsSuspectDuplicateHeader(nsHttpAtom header);

    // All members must be copy-constructable and assignable
    nsTArray<nsEntry> mHeaders;

    friend struct IPC::ParamTraits<nsHttpHeaderArray>;
    friend class nsHttpRequestHead;
};


//-----------------------------------------------------------------------------
// nsHttpHeaderArray <private>: inline functions
//-----------------------------------------------------------------------------

inline int32_t
nsHttpHeaderArray::LookupEntry(nsHttpAtom header, const nsEntry **entry) const
{
    uint32_t index = 0;
    while (index != UINT32_MAX) {
        index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
        if (index != UINT32_MAX) {
            if ((&mHeaders[index])->variety != eVarietyResponseNetOriginal) {
                *entry = &mHeaders[index];
                return index;
            }
            index++;
        }
    }

    return index;
}

inline int32_t
nsHttpHeaderArray::LookupEntry(nsHttpAtom header, nsEntry **entry)
{
    uint32_t index = 0;
    while (index != UINT32_MAX) {
        index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
        if (index != UINT32_MAX) {
            if ((&mHeaders[index])->variety != eVarietyResponseNetOriginal) {
                *entry = &mHeaders[index];
                return index;
            }
            index++;
        }
    }
    return index;
}

inline bool
nsHttpHeaderArray::IsSingletonHeader(nsHttpAtom header)
{
    return header == nsHttp::Content_Type                ||
           header == nsHttp::Content_Disposition         ||
           header == nsHttp::Content_Length              ||
           header == nsHttp::User_Agent                  ||
           header == nsHttp::Referer                     ||
           header == nsHttp::Host                        ||
           header == nsHttp::Authorization               ||
           header == nsHttp::Proxy_Authorization         ||
           header == nsHttp::If_Modified_Since           ||
           header == nsHttp::If_Unmodified_Since         ||
           header == nsHttp::From                        ||
           header == nsHttp::Location                    ||
           header == nsHttp::Max_Forwards                ||
           // Ignore-multiple-headers are singletons in the sense that they
           // shouldn't be merged.
           IsIgnoreMultipleHeader(header);
}

// These are headers for which, in the presence of multiple values, we only
// consider the first.
inline bool nsHttpHeaderArray::IsIgnoreMultipleHeader(nsHttpAtom header)
{
    // https://tools.ietf.org/html/rfc6797#section-8:
    //
    //     If a UA receives more than one STS header field in an HTTP
    //     response message over secure transport, then the UA MUST process
    //     only the first such header field.
    return header == nsHttp::Strict_Transport_Security;
}

inline bool
nsHttpHeaderArray::TrackEmptyHeader(nsHttpAtom header)
{
    return header == nsHttp::Content_Length ||
           header == nsHttp::Location ||
           header == nsHttp::Access_Control_Allow_Origin;
}

inline MOZ_MUST_USE nsresult
nsHttpHeaderArray::MergeHeader(nsHttpAtom header,
                               nsEntry *entry,
                               const nsACString &value,
                               nsHttpHeaderArray::HeaderVariety variety)
{
    if (value.IsEmpty())
        return NS_OK;   // merge of empty header = no-op

    nsCString newValue = entry->value;
    if (!newValue.IsEmpty()) {
        // Append the new value to the existing value
        if (header == nsHttp::Set_Cookie ||
            header == nsHttp::WWW_Authenticate ||
            header == nsHttp::Proxy_Authenticate)
        {
            // Special case these headers and use a newline delimiter to
            // delimit the values from one another as commas may appear
            // in the values of these headers contrary to what the spec says.
            newValue.Append('\n');
        } else {
            // Delimit each value from the others using a comma (per HTTP spec)
            newValue.AppendLiteral(", ");
        }
    }

    newValue.Append(value);
    if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
        MOZ_ASSERT(variety == eVarietyResponse);
        entry->variety = eVarietyResponseNetOriginal;
        // Copy entry->headerNameOriginal because in SetHeader_internal we are going
        // to a new one and a realocation can happen.
        nsCString headerNameOriginal = entry->headerNameOriginal;
        nsresult rv = SetHeader_internal(header, headerNameOriginal,
                                         newValue, eVarietyResponse);
        if (NS_FAILED(rv)) {
            return rv;
        }
    } else {
        entry->value = newValue;
        entry->variety = variety;
    }
    return NS_OK;
}

inline bool
nsHttpHeaderArray::IsSuspectDuplicateHeader(nsHttpAtom header)
{
    bool retval =  header == nsHttp::Content_Length         ||
                     header == nsHttp::Content_Disposition    ||
                     header == nsHttp::Location;

    MOZ_ASSERT(!retval || IsSingletonHeader(header),
               "Only non-mergeable headers should be in this list\n");

    return retval;
}

} // namespace net
} // namespace mozilla

#endif
