_cpreqbody.py (17816B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\carbon\common\lib\cherrypy\_cpreqbody.py 2 import re 3 import sys 4 import tempfile 5 from urllib import unquote_plus 6 import cherrypy 7 from cherrypy._cpcompat import basestring, ntob, ntou 8 from cherrypy.lib import httputil 9 10 def process_urlencoded(entity): 11 qs = entity.fp.read() 12 for charset in entity.attempt_charsets: 13 try: 14 params = {} 15 for aparam in qs.split(ntob('&')): 16 for pair in aparam.split(ntob(';')): 17 if not pair: 18 continue 19 atoms = pair.split(ntob('='), 1) 20 if len(atoms) == 1: 21 atoms.append(ntob('')) 22 key = unquote_plus(atoms[0]).decode(charset) 23 try: 24 value = unquote_plus(atoms[1]).decode(charset) 25 except Exception: 26 value = unquote_plus(atoms[1]) 27 28 if key in params: 29 if not isinstance(params[key], list): 30 params[key] = [params[key]] 31 params[key].append(value) 32 else: 33 params[key] = value 34 35 except UnicodeDecodeError: 36 pass 37 else: 38 entity.charset = charset 39 break 40 41 else: 42 raise cherrypy.HTTPError(400, 'The request entity could not be decoded. The following charsets were attempted: %s' % repr(entity.attempt_charsets)) 43 44 for key, value in params.items(): 45 if key in entity.params: 46 if not isinstance(entity.params[key], list): 47 entity.params[key] = [entity.params[key]] 48 entity.params[key].append(value) 49 else: 50 entity.params[key] = value 51 52 53 def process_multipart(entity): 54 ib = '' 55 if 'boundary' in entity.content_type.params: 56 ib = entity.content_type.params['boundary'].strip('"') 57 if not re.match('^[ -~]{0,200}[!-~]$', ib): 58 raise ValueError('Invalid boundary in multipart form: %r' % (ib,)) 59 ib = ('--' + ib).encode('ascii') 60 while True: 61 b = entity.readline() 62 if not b: 63 return 64 b = b.strip() 65 if b == ib: 66 break 67 68 while True: 69 part = entity.part_class.from_fp(entity.fp, ib) 70 entity.parts.append(part) 71 part.process() 72 if part.fp.done: 73 break 74 75 76 def process_multipart_form_data(entity): 77 process_multipart(entity) 78 kept_parts = [] 79 for part in entity.parts: 80 if part.name is None: 81 kept_parts.append(part) 82 else: 83 if part.filename is None: 84 value = part.fullvalue() 85 else: 86 value = part 87 if part.name in entity.params: 88 if not isinstance(entity.params[part.name], list): 89 entity.params[part.name] = [entity.params[part.name]] 90 entity.params[part.name].append(value) 91 else: 92 entity.params[part.name] = value 93 94 entity.parts = kept_parts 95 96 97 def _old_process_multipart(entity): 98 process_multipart(entity) 99 params = entity.params 100 for part in entity.parts: 101 if part.name is None: 102 key = ntou('parts') 103 else: 104 key = part.name 105 if part.filename is None: 106 value = part.fullvalue() 107 else: 108 value = part 109 if key in params: 110 if not isinstance(params[key], list): 111 params[key] = [params[key]] 112 params[key].append(value) 113 else: 114 params[key] = value 115 116 117 class Entity(object): 118 attempt_charsets = ['utf-8'] 119 charset = None 120 content_type = None 121 default_content_type = 'application/x-www-form-urlencoded' 122 filename = None 123 fp = None 124 headers = None 125 length = None 126 name = None 127 params = None 128 processors = {'application/x-www-form-urlencoded': process_urlencoded, 129 'multipart/form-data': process_multipart_form_data, 130 'multipart': process_multipart} 131 parts = None 132 part_class = None 133 134 def __init__(self, fp, headers, params = None, parts = None): 135 self.processors = self.processors.copy() 136 self.fp = fp 137 self.headers = headers 138 if params is None: 139 params = {} 140 self.params = params 141 if parts is None: 142 parts = [] 143 self.parts = parts 144 self.content_type = headers.elements('Content-Type') 145 if self.content_type: 146 self.content_type = self.content_type[0] 147 else: 148 self.content_type = httputil.HeaderElement.from_str(self.default_content_type) 149 dec = self.content_type.params.get('charset', None) 150 if dec: 151 self.attempt_charsets = [dec] + [ c for c in self.attempt_charsets if c != dec ] 152 else: 153 self.attempt_charsets = self.attempt_charsets[:] 154 self.length = None 155 clen = headers.get('Content-Length', None) 156 if clen is not None and 'chunked' not in headers.get('Transfer-Encoding', ''): 157 try: 158 self.length = int(clen) 159 except ValueError: 160 pass 161 162 self.name = None 163 self.filename = None 164 disp = headers.elements('Content-Disposition') 165 if disp: 166 disp = disp[0] 167 if 'name' in disp.params: 168 self.name = disp.params['name'] 169 if self.name.startswith('"') and self.name.endswith('"'): 170 self.name = self.name[1:-1] 171 if 'filename' in disp.params: 172 self.filename = disp.params['filename'] 173 if self.filename.startswith('"') and self.filename.endswith('"'): 174 self.filename = self.filename[1:-1] 175 176 type = property(lambda self: self.content_type, doc='A deprecated alias for :attr:`content_type<cherrypy._cpreqbody.Entity.content_type>`.') 177 178 def read(self, size = None, fp_out = None): 179 return self.fp.read(size, fp_out) 180 181 def readline(self, size = None): 182 return self.fp.readline(size) 183 184 def readlines(self, sizehint = None): 185 return self.fp.readlines(sizehint) 186 187 def __iter__(self): 188 return self 189 190 def next(self): 191 line = self.readline() 192 if not line: 193 raise StopIteration 194 return line 195 196 def read_into_file(self, fp_out = None): 197 if fp_out is None: 198 fp_out = self.make_file() 199 self.read(fp_out=fp_out) 200 return fp_out 201 202 def make_file(self): 203 return tempfile.TemporaryFile() 204 205 def fullvalue(self): 206 if self.file: 207 self.file.seek(0) 208 value = self.file.read() 209 self.file.seek(0) 210 else: 211 value = self.value 212 return value 213 214 def process(self): 215 proc = None 216 ct = self.content_type.value 217 try: 218 proc = self.processors[ct] 219 except KeyError: 220 toptype = ct.split('/', 1)[0] 221 try: 222 proc = self.processors[toptype] 223 except KeyError: 224 pass 225 226 if proc is None: 227 self.default_proc() 228 else: 229 proc(self) 230 231 def default_proc(self): 232 pass 233 234 235 class Part(Entity): 236 attempt_charsets = ['us-ascii', 'utf-8'] 237 boundary = None 238 default_content_type = 'text/plain' 239 maxrambytes = 1000 240 241 def __init__(self, fp, headers, boundary): 242 Entity.__init__(self, fp, headers) 243 self.boundary = boundary 244 self.file = None 245 self.value = None 246 247 def from_fp(cls, fp, boundary): 248 headers = cls.read_headers(fp) 249 return cls(fp, headers, boundary) 250 251 from_fp = classmethod(from_fp) 252 253 def read_headers(cls, fp): 254 headers = httputil.HeaderMap() 255 while True: 256 line = fp.readline() 257 if not line: 258 raise EOFError('Illegal end of headers.') 259 if line == ntob('\r\n'): 260 break 261 if not line.endswith(ntob('\r\n')): 262 raise ValueError('MIME requires CRLF terminators: %r' % line) 263 if line[0] in ntob(' \t'): 264 v = line.strip().decode('ISO-8859-1') 265 else: 266 k, v = line.split(ntob(':'), 1) 267 k = k.strip().decode('ISO-8859-1') 268 v = v.strip().decode('ISO-8859-1') 269 existing = headers.get(k) 270 if existing: 271 v = ', '.join((existing, v)) 272 headers[k] = v 273 274 return headers 275 276 read_headers = classmethod(read_headers) 277 278 def read_lines_to_boundary(self, fp_out = None): 279 endmarker = self.boundary + ntob('--') 280 delim = ntob('') 281 prev_lf = True 282 lines = [] 283 seen = 0 284 while True: 285 line = self.fp.readline(65536) 286 if not line: 287 raise EOFError('Illegal end of multipart body.') 288 if line.startswith(ntob('--')) and prev_lf: 289 strippedline = line.strip() 290 if strippedline == self.boundary: 291 break 292 if strippedline == endmarker: 293 self.fp.finish() 294 break 295 line = delim + line 296 if line.endswith(ntob('\r\n')): 297 delim = ntob('\r\n') 298 line = line[:-2] 299 prev_lf = True 300 elif line.endswith(ntob('\n')): 301 delim = ntob('\n') 302 line = line[:-1] 303 prev_lf = True 304 else: 305 delim = ntob('') 306 prev_lf = False 307 if fp_out is None: 308 lines.append(line) 309 seen += len(line) 310 if seen > self.maxrambytes: 311 fp_out = self.make_file() 312 for line in lines: 313 fp_out.write(line) 314 315 else: 316 fp_out.write(line) 317 318 if fp_out is None: 319 result = ntob('').join(lines) 320 for charset in self.attempt_charsets: 321 try: 322 result = result.decode(charset) 323 except UnicodeDecodeError: 324 pass 325 else: 326 self.charset = charset 327 return result 328 329 else: 330 raise cherrypy.HTTPError(400, 'The request entity could not be decoded. The following charsets were attempted: %s' % repr(self.attempt_charsets)) 331 332 else: 333 fp_out.seek(0) 334 return fp_out 335 336 def default_proc(self): 337 if self.filename: 338 self.file = self.read_into_file() 339 else: 340 result = self.read_lines_to_boundary() 341 if isinstance(result, basestring): 342 self.value = result 343 else: 344 self.file = result 345 346 def read_into_file(self, fp_out = None): 347 if fp_out is None: 348 fp_out = self.make_file() 349 self.read_lines_to_boundary(fp_out=fp_out) 350 return fp_out 351 352 353 Entity.part_class = Part 354 355 class Infinity(object): 356 357 def __cmp__(self, other): 358 return 1 359 360 def __sub__(self, other): 361 return self 362 363 364 inf = Infinity() 365 comma_separated_headers = ['Accept', 366 'Accept-Charset', 367 'Accept-Encoding', 368 'Accept-Language', 369 'Accept-Ranges', 370 'Allow', 371 'Cache-Control', 372 'Connection', 373 'Content-Encoding', 374 'Content-Language', 375 'Expect', 376 'If-Match', 377 'If-None-Match', 378 'Pragma', 379 'Proxy-Authenticate', 380 'Te', 381 'Trailer', 382 'Transfer-Encoding', 383 'Upgrade', 384 'Vary', 385 'Via', 386 'Warning', 387 'Www-Authenticate'] 388 389 class SizedReader: 390 391 def __init__(self, fp, length, maxbytes, bufsize = 8192, has_trailers = False): 392 self.fp = fp 393 self.length = length 394 self.maxbytes = maxbytes 395 self.buffer = ntob('') 396 self.bufsize = bufsize 397 self.bytes_read = 0 398 self.done = False 399 self.has_trailers = has_trailers 400 401 def read(self, size = None, fp_out = None): 402 if self.length is None: 403 if size is None: 404 remaining = inf 405 else: 406 remaining = size 407 else: 408 remaining = self.length - self.bytes_read 409 if size and size < remaining: 410 remaining = size 411 if remaining == 0: 412 self.finish() 413 if fp_out is None: 414 return ntob('') 415 else: 416 return 417 chunks = [] 418 if self.buffer: 419 if remaining is inf: 420 data = self.buffer 421 self.buffer = ntob('') 422 else: 423 data = self.buffer[:remaining] 424 self.buffer = self.buffer[remaining:] 425 datalen = len(data) 426 remaining -= datalen 427 self.bytes_read += datalen 428 if self.maxbytes and self.bytes_read > self.maxbytes: 429 raise cherrypy.HTTPError(413) 430 if fp_out is None: 431 chunks.append(data) 432 else: 433 fp_out.write(data) 434 while remaining > 0: 435 chunksize = min(remaining, self.bufsize) 436 try: 437 data = self.fp.read(chunksize) 438 except Exception: 439 e = sys.exc_info()[1] 440 if e.__class__.__name__ == 'MaxSizeExceeded': 441 raise cherrypy.HTTPError(413, 'Maximum request length: %r' % e.args[1]) 442 else: 443 raise 444 445 if not data: 446 self.finish() 447 break 448 datalen = len(data) 449 remaining -= datalen 450 self.bytes_read += datalen 451 if self.maxbytes and self.bytes_read > self.maxbytes: 452 raise cherrypy.HTTPError(413) 453 if fp_out is None: 454 chunks.append(data) 455 else: 456 fp_out.write(data) 457 458 if fp_out is None: 459 return ntob('').join(chunks) 460 461 def readline(self, size = None): 462 chunks = [] 463 while size is None or size > 0: 464 chunksize = self.bufsize 465 if size is not None and size < self.bufsize: 466 chunksize = size 467 data = self.read(chunksize) 468 if not data: 469 break 470 pos = data.find(ntob('\n')) + 1 471 if pos: 472 chunks.append(data[:pos]) 473 remainder = data[pos:] 474 self.buffer += remainder 475 self.bytes_read -= len(remainder) 476 break 477 else: 478 chunks.append(data) 479 480 return ntob('').join(chunks) 481 482 def readlines(self, sizehint = None): 483 if self.length is not None: 484 if sizehint is None: 485 sizehint = self.length - self.bytes_read 486 else: 487 sizehint = min(sizehint, self.length - self.bytes_read) 488 lines = [] 489 seen = 0 490 while True: 491 line = self.readline() 492 if not line: 493 break 494 lines.append(line) 495 seen += len(line) 496 if seen >= sizehint: 497 break 498 499 return lines 500 501 def finish(self): 502 self.done = True 503 if self.has_trailers and hasattr(self.fp, 'read_trailer_lines'): 504 self.trailers = {} 505 try: 506 for line in self.fp.read_trailer_lines(): 507 if line[0] in ntob(' \t'): 508 v = line.strip() 509 else: 510 try: 511 k, v = line.split(ntob(':'), 1) 512 except ValueError: 513 raise ValueError('Illegal header line.') 514 515 k = k.strip().title() 516 v = v.strip() 517 if k in comma_separated_headers: 518 existing = self.trailers.get(envname) 519 if existing: 520 v = ntob(', ').join((existing, v)) 521 self.trailers[k] = v 522 523 except Exception: 524 e = sys.exc_info()[1] 525 if e.__class__.__name__ == 'MaxSizeExceeded': 526 raise cherrypy.HTTPError(413, 'Maximum request length: %r' % e.args[1]) 527 else: 528 raise 529 530 531 class RequestBody(Entity): 532 bufsize = 8192 533 default_content_type = '' 534 maxbytes = None 535 536 def __init__(self, fp, headers, params = None, request_params = None): 537 Entity.__init__(self, fp, headers, params) 538 if self.content_type.value.startswith('text/'): 539 for c in ('ISO-8859-1', 'iso-8859-1', 'Latin-1', 'latin-1'): 540 if c in self.attempt_charsets: 541 break 542 else: 543 self.attempt_charsets.append('ISO-8859-1') 544 545 self.processors['multipart'] = _old_process_multipart 546 if request_params is None: 547 request_params = {} 548 self.request_params = request_params 549 550 def process(self): 551 h = cherrypy.serving.request.headers 552 if 'Content-Length' not in h and 'Transfer-Encoding' not in h: 553 raise cherrypy.HTTPError(411) 554 self.fp = SizedReader(self.fp, self.length, self.maxbytes, bufsize=self.bufsize, has_trailers='Trailer' in h) 555 super(RequestBody, self).process() 556 request_params = self.request_params 557 for key, value in self.params.items(): 558 if isinstance(key, unicode): 559 key = key.encode('ISO-8859-1') 560 if key in request_params: 561 if not isinstance(request_params[key], list): 562 request_params[key] = [request_params[key]] 563 request_params[key].append(value) 564 else: 565 request_params[key] = value