<commit_before>import collections
import email.utils
import re
import time

from netlib import multidict

"""
A flexible module for cookie parsing and manipulation.

This module differs from usual standards-compliant cookie modules in a number
of ways. We try to be as permissive as possible, and to retain even mal-formed
information. Duplicate cookies are preserved in parsing, and can be set in
formatting. We do attempt to escape and quote values where needed, but will not
reject data that violate the specs.

Parsing accepts the formats in RFC6265 and partially RFC2109 and RFC2965. We
also parse the comma-separated variant of Set-Cookie that allows multiple
cookies to be set in a single header. Serialization follows RFC6265.

    http://tools.ietf.org/html/rfc6265
    http://tools.ietf.org/html/rfc2109
    http://tools.ietf.org/html/rfc2965
"""

_cookie_params = set((
    'expires', 'path', 'comment', 'max-age',
    'secure', 'httponly', 'version',
))

ESCAPE = re.compile(r"([\"\\])")


class CookieAttrs(multidict.ImmutableMultiDict):
    @staticmethod
    def _kconv(key):
        return key.lower()

    @staticmethod
    def _reduce_values(values):
        # See the StickyCookieTest for a weird cookie that only makes sense
        # if we take the last part.
        return values[-1]

SetCookie = collections.namedtuple("SetCookie", ["value", "attrs"])


def _read_until(s, start, term):
    """
        Read until one of the characters in term is reached.
    """
    if start == len(s):
        return "", start + 1
    for i in range(start, len(s)):
        if s[i] in term:
            return s[start:i], i
    return s[start:i + 1], i + 1


def _read_quoted_string(s, start):
    """
        start: offset to the first quote of the string to be read

        A sort of loose super-set of the various quoted string specifications.

        RFC6265 disallows backslashes or double quotes within quoted strings.
        Prior RFCs use backslashes to escape. This leaves us free to apply
        backslash escaping by default and be compatible with everything.
    """
    escaping = False
    ret = []
    # Skip the first quote
    i = start  # initialize in case the loop doesn't run.
    for i in range(start + 1, len(s)):
        if escaping:
            ret.append(s[i])
            escaping = False
        elif s[i] == '"':
            break
        elif s[i] == "\\":
            escaping = True
        else:
            ret.append(s[i])
    return "".join(ret), i + 1


def _read_key(s, start, delims=";="):
    """
        Read a key - the LHS of a token/value pair in a cookie.
    """
    return _read_until(s, start, delims)


def _read_value(s, start, delims):
    """
        Reads a value - the RHS of a token/value pair in a cookie.
    """
    if start >= len(s):
        return "", start
    elif s[start] == '"':
        return _read_quoted_string(s, start)
    else:
        return _read_until(s, start, delims)


def _read_cookie_pairs(s, off=0):
    """
        Read pairs of lhs=rhs values from Cookie headers.

        off: start offset
    """
    pairs = []

    while True:
        lhs, off = _read_key(s, off)
        lhs = lhs.lstrip()

        if lhs:
            rhs = None
            if off < len(s) and s[off] == "=":
                rhs, off = _read_value(s, off + 1, ";")

            pairs.append([lhs, rhs])

        off += 1

        if not off < len(s):
            break

    return pairs, off


def _read_set_cookie_pairs(s, off=0):
    """
        Read pairs of lhs=rhs values from SetCookie headers while handling multiple cookies.

        off: start offset
        specials: attributes that are treated specially
    """
    cookies = []
    pairs = []

    while True:
        lhs, off = _read_key(s, off, ";=,")
        lhs = lhs.lstrip()

        if lhs:
            rhs = None
            if off < len(s) and s[off] == "=":
                rhs, off = _read_value(s, off + 1, ";,")

                # Special handliing of attributes
                if lhs.lower() == "expires":
                    # 'expires' values can contain commas in them so they need to
                    # be handled separately.

                    # '3' is just a heuristic we use to determine whether we've
                    # only read a part of the datetime and should read more.
                    if len(rhs) <= 3:
                        trail, off = _read_value(s, off + 1, ";,")
                        rhs = rhs + "," + trail

            pairs.append([lhs, rhs])

            # comma marks the beginning of a new cookie
            if off < len(s) and s[off] == ",":
                cookies.append(pairs)
                pairs = []

        off += 1

        if not off < len(s):
            break

    if pairs or not cookies:
        cookies.append(pairs)

    return cookies, off


def _has_special(s):
    for i in s:
        if i in '",;\\':<commit_msg>Clarify that 'expires' values MUST have a comma now<commit_after>import collections
import email.utils
import re
import time

from netlib import multidict

