/*
 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
 *
 * Squid software is distributed under GPLv2+ license and includes
 * contributions from numerous individuals and organizations.
 * Please see the COPYING and CONTRIBUTORS files for details.
 */

/* DEBUG: section 73    HTTP Request */

#include "squid.h"
#include "http/RequestMethod.h"
#include "SquidConfig.h"
#include "wordlist.h"

static Http::MethodType &
operator++ (Http::MethodType &aMethod)
{
    int tmp = (int)aMethod;
    aMethod = (Http::MethodType)(++tmp);
    return aMethod;
}

/**
 * Construct a HttpRequestMethod from a C-string such as "GET"
 * Assumes the string is either nul-terminated or contains whitespace
 *
 * \deprecated use SBuf constructor instead
 */
void
HttpRequestMethod::HttpRequestMethodXXX(char const *begin)
{
    // XXX: performance regression due to this method no longer being a constructor
    // ensure the members are empty/default values before any of the early-return
    // optimizations can be used.
    theMethod = Http::METHOD_NONE;
    theImage.clear();

    if (begin == nullptr)
        return;

    char const *end = begin + strcspn(begin, w_space);

    if (end == begin)
        return;

    // TODO: Optimize this linear search.
    for (++theMethod; theMethod < Http::METHOD_ENUM_END; ++theMethod) {
        // RFC 2616 section 5.1.1 - Method names are case-sensitive
        // NP: this is not a HTTP_VIOLATIONS case since there is no MUST/SHOULD involved.
        if (0 == image().caseCmp(begin, end-begin)) {

            // relaxed parser allows mixed-case and corrects them on output
            if (Config.onoff.relaxed_header_parser)
                return;

            if (0 == image().cmp(begin, end-begin))
                return;
        }
    }

    // if method not found and method string is not null then it is other method
    theMethod = Http::METHOD_OTHER;
    theImage.assign(begin, end-begin);
}

/**
 * Construct a HttpRequestMethod from an SBuf string such as "GET"
 * or from a range of chars such as "FOO" from buffer "GETFOOBARBAZ"
 *
 * Assumes the s parameter contains only the characters representing the method name
 */
HttpRequestMethod::HttpRequestMethod(const SBuf &s) : theMethod(Http::METHOD_NONE)
{
    if (s.isEmpty())
        return;

    // TODO: Optimize this linear search.
    for (++theMethod; theMethod < Http::METHOD_ENUM_END; ++theMethod) {
        // RFC 2616 section 5.1.1 - Method names are case-sensitive
        // NP: this is not a HTTP_VIOLATIONS case since there is no MUST/SHOULD involved.
        if (0 == image().caseCmp(s)) {

            // relaxed parser allows mixed-case and corrects them on output
            if (Config.onoff.relaxed_header_parser)
                return;

            if (0 == image().cmp(s))
                return;
        }
    }

    // if method not found and method string is not null then it is other method
    theMethod = Http::METHOD_OTHER;
    theImage = s;
}

const SBuf &
HttpRequestMethod::image() const
{
    static const SBuf methodOther("METHOD_OTHER");
    if (Http::METHOD_OTHER != theMethod) {
        return Http::MethodType_sb[theMethod];
    } else {
        if (!theImage.isEmpty()) {
            return theImage;
        } else {
            return methodOther;
        }
    }
}

bool
HttpRequestMethod::isHttpSafe() const
{
    // Only a few methods are defined as safe. All others are "unsafe"

    // NOTE:
    // All known RFCs which register methods are listed in comments.
    // if there is one not listed which defines methods, it needs
    // checking and adding. If only to say it is known to define none.

    switch (theMethod) {
    // RFC 2068 - none

    // RFC 2616 section 9.1.1
    case Http::METHOD_GET:
    case Http::METHOD_HEAD:
    case Http::METHOD_OPTIONS:

    // RFC 3253 section 3.6
    case Http::METHOD_REPORT:

    // RFC 3648 - none
    // RFC 3744 - none
    // RFC 4437 - none
    // RFC 4791 - none

    // RFC 4918 section 9.1
    case Http::METHOD_PROPFIND:

    // RFC 5323 section 2
    case Http::METHOD_SEARCH:

    // RFC 5789 - none
    // RFC 5842 - none

    // RFC 9113 section 3.4
    case Http::METHOD_PRI:

        return true;

    default:
        return false;
    }
}

