This file is indexed.

/usr/share/pyshared/pesto/httputils.py is in python-pesto 25-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# Copyright (c) 2009-2011 Oliver Cope. All rights reserved.
# See LICENSE.txt for terms of redistribution and use.

"""
pesto.httputils
---------------

Utility functions to handle HTTP data
"""
try:
    from email.message import Message
    from email.parser import Parser
except ImportError:
    from email.Message import Message
    from email.Parser import Parser
import re
from urllib import unquote_plus
from shutil import copyfileobj

from pesto.utils import ExpandableOutput, SizeLimitedInput, PutbackInput, DelimitedInput

KB = 1024
MB = 1024 * KB

class RequestParseError(Exception):
    """
    Error encountered while parsing the HTTP request
    """
    def response(self):
        """
        Return a ``pesto.response.Response`` object to represent this error condition
        """
        from pesto.response import Response
        return Response.bad_request().add_header('X-Pesto-Exception', repr(self))

class TooBig(RequestParseError):
    """
    Request body is too big
    """
    def response(self):
        """
        Return a ``pesto.response.Response`` object to represent this error condition
        """
        from pesto.response import Response
        return Response.request_entity_too_large()

class MissingContentLength(RequestParseError):
    """
    No ``Content-Length`` header given
    """
    def response(self):
        """
        Return a ``pesto.response.Response`` object to represent this error condition
        """
        from pesto.response import Response
        return Response.length_required()

def dequote(s):
    """
    Return ``s`` with surrounding quotes removed. Example usage::

        >>> dequote('foo')
        'foo'
        >>> dequote('"foo"')
        'foo'
    """
    if len(s) > 1 and s[0] == '"' == s[-1]:
        return s[1:-1]
    return s

def parse_header(header):
    """
    Given a header, return a tuple of ``(value, [(parameter_name, parameter_value)])``.

    Example usage::

        >>> parse_header("text/html; charset=UTF-8")
        ('text/html', {'charset': 'UTF-8'})
        >>> parse_header("multipart/form-data; boundary=---------------------------7d91772e200be")
        ('multipart/form-data', {'boundary': '---------------------------7d91772e200be'})
    """
    items = header.split(';')
    pairs = [
        (name, dequote(value))
             for name, value in (
                 item.lstrip().split('=', 1)
                    for item in items[1:]
            )
    ]
    return (items[0], dict(pairs))

def parse_querystring(
    data,
    charset=None,
    strict=False,
    keep_blank_values=True,
    pairsplitter=re.compile('[;&]').split
):
    """
    Return ``(key, value)`` pairs from the given querystring::

        >>> list(parse_querystring('green%20eggs=ham;me=sam+i+am'))
        [(u'green eggs', u'ham'), (u'me', u'sam i am')]

    :param charset: Character encoding used to decode values. If not specified,
        ``pesto.DEFAULT_CHARSET`` will be used.

    :param keep_blank_values: if True, keys without associated values will be returned
        as empty strings. if False, no key, value pair will be returned.

    :param strict: if ``True``, a ``ValueError`` will be raised on parsing errors.
    """

    if charset is None:
        charset = DEFAULT_CHARSET

    for item in pairsplitter(data):
        if not item:
            continue
        try:
            key, value = item.split('=', 1)
        except ValueError:
            if strict:
                raise RequestParseError("bad query field: %r" % (item,))
            if not keep_blank_values:
                continue
            key, value = item, ''

        try:
            yield unquote_plus(key).decode(charset), unquote_plus(value).decode(charset)
        except UnicodeDecodeError:
            raise RequestParseError("Invalid character data: can't decode using %r" % (charset,))

def parse_post(environ, fp, default_charset=None, max_size=16*KB, max_multipart_size=2*MB):
    """
    Parse the contents of an HTTP POST request, which may be either
    application/x-www-form-urlencoded or multipart/form-data encoded.

    Returned items are either tuples of (name, value) for simple string values
    or (name, FileUpload) for uploaded files.

    :param max_multipart_size: Maximum size of total data for a multipart form submission

    :param max_size: The maximum size of data allowed to be read into memory. 
        For a application/x-www-form-urlencoded submission, this is the maximum
        size of the entire data.
        For a multipart/form-data submission, this is the maximum size of any
        individual field (except file uploads).
    """
    content_type, content_type_params = parse_header(
        environ.get('CONTENT_TYPE', 'application/x-www-form-urlencoded')
    )

    if default_charset is None:
        default_charset = DEFAULT_CHARSET
    charset = content_type_params.get('charset', default_charset)

    try:
        content_length = int(environ['CONTENT_LENGTH'])
    except (TypeError, ValueError, KeyError):
        raise MissingContentLength()

    if content_type == 'application/x-www-form-urlencoded':

        if content_length > max_size:
            raise TooBig("Content Length exceeds permitted size")
        return parse_querystring(SizeLimitedInput(fp, content_length).read(), charset)

    else:
        if content_length > max_multipart_size:
            raise TooBig("Content Length exceeds permitted size")
        try:
            boundary = content_type_params['boundary']
        except KeyError:
            raise RequestParseError("No boundary given in multipart/form-data content-type")
        return parse_multipart(SizeLimitedInput(fp, content_length), boundary, charset, max_size)

