def multipart_encode(self, vars, files, boundary=None, buffer=None): if boundary is None: if isPython3: boundary = mimetools._make_boundary() boundary = boundary.replace("=", "-") else: boundary = mimetools.choose_boundary() if buffer is None: buffer = '' for (key, value) in vars: buffer += '--%s\r\n' % boundary buffer += 'Content-Disposition: form-data; name="%s"' % key buffer += '\r\n\r\n' + value + '\r\n' for (key, fd) in files: filename = fd.name.split('/')[-1] contenttype = mimetypes.guess_type( filename)[0] or 'application/octet-stream' buffer += '--%s\r\n' % boundary buffer += 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % ( key, filename) buffer += 'Content-Type: %s\r\n' % contenttype fd.seek(0) if isPython3: buffer = buffer.encode() + b'\r\n' + fd.read() + b'\r\n' else: buffer += '\r\n' + fd.read() + '\r\n' if isPython3: buffer += b'--%b--\r\n\r\n' % boundary.encode() else: buffer += '--%s--\r\n\r\n' % boundary return boundary, buffer
def __encode_multipart_formdata(self, fields, files): """ fields is a sequence of (key, value) elements for regular form fields. files is a sequence of (filename, filehandle) files to be uploaded returns (content_type, body) """ BOUNDARY = _make_boundary() if len(files) > 0: fields['nFileCount'] = str(len(files)) crlf = '\r\n' buf = StringIO() for k, v in list(fields.items()): if DEBUG: print(("field: %s: %s"% (repr(k), repr(v)))) buf.write(crlf.join([ '--' + BOUNDARY, 'Content-disposition: form-data; name="%s"' % k, '', str(v), '' ])) n = 0 for f, h in list(files.items()): n += 1 buf.write(crlf.join([ '--' + BOUNDARY, 'Content-disposition: form-data; name="File%d"; filename="%s"' % ( n, f), '' ])) buf.write(crlf.join([ 'Content-type: application/octet-stream', '', '' ])) buf.write(h.read()) buf.write(crlf) buf.write('--' + BOUNDARY + '--' + crlf) content_type = "multipart/form-data; boundary=%s" % BOUNDARY return content_type, buf.getvalue().encode('utf-8')
def __init__(self, ak, sk, type='TOP', upload_endpoint=config.UPLOAD_ENDPOINT, manage_endpoint=config.MANAGE_ENDPOINT): '''Description : 文件上传对象. Input : AK: 开发者的AccessKeyId SK: 开发者的AccessKeySecret type: 开发者的服务类型(即AK/SK的颁发类型,百川用户都是TOP),可不填写。 `upload_endpoint`和`manage_endpoint`一般不需要填写,使用默认值即可。 ''' self.__ak = ak self.__sk = sk self.__type = type self.upload_endpoint = upload_endpoint self.manage_endpoint = manage_endpoint self.http_client = WantHttp() self.boundary = _make_boundary()
def multipart_encode(self, vars): "Enconde form data (vars dict)" boundary = _make_boundary() buf = StringIO() for key, value in list(vars.items()): if not isinstance(value, IOBase): buf.write('--%s\r\n' % boundary) buf.write('Content-Disposition: form-data; name="%s"' % key) buf.write('\r\n\r\n' + value + '\r\n') else: fd = value file_size = os.fstat(fd.fileno())[stat.ST_SIZE] filename = os.path.basename(fd.name) contenttype = mimetypes.guess_type( filename)[0] or 'application/octet-stream' buf.write('--%s\r\n' % boundary) buf.write( 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename)) buf.write('Content-Type: %s\r\n' % contenttype) # buffer += 'Content-Length: %s\r\n' % file_size fd.seek(0) buf.write('\r\n' + fd.read() + '\r\n') buf.write('--' + boundary + '--\r\n\r\n') buf = buf.getvalue() return boundary, buf
def multipart_encode(self, vars, files, boundary=None, buffer=None): if boundary is None: if isPython3: boundary = mimetools._make_boundary() boundary = boundary.replace("=", "-") else: boundary = mimetools.choose_boundary() if buffer is None: buffer = '' for(key, value) in vars: buffer += '--%s\r\n' % boundary buffer += 'Content-Disposition: form-data; name="%s"' % key buffer += '\r\n\r\n' + value + '\r\n' for(key, fd) in files: filename = fd.name.split('/')[-1] contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' buffer += '--%s\r\n' % boundary buffer += 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename) buffer += 'Content-Type: %s\r\n' % contenttype fd.seek(0) if isPython3: buffer = buffer.encode() + b'\r\n' + fd.read() + b'\r\n' else: buffer += '\r\n' + fd.read() + '\r\n' if isPython3: buffer += b'--%b--\r\n\r\n' % boundary.encode() else: buffer += '--%s--\r\n\r\n' % boundary return boundary, buffer
def _handle_multipart(self, msg): subparts = msg.get_payload() if subparts is None: subparts = [] elif isinstance(subparts, basestring): self._fp.write(subparts) return elif not isinstance(subparts, list): subparts = [subparts] msgtexts = [self._flatten_submessage(part) for part in subparts] alltext = '\r\n'.join(msgtexts) no_boundary = object() boundary = msg.get_boundary(failobj=no_boundary) if boundary is no_boundary: boundary = _make_boundary(alltext) msg.set_boundary(boundary) if msg.preamble is not None: self._fp.write(msg.preamble) self._fp.write('\r\n') self._fp.write('--' + boundary) for body_part in msgtexts: self._fp.write('\r\n') self._fp.write(body_part) self._fp.write('\r\n--' + boundary) self._fp.write('--\r\n') if msg.epilogue is not None: self._fp.write('\r\n') self._fp.write(msg.epilogue)
def _encode_form_data(self, fields, files): boundary = _make_boundary() parts = [] for name, value in fields: parts.append(b("--%s" % boundary)) parts.append(b("Content-Disposition: form-data; name=\"%s\"" % name)) parts.append(b("Content-Type: text/plain")) parts.append(b("")) parts.append(b(value)) for name, fp in files: filename = basename(fp.name) mimetype = _get_content_type(filename) fp.seek(0) parts.append(b("--%s" % boundary)) parts.append(b("Content-Disposition: file; name=\"%s\"; " \ "filename=\"%s\"" % (name, filename))) parts.append(b("Content-Type: %s" % mimetype)) parts.append(b("")) parts.append(fp.read()) parts.append(b("--%s--" % boundary)) data = b(NEWLINE).join(parts) return boundary, data
def _handle_multipart(self, msg): # The trick here is to write out each part separately, merge the all # together, and then make sure that the boundary we've chosen isn't # present in the payload. msgtexts = [] subparts = msg.get_payload() if subparts is None: subparts = [] elif isinstance(subparts, basestring): # e.g. a non-strict parse of a message with no starting boundary self._fp.write(subparts) return elif not isinstance(subparts, list): # Scalar payload subparts = [subparts] for part in subparts: s = StringIO() g = self.clone(s) g.flatten(part, unixfrom=False) msgtexts.append(s.getvalue()) # BAW: What about boundaries that are wrapped in double-quotes? boundary = msg.get_boundary() if not boundary: # Create a boundary that doesn't appear in any of the # message texts. alltext = NL.join(msgtexts) boundary = _make_boundary(alltext) msg.set_boundary(boundary) # If there's a preamble, write it out, with a trailing CRLF if msg.preamble is not None: if self._mangle_from_: preamble = fcre.sub('>From ', msg.preamble) else: preamble = msg.preamble print >> self._fp, preamble # dash-boundary transport-padding CRLF print >> self._fp, '--' + boundary # body-part if msgtexts: self._fp.write(msgtexts.pop(0)) # *encapsulation # --> delimiter transport-padding # --> CRLF body-part for body_part in msgtexts: # delimiter transport-padding CRLF print >> self._fp, '\n--' + boundary # body-part self._fp.write(body_part) # close-delimiter transport-padding self._fp.write('\n--' + boundary + '--' + NL) if msg.epilogue is not None: if self._mangle_from_: epilogue = fcre.sub('>From ', msg.epilogue) else: epilogue = msg.epilogue self._fp.write(epilogue)
def __create_request_parts(self, files): request_list = [] boundary = _make_boundary() content_length = 0 boundary_string = b'--%s\r\n' % (boundary) for fieldname, value in iter_fields(files): content_length += len(boundary_string) if isinstance(value, tuple): filename, data = value content_disposition_string = ( ('Content-Disposition: form-data; name="%s"; ' 'filename="%s"\r\n' % (fieldname, filename)) + ('Content-Type: %s\r\n\r\n' % (guess_content_type(filename)))) else: data = value content_disposition_string = ( ('Content-Disposition: form-data; name="%s"\r\n' % (fieldname)) + 'Content-Type: text/plain\r\n\r\n') request_list.append( BytesIO(str(boundary_string + content_disposition_string))) content_length += len(content_disposition_string) if hasattr(data, 'read'): data_stream = data else: data_stream = BytesIO(str(data)) data_stream.seek(0, 2) data_size = data_stream.tell() data_stream.seek(0) request_list.append(data_stream) content_length += data_size end_string = b'\r\n' request_list.append(BytesIO(end_string)) content_length += len(end_string) request_list.append(BytesIO(b'--%s--\r\n' % (boundary))) content_length += len(boundary_string) # There's a bug in httplib.py that generates a UnicodeDecodeError on binary uploads if # there are *any* unicode strings passed into headers as part of the requests call. # For this reason all strings are explicitly converted to non-unicode at this point. self.content_type_header = { b'Content-Type': b'multipart/form-data; boundary=%s' % boundary } self.content_length_header = {b'Content-Length': str(content_length)} self._body_parts = request_list self._content_length = content_length + 2
def __encode_multipart_formdata(self, fields, files): """ fields is a sequence of (key, value) elements for regular form fields. files is a sequence of (filename, filehandle) files to be uploaded returns (content_type, body) """ BOUNDARY = _make_boundary() if len(files) > 0: fields['nFileCount'] = str(len(files)) crlf = '\r\n' buf = BytesIO() for k, v in fields.items(): if DEBUG: print("field: %s: %s"% (repr(k), repr(v))) lines = [ '--' + BOUNDARY, 'Content-disposition: form-data; name="%s"' % k, '', str(v), '', ] buf.write(crlf.join(lines).encode('utf-8')) n = 0 for f, h in files.items(): n += 1 lines = [ '--' + BOUNDARY, 'Content-disposition: form-data; name="File%d"; ' 'filename="%s"' % (n, f), '', ] buf.write(crlf.join(lines).encode('utf-8')) lines = [ 'Content-type: application/octet-stream', '', '', ] buf.write(crlf.join(lines).encode('utf-8')) buf.write(h.read()) buf.write(crlf.encode('utf-8')) buf.write(('--' + BOUNDARY + '--' + crlf).encode('utf-8')) content_type = "multipart/form-data; boundary=%s" % BOUNDARY return content_type, buf.getvalue()
def __create_request_parts(self, files): request_list = [] boundary = _make_boundary() content_length = 0 boundary_string = '--%s\r\n' % (boundary) for fieldname, value in iter_fields(files): content_length += len(boundary_string) if isinstance(value, tuple): filename, data = value content_disposition_string = (('Content-Disposition: form-data; name="%s"; ''filename="%s"\r\n' % (fieldname, filename)) + ('Content-Type: %s\r\n\r\n' % (guess_content_type(filename)))) else: data = value content_disposition_string = (('Content-Disposition: form-data; name="%s"\r\n' % (fieldname)) + 'Content-Type: text/plain\r\n\r\n') request_list.append(BytesIO(str.encode(boundary_string + content_disposition_string))) content_length += len(content_disposition_string) if hasattr(data, 'read'): data_stream = data else: data_stream = BytesIO(str.encode(data)) data_stream.seek(0,2) data_size = data_stream.tell() data_stream.seek(0) request_list.append(data_stream) content_length += data_size end_string = b'\r\n' request_list.append(BytesIO(end_string)) content_length += len(end_string) request_list.append(BytesIO(str.encode('--%s--\r\n' % (boundary)))) content_length += len(boundary_string) # There's a bug in httplib.py that generates a UnicodeDecodeError on binary uploads if # there are *any* unicode strings passed into headers as part of the requests call. # For this reason all strings are explicitly converted to non-unicode at this point. self.content_type_header = {b'Content-Type': str.encode('multipart/form-data; boundary=%s' % boundary)} self.content_length_header = {b'Content-Length': str.encode(str(content_length))} self._body_parts = request_list self._content_length = content_length + 2
def encode_multipart_formdata(fields, files): BOUNDARY = _make_boundary() CRLF = '\r\n' L = [] for (key, value) in fields: L.append('--' + BOUNDARY) L.append('Content-Disposition: form-data; name="%s"' % key) L.append('') L.append(value) for (key, filename, value) in files: L.append('--' + BOUNDARY) L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) L.append('Content-Type: %s' % mimetypes.guess_type(filename)[0] or 'application/octet-stream') L.append('') L.append(value) L.append('--' + BOUNDARY + '--') L.append('') body = CRLF.join(L) content_type = 'multipart/form-data; boundary=%s' % BOUNDARY return content_type, body
def encode_multipart_formdata(fields, files): BOUNDARY = _make_boundary() CRLF = ('\r\n').encode() L = [] if fields is not None: for key in fields: L.append(('--' + BOUNDARY).encode()) L.append(('Content-Disposition: form-data; name="%s"' % key).encode()) L.append(('').encode()) L.append(fields[key].encode()) for (key, filename, value) in files: L.append(('--' + BOUNDARY).encode()) L.append(('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)).encode()) L.append(('Content-Type: %s' % get_content_type(filename)).encode()) L.append(('').encode()) L.append(value) L.append(('--' + BOUNDARY + '--').encode()) L.append(('').encode()) body = CRLF.join(L) content_type = 'multipart/form-data; boundary=%s' % BOUNDARY return content_type, body
def multipart_encode(self, these_vars, files, boundary = None, buf = None): if boundary is None: boundary = _make_boundary(os.getuid + os.getpid()) if buf is None: buf = StringIO() for(key, value) in these_vars: buf.write('--%s\r\n' % boundary) buf.write('Content-Disposition: form-data; name="%s"' % key) buf.write('\r\n\r\n' + value + '\r\n') for(key, fd) in files: file_size = os.fstat(fd.fileno())[stat.ST_SIZE] filename = fd.name.split('/')[-1] contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' buf.write('--%s\r\n' % boundary) buf.write('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename)) buf.write('Content-Type: %s\r\n' % contenttype) # buffer += 'Content-Length: %s\r\n' % file_size fd.seek(0) buf.write('\r\n' + fd.read() + '\r\n') buf.write('--' + boundary + '--\r\n\r\n') buf = buf.getvalue() return boundary, buf
def encode_multipart_formdata(fields, files): BOUNDARY = _make_boundary() CRLF = '\r\n' L = [] for (key, value) in fields: L.append('--' + BOUNDARY) L.append('Content-Disposition: form-data; name="%s"' % key) L.append('') L.append(value) for (key, filename, value) in files: L.append('--' + BOUNDARY) L.append( 'Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) L.append('Content-Type: %s' % mimetypes.guess_type(filename)[0] or 'application/octet-stream') L.append('') L.append(value) L.append('--' + BOUNDARY + '--') L.append('') body = CRLF.join(L) content_type = 'multipart/form-data; boundary=%s' % BOUNDARY return content_type, body
def fetch_form(self, form, submit=None): values = [] form_elements = find_form_elements(form) if submit is None: submits = [_submit for _submit in form_elements if _submit.get('type', '').lower() in ('submit', 'image')] if len(submits) == 1: submit = submits[0] elif submits: raise MechanizeError('multiple submit buttons exist. specify any one') collect_form_values(values, form_elements, submit) method = form.get('method').upper() enctype = form.get('enctype', FORM_URLENCODE_MIME_TYPE) url = urljoin(self.location, form.get('action')) logger.debug('submit_form: method=%s, url=%s, enctype=%s, values=%r' % (method, url, enctype, values)) if method.upper() == 'GET': encoding = self.encoding queries = urlencoded_form_data(values) final_url = urljoin(url, '?' + queries) return self.create_loader(urllib2.Request(final_url)) else: headers = None if enctype == FORM_URLENCODE_MIME_TYPE: data = encode_urlencoded_form_data(values, self.encoding) headers = { 'Content-Type': FORM_URLENCODE_MIME_TYPE } else: boundary = _make_boundary() data = encode_mime_multipart_form_data(values, self.encoding, boundary) headers = { 'Content-Type': '%s; boundary=%s' % (FORM_MULTIPART_MIME_TYPE, boundary) } return self.create_loader(urllib2.Request(url, data=data, headers=headers))
def serve_file(request, response, path, type=None, disposition=None, name=None): """Set status, headers, and body in order to serve the given file. The Content-Type header will be set to the type arg, if provided. If not provided, the Content-Type will be guessed by the file extension of the 'path' argument. If disposition is not None, the Content-Disposition header will be set to "<disposition>; filename=<name>". If name is None, it will be set to the basename of path. If disposition is None, no Content-Disposition header will be written. """ if not os.path.isabs(path): raise ValueError("'%s' is not an absolute path." % path) try: st = os.stat(path) except OSError: return notfound(request, response) # Check if path is a directory. if stat.S_ISDIR(st.st_mode): # Let the caller deal with it as they like. return notfound(request, response) # Set the Last-Modified response header, so that # modified-since validation code can work. response.headers['Last-Modified'] = formatdate( st.st_mtime, usegmt=True ) result = validate_since(request, response) if result is not None: return result if type is None: # Set content-type based on filename extension ext = "" i = path.rfind('.') if i != -1: ext = path[i:].lower() type = mimetypes.types_map.get(ext, "text/plain") response.headers['Content-Type'] = type if disposition is not None: if name is None: name = os.path.basename(path) cd = '%s; filename="%s"' % (disposition, name) response.headers["Content-Disposition"] = cd # Set Content-Length and use an iterable (file object) # this way CP won't load the whole file in memory c_len = st.st_size bodyfile = open(path, 'rb') # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code if request.protocol >= (1, 1): response.headers["Accept-Ranges"] = "bytes" r = get_ranges(request.headers.get('Range'), c_len) if r == []: response.headers['Content-Range'] = "bytes */%s" % c_len return httperror(request, response, 416) if r: if len(r) == 1: # Return a single-part response. start, stop = r[0] r_len = stop - start response.status = 206 response.headers['Content-Range'] = ( "bytes %s-%s/%s" % (start, stop - 1, c_len) ) response.headers['Content-Length'] = r_len bodyfile.seek(start) response.body = bodyfile.read(r_len) else: # Return a multipart/byteranges response. response.status = 206 boundary = _make_boundary() ct = "multipart/byteranges; boundary=%s" % boundary response.headers['Content-Type'] = ct if "Content-Length" in response.headers: # Delete Content-Length header so finalize() recalcs it. del response.headers["Content-Length"] def file_ranges(): # Apache compatibility: yield "\r\n" for start, stop in r: yield "--" + boundary yield "\r\nContent-type: %s" % type yield ("\r\nContent-range: bytes %s-%s/%s\r\n\r\n" % (start, stop - 1, c_len)) bodyfile.seek(start) yield bodyfile.read(stop - start) yield "\r\n" # Final boundary yield "--" + boundary + "--" # Apache compatibility: yield "\r\n" response.body = file_ranges() else: response.headers['Content-Length'] = c_len response.body = bodyfile else: response.headers['Content-Length'] = c_len response.body = bodyfile return response
def _handle_multipart(self, msg): """ A multipart handling implementation that addresses issue #14983. This is just a copy of the parent's method which fixes the following bug: http://bugs.python.org/issue14983 (see the line marked with "(***)"). :param msg: The multipart message to be handled. :type msg: email.message.Message """ # The trick here is to write out each part separately, merge them all # together, and then make sure that the boundary we've chosen isn't # present in the payload. msgtexts = [] subparts = msg.get_payload() if subparts is None: subparts = [] elif isinstance(subparts, basestring): # e.g. a non-strict parse of a message with no starting boundary. self._fp.write(subparts) return elif not isinstance(subparts, list): # Scalar payload subparts = [subparts] for part in subparts: s = StringIO() g = self.clone(s) g.flatten(part, unixfrom=False) msgtexts.append(s.getvalue()) # BAW: What about boundaries that are wrapped in double-quotes? boundary = msg.get_boundary() if not boundary: # Create a boundary that doesn't appear in any of the # message texts. alltext = NL.join(msgtexts) boundary = _make_boundary(alltext) msg.set_boundary(boundary) # If there's a preamble, write it out, with a trailing CRLF if msg.preamble is not None: preamble = msg.preamble if self._mangle_from_: preamble = fcre.sub('>From ', msg.preamble) self._fp.write(preamble + '\n') # dash-boundary transport-padding CRLF self._fp.write('--' + boundary + '\n') # body-part if msgtexts: self._fp.write(msgtexts.pop(0)) # *encapsulation # --> delimiter transport-padding # --> CRLF body-part for body_part in msgtexts: # delimiter transport-padding CRLF self._fp.write('\n--' + boundary + '\n') # body-part self._fp.write(body_part) # close-delimiter transport-padding self._fp.write('\n--' + boundary + '--' + '\n') # (***) Solve #14983 if msg.epilogue is not None: self._fp.write('\n') epilogue = msg.epilogue if self._mangle_from_: epilogue = fcre.sub('>From ', msg.epilogue) self._fp.write(epilogue)
def gen_boundary(): return _make_boundary()
def __init__(self): self.files = [] self.boundary = _make_boundary()
def _range_request_handler(self, REQUEST, RESPONSE): # HTTP Range header handling: return True if we've served a range # chunk out of our data. range = REQUEST.get_header('Range', None) request_range = REQUEST.get_header('Request-Range', None) if request_range is not None: # Netscape 2 through 4 and MSIE 3 implement a draft version # Later on, we need to serve a different mime-type as well. range = request_range if_range = REQUEST.get_header('If-Range', None) if range is not None: ranges = HTTPRangeSupport.parseRange(range) if if_range is not None: # Only send ranges if the data isn't modified, otherwise send # the whole object. Support both ETags and Last-Modified dates! if len(if_range) > 1 and if_range[:2] == 'ts': # ETag: if if_range != self.http__etag(): # Modified, so send a normal response. We delete # the ranges, which causes us to skip to the 200 # response. ranges = None else: # Date date = if_range.split(';')[0] try: mod_since = int(DateTime(date).timeTime()) except Exception: mod_since = None if mod_since is not None: if self._p_mtime: last_mod = int(self._p_mtime) else: last_mod = 0 if last_mod > mod_since: # Modified, so send a normal response. We delete # the ranges, which causes us to skip to the 200 # response. ranges = None if ranges: # Search for satisfiable ranges. satisfiable = 0 for start, end in ranges: if start < self.size: satisfiable = 1 break if not satisfiable: RESPONSE.setHeader( 'Content-Range', 'bytes */%d' % self.size) RESPONSE.setHeader('Accept-Ranges', 'bytes') RESPONSE.setHeader( 'Last-Modified', rfc1123_date(self._p_mtime)) RESPONSE.setHeader('Content-Type', self.content_type) RESPONSE.setHeader('Content-Length', self.size) RESPONSE.setStatus(416) return True ranges = HTTPRangeSupport.expandRanges(ranges, self.size) if len(ranges) == 1: # Easy case, set extra header and return partial set. start, end = ranges[0] size = end - start RESPONSE.setHeader( 'Last-Modified', rfc1123_date(self._p_mtime)) RESPONSE.setHeader('Content-Type', self.content_type) RESPONSE.setHeader('Content-Length', size) RESPONSE.setHeader('Accept-Ranges', 'bytes') RESPONSE.setHeader( 'Content-Range', 'bytes %d-%d/%d' % (start, end - 1, self.size)) RESPONSE.setStatus(206) # Partial content data = self.data if isinstance(data, binary_type): RESPONSE.write(data[start:end]) return True # Linked Pdata objects. Urgh. pos = 0 while data is not None: l = len(data.data) pos = pos + l if pos > start: # We are within the range lstart = l - (pos - start) if lstart < 0: lstart = 0 # find the endpoint if end <= pos: lend = l - (pos - end) # Send and end transmission RESPONSE.write(data[lstart:lend]) break # Not yet at the end, transmit what we have. RESPONSE.write(data[lstart:]) data = data.next return True else: boundary = _make_boundary() # Calculate the content length size = (8 + len(boundary) + # End marker length len(ranges) * ( # Constant lenght per set 49 + len(boundary) + len(self.content_type) + len('%d' % self.size))) for start, end in ranges: # Variable length per set size = (size + len('%d%d' % (start, end - 1)) + end - start) # Some clients implement an earlier draft of the spec, they # will only accept x-byteranges. draftprefix = (request_range is not None) and 'x-' or '' RESPONSE.setHeader('Content-Length', size) RESPONSE.setHeader('Accept-Ranges', 'bytes') RESPONSE.setHeader( 'Last-Modified', rfc1123_date(self._p_mtime)) RESPONSE.setHeader( 'Content-Type', 'multipart/%sbyteranges; boundary=%s' % ( draftprefix, boundary)) RESPONSE.setStatus(206) # Partial content data = self.data # The Pdata map allows us to jump into the Pdata chain # arbitrarily during out-of-order range searching. pdata_map = {} pdata_map[0] = data for start, end in ranges: RESPONSE.write( b'\r\n--' + boundary.encode('ascii') + b'\r\n') RESPONSE.write( b'Content-Type: ' + self.content_type.encode('ascii') + b'\r\n') RESPONSE.write( b'Content-Range: bytes ' + str(start).encode('ascii') + b'-' + str(end - 1).encode('ascii') + b'/' + str(self.size).encode('ascii') + b'\r\n\r\n') if isinstance(data, binary_type): RESPONSE.write(data[start:end]) else: # Yippee. Linked Pdata objects. The following # calculations allow us to fast-forward through the # Pdata chain without a lot of dereferencing if we # did the work already. first_size = len(pdata_map[0].data) if start < first_size: closest_pos = 0 else: closest_pos = ( ((start - first_size) >> 16 << 16) + first_size) pos = min(closest_pos, max(pdata_map.keys())) data = pdata_map[pos] while data is not None: l = len(data.data) pos = pos + l if pos > start: # We are within the range lstart = l - (pos - start) if lstart < 0: lstart = 0 # find the endpoint if end <= pos: lend = l - (pos - end) # Send and loop to next range RESPONSE.write(data[lstart:lend]) break # Not yet at the end, # transmit what we have. RESPONSE.write(data[lstart:]) data = data.next # Store a reference to a Pdata chain link # so we don't have to deref during # this request again. pdata_map[pos] = data # Do not keep the link references around. del pdata_map RESPONSE.write( b'\r\n--' + boundary.encode('ascii') + b'--\r\n') return True
def serve_file(request, response, path, type=None, disposition=None, name=None): """Set status, headers, and body in order to serve the given file. The Content-Type header will be set to the type arg, if provided. If not provided, the Content-Type will be guessed by the file extension of the 'path' argument. If disposition is not None, the Content-Disposition header will be set to "<disposition>; filename=<name>". If name is None, it will be set to the basename of path. If disposition is None, no Content-Disposition header will be written. """ if not os.path.isabs(path): raise ValueError("'%s' is not an absolute path." % path) try: st = os.stat(path) except OSError: return notfound(request, response) # Check if path is a directory. if stat.S_ISDIR(st.st_mode): # Let the caller deal with it as they like. return notfound(request, response) # Set the Last-Modified response header, so that # modified-since validation code can work. response.headers['Last-Modified'] = formatdate(st.st_mtime, usegmt=True) result = validate_since(request, response) if result is not None: return result if type is None: # Set content-type based on filename extension ext = os.path.splitext(path)[-1].lower() type = mimetypes.types_map.get(ext, "text/plain") response.headers['Content-Type'] = type if disposition is not None: if name is None: name = os.path.basename(path) cd = '%s; filename="%s"' % (disposition, name) response.headers["Content-Disposition"] = cd # Set Content-Length and use an iterable (file object) # this way CP won't load the whole file in memory c_len = st.st_size bodyfile = open(path, 'rb') # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code if request.protocol >= (1, 1): response.headers["Accept-Ranges"] = "bytes" r = get_ranges(request.headers.get('Range'), c_len) if r == []: response.headers['Content-Range'] = "bytes */%s" % c_len return httperror(request, response, 416) if r: if len(r) == 1: # Return a single-part response. start, stop = r[0] r_len = stop - start response.status = 206 response.headers['Content-Range'] = ("bytes %s-%s/%s" % (start, stop - 1, c_len)) response.headers['Content-Length'] = r_len bodyfile.seek(start) response.body = bodyfile.read(r_len) else: # Return a multipart/byteranges response. response.status = 206 boundary = _make_boundary() ct = "multipart/byteranges; boundary=%s" % boundary response.headers['Content-Type'] = ct if "Content-Length" in response.headers: # Delete Content-Length header so finalize() recalcs it. del response.headers["Content-Length"] def file_ranges(): # Apache compatibility: yield "\r\n" for start, stop in r: yield "--" + boundary yield "\r\nContent-type: %s" % type yield ("\r\nContent-range: bytes %s-%s/%s\r\n\r\n" % (start, stop - 1, c_len)) bodyfile.seek(start) yield bodyfile.read(stop - start) yield "\r\n" # Final boundary yield "--" + boundary + "--" # Apache compatibility: yield "\r\n" response.body = file_ranges() else: response.headers['Content-Length'] = c_len response.body = bodyfile else: response.headers['Content-Length'] = c_len response.body = bodyfile return response
def _range_request_handler(self, REQUEST, RESPONSE): # HTTP Range header handling: return True if we've served a range # chunk out of our data. range = REQUEST.get_header('Range', None) request_range = REQUEST.get_header('Request-Range', None) if request_range is not None: # Netscape 2 through 4 and MSIE 3 implement a draft version # Later on, we need to serve a different mime-type as well. range = request_range if_range = REQUEST.get_header('If-Range', None) if range is not None: ranges = HTTPRangeSupport.parseRange(range) if if_range is not None: # Only send ranges if the data isn't modified, otherwise send # the whole object. Support both ETags and Last-Modified dates! if len(if_range) > 1 and if_range[:2] == 'ts': # ETag: if if_range != self.http__etag(): # Modified, so send a normal response. We delete # the ranges, which causes us to skip to the 200 # response. ranges = None else: # Date date = if_range.split(';')[0] try: mod_since = int(DateTime(date).timeTime()) except Exception: mod_since = None if mod_since is not None: if self._p_mtime: last_mod = int(self._p_mtime) else: last_mod = 0 if last_mod > mod_since: # Modified, so send a normal response. We delete # the ranges, which causes us to skip to the 200 # response. ranges = None if ranges: # Search for satisfiable ranges. satisfiable = 0 for start, end in ranges: if start < self.size: satisfiable = 1 break if not satisfiable: RESPONSE.setHeader('Content-Range', 'bytes */%d' % self.size) RESPONSE.setHeader('Accept-Ranges', 'bytes') RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime)) RESPONSE.setHeader('Content-Type', self.content_type) RESPONSE.setHeader('Content-Length', self.size) RESPONSE.setStatus(416) return True ranges = HTTPRangeSupport.expandRanges(ranges, self.size) if len(ranges) == 1: # Easy case, set extra header and return partial set. start, end = ranges[0] size = end - start RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime)) RESPONSE.setHeader('Content-Type', self.content_type) RESPONSE.setHeader('Content-Length', size) RESPONSE.setHeader('Accept-Ranges', 'bytes') RESPONSE.setHeader( 'Content-Range', 'bytes %d-%d/%d' % (start, end - 1, self.size)) RESPONSE.setStatus(206) # Partial content data = self.data if isinstance(data, binary_type): RESPONSE.write(data[start:end]) return True # Linked Pdata objects. Urgh. pos = 0 while data is not None: length = len(data.data) pos = pos + length if pos > start: # We are within the range lstart = length - (pos - start) if lstart < 0: lstart = 0 # find the endpoint if end <= pos: lend = length - (pos - end) # Send and end transmission RESPONSE.write(data[lstart:lend]) break # Not yet at the end, transmit what we have. RESPONSE.write(data[lstart:]) data = data.next return True else: boundary = _make_boundary() # Calculate the content length size = ( 8 + len(boundary) # End marker length + len(ranges) * ( # Constant lenght per set 49 + len(boundary) + len(self.content_type) + len('%d' % self.size))) for start, end in ranges: # Variable length per set size = (size + len('%d%d' % (start, end - 1)) + end - start) # Some clients implement an earlier draft of the spec, they # will only accept x-byteranges. draftprefix = (request_range is not None) and 'x-' or '' RESPONSE.setHeader('Content-Length', size) RESPONSE.setHeader('Accept-Ranges', 'bytes') RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime)) RESPONSE.setHeader( 'Content-Type', 'multipart/%sbyteranges; boundary=%s' % ( draftprefix, boundary, )) RESPONSE.setStatus(206) # Partial content data = self.data # The Pdata map allows us to jump into the Pdata chain # arbitrarily during out-of-order range searching. pdata_map = {} pdata_map[0] = data for start, end in ranges: RESPONSE.write(b'\r\n--' + boundary.encode('ascii') + b'\r\n') RESPONSE.write(b'Content-Type: ' + self.content_type.encode('ascii') + b'\r\n') RESPONSE.write(b'Content-Range: bytes ' + str(start).encode('ascii') + b'-' + str(end - 1).encode('ascii') + b'/' + str(self.size).encode('ascii') + b'\r\n\r\n') if isinstance(data, binary_type): RESPONSE.write(data[start:end]) else: # Yippee. Linked Pdata objects. The following # calculations allow us to fast-forward through the # Pdata chain without a lot of dereferencing if we # did the work already. first_size = len(pdata_map[0].data) if start < first_size: closest_pos = 0 else: closest_pos = (( (start - first_size) >> 16 << 16) + first_size) pos = min(closest_pos, max(pdata_map.keys())) data = pdata_map[pos] while data is not None: length = len(data.data) pos = pos + length if pos > start: # We are within the range lstart = length - (pos - start) if lstart < 0: lstart = 0 # find the endpoint if end <= pos: lend = length - (pos - end) # Send and loop to next range RESPONSE.write(data[lstart:lend]) break # Not yet at the end, # transmit what we have. RESPONSE.write(data[lstart:]) data = data.next # Store a reference to a Pdata chain link # so we don't have to deref during # this request again. pdata_map[pos] = data # Do not keep the link references around. del pdata_map RESPONSE.write(b'\r\n--' + boundary.encode('ascii') + b'--\r\n') return True