"""
A flexible module for cookie parsing and manipulation.

This module differs from usual standards-compliant cookie modules in a number
of ways. We try to be as permissive as possible, and to retain even mal-formed
information. Duplicate cookies are preserved in parsing, and can be set in
formatting. We do attempt to escape and quote values where needed, but will not
reject data that violate the specs.

Parsing accepts the formats in RFC6265 and partially RFC2109 and RFC2965. We
also parse the comma-separated variant of Set-Cookie that allows multiple
cookies to be set in a single header. Serialization follows RFC6265.

    http://tools.ietf.org/html/rfc6265
    http://tools.ietf.org/html/rfc2109
    http://tools.ietf.org/html/rfc2965
"""

_cookie_params = set((
    'expires', 'path', 'comment', 'max-age',
    'secure', 'httponly', 'version',
))

ESCAPE = re.compile(r"([\"\\])")


class CookieAttrs(multidict.ImmutableMultiDict):
    @staticmethod
    def _kconv(key):
        return key.lower()

    @staticmethod
    def _reduce_values(values):
        # See the StickyCookieTest for a weird cookie that only makes sense
        # if we take the last part.
        return values[-1]

SetCookie = collections.namedtuple("SetCookie", ["value", "attrs"])


def _read_until(s, start, term):
    """
        Read until one of the characters in term is reached.
    """
    if start == len(s):
        return "", start + 1
    for i in range(start, len(s)):
        if s[i] in term:
            return s[start:i], i
    return s[start:i + 1], i + 1


def _read_quoted_string(s, start):
    """
        start: offset to the first quote of the string to be read

        A sort of loose super-set of the various quoted string specifications.

        RFC6265 disallows backslashes or double quotes within quoted strings.
        Prior RFCs use backslashes to escape. This leaves us free to apply
        backslash escaping by default and be compatible with everything.
    """
    escaping = False
    ret = []
    # Skip the first quote
    i = start  # initialize in case the loop doesn't run.
    for i in range(start + 1, len(s)):
        if escaping:
            ret.append(s[i])
            escaping = False
        elif s[i] == '"':
            break
        elif s[i] == "\\":
            escaping = True
        else:
            ret.append(s[i])
    return "".join(ret), i + 1


def _read_key(s, start, delims=";="):
    """
        Read a key - the LHS of a token/value pair in a cookie.
    """
    return _read_until(s, start, delims)


def _read_value(s, start, delims):
    """
        Reads a value - the RHS of a token/value pair in a cookie.
    """
    if start >= len(s):
        return "", start
    elif s[start] == '"':
        return _read_quoted_string(s, start)
    else:
        return _read_until(s, start, delims)


def _read_cookie_pairs(s, off=0):
    """
        Read pairs of lhs=rhs values from Cookie headers.

        off: start offset
    """
    pairs = []

    while True:
        lhs, off = _read_key(s, off)
        lhs = lhs.lstrip()

        if lhs:
            rhs = None
            if off < len(s) and s[off] == "=":
                rhs, off = _read_value(s, off + 1, ";")

            pairs.append([lhs, rhs])

        off += 1

        if not off < len(s):
            break

    return pairs, off


def _read_set_cookie_pairs(s, off=0):
    """
        Read pairs of lhs=rhs values from SetCookie headers while handling multiple cookies.

        off: start offset
        specials: attributes that are treated specially
    """
    cookies = []
    pairs = []

    while True:
        lhs, off = _read_key(s, off, ";=,")
        lhs = lhs.lstrip()

        if lhs:
            rhs = None
            if off < len(s) and s[off] == "=":
                rhs, off = _read_value(s, off + 1, ";,")

                # Special handliing of attributes
                if lhs.lower() == "expires":
                    # 'expires' values can contain commas in them so they need to
                    # be handled separately.

                    # We actually bank on the fact that the expires value WILL
                    # contain a comma. Things will fail, if they don't.

                    # '3' is just a heuristic we use to determine whether we've
                    # only read a part of the expires value and we should read more.
                    if len(rhs) <= 3:
                        trail, off = _read_value(s, off + 1, ";,")
                        rhs = rhs + "," + trail

            pairs.append([lhs, rhs])

            # comma marks the beginning of a new cookie
            if off < len(s) and s[off] == ",":
                cookies.append(pairs)
                pairs = []

        off += 1

        if not off < len(s):
            break

    if pairs or not cookies:
        cookies.append(pairs)

    return cookies, off


def _has_special(s):
    for i in s:
        if i in '",;\\':
{'fraction_non_alphanumeric': 0.10153294843718894, 'fraction_numerical': 0.009356957993231137, 'mean_word_length': 1.9602239245727755, 'pattern_counts': {'":': 10, '<': 15, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5487337', 'n_tokens_mistral': 3110, 'n_tokens_neox': 2931, 'n_words': 1100}