def _decode_string(s, charset): try: return s.decode(charset) except LookupError: raise RequestError('unknown charset %r' % charset) except UnicodeDecodeError: raise RequestError('invalid %r encoded string' % charset)
def _process_multipart_body(self, mimeinput, charset): headers = io.BytesIO() lines = mimeinput.readpart() for line in lines: headers.write(line) if line == b'\r\n': break headers.seek(0) headers = email.message_from_binary_file(headers) ctype, ctype_params = parse_header(headers.get('content-type', '')) if ctype and 'charset' in ctype_params: charset = ctype_params['charset'] cdisp, cdisp_params = parse_header( headers.get('content-disposition', '')) if not cdisp: raise RequestError('expected Content-Disposition header') name = cdisp_params.get('name') filename = cdisp_params.get('filename') if not (cdisp == 'form-data' and name): raise RequestError('expected Content-Disposition: form-data' 'with a "name" parameter: got %r' % headers.get('content-disposition', '')) # FIXME: should really to handle Content-Transfer-Encoding and other # MIME complexity here. See RFC2048 for the full horror story. if filename: # it might be large file upload so use a temporary file upload = Upload(filename, ctype, charset) upload.receive(lines) _add_field_value(self.form, name, upload) else: value = _decode_string(b''.join(lines), charset or self.charset) _add_field_value(self.form, name, value)
def parse_query(qs, charset): """(qs: string) -> {key:string, string|[string]} Parse a query given as a string argument and return a dictionary. """ fields = {} for chunk in qs.split('&'): if not chunk: continue if '=' not in chunk: name = chunk value = '' else: name, value = chunk.split('=', 1) try: name = urllib.parse.unquote_plus(name, encoding=charset, errors='strict') value = urllib.parse.unquote_plus(value, encoding=charset, errors='strict') except LookupError: raise RequestError('unknown charset %r' % charset) except UnicodeDecodeError: raise RequestError('invalid %r encoded string' % charset) _add_field_value(fields, name, value) return fields
def _decode_string(s, charset): if charset == 'iso-8859-1' == quixote.DEFAULT_CHARSET: # To avoid breaking applications that are not Unicode-safe, return # a str instance in this case. return s try: return s.decode(charset) except LookupError: raise RequestError('unknown charset %r' % charset) except UnicodeDecodeError: raise RequestError('invalid %r encoded string' % charset)
def _process_multipart(self, length, params): boundary = params.get('boundary') if not boundary: raise RequestError('multipart/form-data missing boundary') charset = params.get('charset') mimeinput = MIMEInput(self.stdin, boundary, length) try: for line in mimeinput.readpart(): pass # discard lines up to first boundary while mimeinput.moreparts(): self._process_multipart_body(mimeinput, charset) except EOFError: raise RequestError('unexpected end of multipart/form-data')
def parse_content_type(self): full_ctype = self.get_header('Content-Type') if full_ctype is None: raise RequestError("no Content-Type header") (ctype, ctype_params) = parse_header(full_ctype) boundary = ctype_params.get('boundary') if not (ctype == "multipart/form-data" and boundary): raise RequestError("expected Content-Type: multipart/form-data " "with a 'boundary' parameter: got %r" % full_ctype) return (ctype, boundary)
def parse_content_disposition(full_cdisp): (cdisp, cdisp_params) = parse_header(full_cdisp) name = cdisp_params.get('name') if not (cdisp == 'form-data' and name): raise RequestError('expected Content-Disposition: form-data ' 'with a "name" parameter: got %r' % full_cdisp) return (name, cdisp_params.get('filename'))
def _process_urlencoded(self, length, params): query = self.stdin.read(length) if len(query) != length: raise RequestError('unexpected end of request body') # Use the declared charset if it's provided (most browser's don't # provide it to avoid breaking old HTTP servers). charset = params.get('charset', self.charset) self.form.update(parse_query(query, charset))
def parse_content_disposition(self, full_cdisp): (cdisp, cdisp_params) = parse_header(full_cdisp) name = cdisp_params.get("name") if not (cdisp == "form-data" and name): raise RequestError("expected Content-Disposition: form-data " "with a 'name' parameter: got %r" % full_cdisp) return (name, cdisp_params.get("filename"))
def _q_index(self, request): if self._type == 'ticket': if len(self.parts) >= 2: proj_name = '/'.join(self.parts[:-1]) target_id = self.parts[-1] if proj_name and target_id.isdigit(): self._proj_name = proj_name self._target_id = target_id self._user = request.user self._cancel = request.get_form_var("cancel") return self.mute() return RequestError("Invalie args!")
def process_inputs(self): length = self.environ.get('CONTENT_LENGTH') or "0" try: length = int(length) except ValueError: raise RequestError('invalid content-length header') if self._stdin is None: # We must consume entire request body as some clients and # middleware expect that. We cannot rely on the application to # read it completely (e.g. if there is some PublishError raised). if length < 20000: fp = io.BytesIO() else: fp = tempfile.TemporaryFile("w+b") remaining = length while remaining > 0: s = self.stdin.read(min(remaining, 10000)) if not s: raise RequestError('unexpected end of request body') fp.write(s) remaining -= len(s) fp.seek(0) self._stdin = self.stdin self.stdin = fp else: # In the case of a database conflict, process_inputs() might # be called more than once. In this case, there is no need # to buffer stdin but reset the form data and input file. self.stdin.seek(0) self.form.clear() query = self.get_query() if query: self.form.update(parse_query(query, self.charset)) ctype = self.environ.get("CONTENT_TYPE") if ctype: ctype, ctype_params = parse_header(ctype) if ctype == 'application/x-www-form-urlencoded': self._process_urlencoded(length, ctype_params) elif ctype == 'multipart/form-data': self._process_multipart(length, ctype_params)
def check_length_read(self, file): # Parse Content-Length header. # XXX if we want to worry about disk free space, this should # be done *before* parsing the body! clen = self.get_header("Content-Length") if clen is not None: clen = int(clen) total_bytes = file.get_bytesread() if total_bytes != clen: raise RequestError( "upload request length mismatch: expected %d bytes, got %d" % (clen, total_bytes))
def process_inputs(self): query = self.get_query() if query: self.form.update(parse_query(query, self.charset)) length = self.environ.get('CONTENT_LENGTH') or "0" try: length = int(length) except ValueError: raise RequestError('invalid content-length header') read_body = length > 0 ctype = self.environ.get("CONTENT_TYPE") if ctype: ctype, ctype_params = parse_header(ctype) if ctype == 'application/x-www-form-urlencoded': self._process_urlencoded(length, ctype_params) read_body = False elif ctype == 'multipart/form-data': self._process_multipart(length, ctype_params) read_body = False if read_body: # We must consume entire request body as some clients and # middleware expect that. We cannot rely on the application to # read it completely (e.g. if there is some PublishError raised). if length < 20000: fp = StringIO() else: fp = tempfile.TemporaryFile("w+b") remaining = length while remaining > 0: s = self.stdin.read(min(remaining, 10000)) if not s: raise RequestError('unexpected end of request body') fp.write(s) remaining -= len(s) fp.seek(0) self._stdin = self.stdin self.stdin = fp
def process_inputs(self): query = self.get_query() if query: self.form.update(parse_query(query, self.DEFAULT_CHARSET)) length = self.environ.get('CONTENT_LENGTH') or "0" try: length = int(length) except ValueError: raise RequestError('invalid content-length header') ctype = self.environ.get("CONTENT_TYPE") if ctype: ctype, ctype_params = parse_header(ctype) if ctype == 'application/x-www-form-urlencoded': self._process_urlencoded(length, ctype_params) elif ctype == 'multipart/form-data': self._process_multipart(length, ctype_params)
def parse_body(self, file, boundary): total_bytes = 0 # total bytes read from 'file' done = 0 while not done: headers = Message(file) cdisp = headers.get('content-disposition') if not cdisp: raise RequestError("expected Content-Disposition header " "in body sub-part") (name, filename) = self.parse_content_disposition(cdisp) if filename: content_type = headers.get('content-type') done = self.handle_upload(name, filename, file, boundary, content_type) else: done = self.handle_regular_var(name, file, boundary)
def __init__(self, stdin, environ, seekable=False): self.stdin = stdin self._stdin = None # set after stdin is buffered to temp file self.body_is_seekable = seekable self.environ = environ self.form = {} self.session = None self.charset = self.DEFAULT_CHARSET or quixote.DEFAULT_CHARSET self.response = HTTPResponse() length = environ.get('CONTENT_LENGTH') or 0 try: self._content_length = int(length) except ValueError: raise RequestError('invalid content-length header') # The strange treatment of SERVER_PORT_SECURE is because IIS # sets this environment variable to "0" for non-SSL requests # (most web servers -- well, Apache at least -- simply don't set # it in that case). if (environ.get('HTTPS', 'off').lower() in ('on', 'yes', '1') or environ.get('SERVER_PORT_SECURE', '0') != '0'): self.scheme = "https" else: self.scheme = "http" k = self.environ.get('HTTP_COOKIE', '') if k: self.cookies = parse_cookies(k) else: self.cookies = {} # IIS breaks PATH_INFO because it leaves in the path to # the script, so SCRIPT_NAME is "/cgi-bin/q.py" and PATH_INFO # is "/cgi-bin/q.py/foo/bar". The following code fixes # PATH_INFO to the expected value "/foo/bar". web_server = environ.get('SERVER_SOFTWARE', 'unknown') if web_server.find('Microsoft-IIS') != -1: script = environ['SCRIPT_NAME'] path = environ['PATH_INFO'] if path.startswith(script): path = path[len(script):] self.environ['PATH_INFO'] = path
def make_body_seekable(self): """Ensure that 'stdin' is a seekable file object.""" if self.body_is_seekable: self.stdin.seek(0) return if self._content_length < 20000: fp = io.BytesIO() else: fp = tempfile.TemporaryFile("w+b") remaining = self._content_length while remaining > 0: s = self.stdin.read(min(remaining, 10000)) if not s: raise RequestError('unexpected end of request body') fp.write(s) remaining -= len(s) fp.seek(0) self._stdin = self.stdin self.stdin = fp self.body_is_seekable = True
def _process_urlencoded(self, length, params): query = self.stdin.read(length) if len(query) != length: raise RequestError('unexpected end of request body') charset = params.get('charset', self.DEFAULT_CHARSET) self.form.update(parse_query(query, charset))
def _q_lookup(request, type_): user = request.user if (type_ in ALLOWED_MUTE_TYPE) and user: return MuteUI(type_) else: return RequestError('Can not parse your request')