/*
 * Copyright (C) 1996-2021 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.
 */

#include "squid.h"
#include "Debug.h"
#include "http/one/Tokenizer.h"

bool
Http::One::Tokenizer::quotedString(SBuf &returnedToken, const bool http1p0)
{
    checkpoint();

    if (!skip('"'))
        return false;

    return qdText(returnedToken, http1p0);
}

bool
Http::One::Tokenizer::quotedStringOrToken(SBuf &returnedToken, const bool http1p0)
{
    checkpoint();

    if (!skip('"'))
        return prefix(returnedToken, CharacterSet::TCHAR);

    return qdText(returnedToken, http1p0);
}

bool
Http::One::Tokenizer::qdText(SBuf &returnedToken, const bool http1p0)
{
    // the initial DQUOTE has been skipped by the caller

    /*
     * RFC 1945 - defines qdtext:
     *   inclusive of LWS (which includes CR and LF)
     *   exclusive of 0x80-0xFF
     *   includes 0x5C ('\') as just a regular character
     */
    static const CharacterSet qdtext1p0 = CharacterSet("qdtext (HTTP/1.0)", 0x23, 0x7E) +
                                          CharacterSet("", "!") +
                                          CharacterSet::CR + CharacterSet::LF + CharacterSet::HTAB + CharacterSet::SP;
    /*
     * RFC 7230 - defines qdtext:
     *   exclusive of CR and LF
     *   inclusive of 0x80-0xFF
     *   includes 0x5C ('\') but only when part of quoted-pair
     */
    static const CharacterSet qdtext1p1 = CharacterSet("qdtext (HTTP/1.1)", 0x23, 0x5B) +
                                          CharacterSet("", "!") +
                                          CharacterSet("", 0x5D, 0x7E) +
                                          CharacterSet::HTAB + CharacterSet::SP +
                                          CharacterSet::OBSTEXT;

    // best we can do is a conditional reference since http1p0 value may change per-client
    const CharacterSet &tokenChars = (http1p0 ? qdtext1p0 : qdtext1p1);

    for (;;) {
        SBuf::size_type prefixLen = buf().findFirstNotOf(tokenChars);
        returnedToken.append(consume(prefixLen));

        // HTTP/1.1 allows quoted-pair, HTTP/1.0 does not
        if (!http1p0 && skip('\\')) {
            /* RFC 7230 section 3.2.6
             *
             * The backslash octet ("\") can be used as a single-octet quoting
             * mechanism within quoted-string and comment constructs.  Recipients
             * that process the value of a quoted-string MUST handle a quoted-pair
             * as if it were replaced by the octet following the backslash.
             *
             *   quoted-pair    = "\" ( HTAB / SP / VCHAR / obs-text )
             */
            static const CharacterSet qPairChars = CharacterSet::HTAB + CharacterSet::SP + CharacterSet::VCHAR + CharacterSet::OBSTEXT;
            SBuf escaped;
            if (!prefix(escaped, qPairChars, 1)) {
                returnedToken.clear();
                restoreLastCheckpoint();
                return false;
            }
            returnedToken.append(escaped);
            continue;

        } else if (skip('"')) {
            break; // done

        } else if (atEnd()) {
            // need more data
            returnedToken.clear();
            restoreLastCheckpoint();
            return false;
        }

        // else, we have an error
        debugs(24, 8, "invalid bytes for set " << tokenChars.name);
        returnedToken.clear();
        restoreLastCheckpoint();
        return false;
    }

    // found the whole string
    return true;
}