bool
HttpRequestMethod::isIdempotent() const
{
    // Only a few methods are defined as idempotent.

    // NOTE:
    // All known RFCs which register methods are listed in comments.
    // if there is one not listed which defines methods, it needs
    // checking and adding. If only to say it is known to define none.

    switch (theMethod) {
    // RFC 2068 - TODO check LINK/UNLINK definition

    // RFC 2616 section 9.1.2
    case Http::METHOD_GET:
    case Http::METHOD_HEAD:
    case Http::METHOD_PUT:
    case Http::METHOD_DELETE:
    case Http::METHOD_OPTIONS:
    case Http::METHOD_TRACE:

    // RFC 3253 - TODO check
    // RFC 3648 - TODO check
    // RFC 3744 - TODO check
    // RFC 4437 - TODO check
    // RFC 4791 - TODO check

    // RFC 4918 section 9
    case Http::METHOD_PROPFIND:
    case Http::METHOD_PROPPATCH:
    case Http::METHOD_MKCOL:
    case Http::METHOD_COPY:
    case Http::METHOD_MOVE:
    case Http::METHOD_UNLOCK:

    // RFC 5323 - TODO check
    // RFC 5789 - TODO check
    // RFC 5842 - TODO check

    // RFC 7540 section 11.6
    case Http::METHOD_PRI:

        return true;

    default:
        return false;
    }
}

bool
HttpRequestMethod::respMaybeCacheable() const
{
    // Only a few methods are defined as cacheable.
    // All other methods from the below RFC are "MUST NOT cache"
    switch (theMethod) {
    // RFC 2616 section 9
    case Http::METHOD_GET:
    case Http::METHOD_HEAD:
        return true;
#if WHEN_POST_CACHE_SUPPORTED
    case Http::METHOD_POST: // Special case.
        // RFC 2616 specifies POST as possibly cacheable
        // However, Squid does not implement the required checks yet
        return true;
#endif

        // RFC 4918 section 9
#if WHEN_PROPFIND_CACHE_SUPPORTED
    case Http::METHOD_PROPFIND: // Special case.
        // RFC 4918 specifies PROPFIND as possibly cacheable
        // However, Squid does not implement the required checks yet
        return true;
#endif

        // RFC 5323 section 2 - defines no cacheable methods

        // RFC 3253
#if WHEN_CC_NOCACHE_DOES_REVALIDATES_IS_CONFIRMED
    case Http::METHOD_CHECKOUT:
    case Http::METHOD_CHECKIN:
    case Http::METHOD_UNCHECKOUT:
    case Http::METHOD_MKWORKSPACE:
    case Http::METHOD_VERSION_CONTROL:
    case Http::METHOD_UPDATE:
    case Http::METHOD_LABEL:
    case Http::METHOD_MERGE:
    case Http::METHOD_BASELINE_CONTROL:
    case Http::METHOD_MKACTIVITY:
        // RFC 3253 defines these methods using "MUST include Cache-Control: no-cache".
        //
        // XXX: follow RFC 2616 definition of "no-cache" meaning "MAY cache, always revalidate"
        // XXX: or treat as unregistered/undefined methods ??
        // However, Squid may not implement the required revalidation checks yet
        return ??;
#endif

    // Special Squid method tokens are not cacheable.
    // RFC 2616 defines all unregistered or unspecified methods as non-cacheable
    // until such time as an RFC defines them cacheable.
    default:
        return false;
    }
}

bool
HttpRequestMethod::shouldInvalidate() const
{
    switch (theMethod) {
    /* RFC 2616 section 13.10 - "MUST invalidate" */
    case Http::METHOD_POST:
    case Http::METHOD_PUT:
    case Http::METHOD_DELETE:
        return true;

    /* Squid extension to force invalidation */
    case Http::METHOD_PURGE:
        return true;

    /*
     * RFC 2616 sayeth, in section 13.10, final paragraph:
     * A cache that passes through requests for methods it does not
     * understand SHOULD invalidate any entities referred to by the
     * Request-URI.
     */
    case Http::METHOD_OTHER:
        return true;

    default:
        // Methods which are known but not required to invalidate.
        return false;
    }
}

bool
HttpRequestMethod::purgesOthers() const
{
    if (shouldInvalidate())
        return true;

    switch (theMethod) {
    /* common sense suggests purging is not required? */
    case Http::METHOD_GET:     // XXX: but we do purge HEAD on successful GET
    case Http::METHOD_HEAD:
    case Http::METHOD_NONE:
    case Http::METHOD_CONNECT:
    case Http::METHOD_TRACE:
    case Http::METHOD_OPTIONS:
    case Http::METHOD_PROPFIND:
    case Http::METHOD_COPY:
    case Http::METHOD_LOCK:
    case Http::METHOD_UNLOCK:
    case Http::METHOD_SEARCH:
        return false;

    default:
        return true;
    }
}