class HTTPMessage(Message):
    """
    Represent HTTP request message headers
    """

CHUNK_SIZE = 8192

def parse_multipart(fp, boundary, default_charset, max_size):
    """
    Parse data encoded as ``multipart/form-data``. Generate tuples of::

        (<field-name>, <data>)

    Where ``data`` will be a string in the case of a regular input field, or a
    ``FileUpload`` instance if a file was uploaded.

    :param fp: input stream from which to read data
    :param boundary: multipart boundary string, as specified by the ``Content-Disposition`` header
    :param default_charset: character set to use for encoding, if not specified by a content-type header.
        In practice web browsers don't supply a content-type header so this
        needs to contain a sensible value.
    :param max_size: Maximum size in bytes for any non file upload part
    """

    boundary_size = len(boundary)
    if not boundary.startswith('--'):
        raise RequestParseError("Malformed boundary string: must start with '--' (rfc 2046)")
    if boundary_size > 72:
        raise RequestParseError("Malformed boundary string: must be no more than 70 characters, not counting the two leading hyphens (rfc 2046)")

    assert boundary_size + 2 < CHUNK_SIZE, "CHUNK_SIZE cannot be smaller than the boundary string"

    if fp.read(2) != '--':
        raise RequestParseError("Malformed POST data: expected two hypens")

    if fp.read(boundary_size) != boundary:
        raise RequestParseError("Malformed POST data: expected boundary")

    if fp.read(2) != '\r\n':
        raise RequestParseError("Malformed POST data: expected CRLF")

    fp = PutbackInput(fp)

    while True:
        headers, data = _read_multipart_field(fp, boundary)
        try:
            disposition_type, params = parse_header(headers['Content-Disposition'])
        except KeyError:
            raise RequestParseError("Missing Content-Disposition header")

        try:
            name = params['name']
        except KeyError:
            raise RequestParseError("Missing name parameter in Content-Disposition header")

        is_file_upload = 'Content-Type' in headers and 'filename' in params
        if is_file_upload:
            io = data._io
            io.seek(0)
            yield name, FileUpload(params['filename'], headers, io)

        else:
            charset = parse_header(headers.get('Content-Type', ''))[1].get('charset', default_charset)
            if data.tell() > max_size:
                raise TooBig("Data block exceeds maximum permitted size")
            try:
                data.seek(0)
                yield name, data.read().decode(charset)
            except UnicodeDecodeError:
                raise RequestParseError("Invalid character data: can't decode using %r" % (charset,))

        chunk = fp.read(2)
        if chunk == '--':
            if fp.peek(3) != '\r\n':
                raise RequestParseError("Expected terminating CRLF at end of stream")
            break

        if chunk != '\r\n':
            raise RequestParseError("Expected CRLF after boundary")

CONTENT_DISPOSITION_FORM_DATA = 'form-data'
CONTENT_DISPOSITION_FILE_UPLOAD = 'file-upload'

def _read_multipart_field(fp, boundary):
    """
    Read a single part from a multipart/form-data message and return a tuple of
    ``(headers, data)``. Stream ``fp`` must be positioned at the start of the
    header block for the field.

    Return a tuple of ('<headers>', '<data>')

    ``headers`` is an instance of ``email.message.Message``.

    ``data`` is an instance of ``ExpandableOutput``.

    Note that this currently cannot handle nested multipart sections.
    """
    data = ExpandableOutput()
    headers = Parser(_class=HTTPMessage).parse(
        DelimitedInput(fp, '\r\n\r\n'),
        headersonly=True
    )
    fp = DelimitedInput(fp, '\r\n--' + boundary)
    # XXX: handle base64 encoding etc
    for chunk in iter(lambda: fp.read(CHUNK_SIZE), ''):
        data.write(chunk)
    data.flush()

    # Fallen off the end of the input without having read a complete field?
    if not fp.delimiter_found:
        raise RequestParseError("Incomplete data (expected boundary)")

    return headers, data


class FileUpload(object):
    """
    Represent a file uploaded in an HTTP form submission
    """

    def __init__(self, filename, headers, fileob):

        self.filename = filename
        self.headers = headers
        self.file = fileob

        # UNC/Windows path
        if self.filename[:2] == '\\\\' or self.filename[1:3] == ':\\':
            self.filename = self.filename[self.filename.rfind('\\')+1:]

    def save(self, fileob):
        """
        Save the upload to the file object or path ``fileob``

        :param fileob: a file-like object open for writing, or the path to the file to be written
        """
        if isinstance(fileob, basestring):
            fileob = open(fileob, 'w')
            try:
                return self.save(fileob)
            finally:
                fileob.close()

        self.file.seek(0)
        copyfileobj(self.file, fileob)

# Imports at end to avoid circular dependencies
from pesto import DEFAULT_CHARSET