def testIfModSince(self): now = time.time() e = {'SERVER_NAME': 'foo', 'SERVER_PORT': '80', 'REQUEST_METHOD': 'GET'} # not modified since t_notmod = rfc1123_date(now) e['HTTP_IF_MODIFIED_SINCE'] = t_notmod out = BytesIO() resp = HTTPResponse(stdout=out) req = HTTPRequest(sys.stdin, e, resp) data = self.file.index_html(req, resp) self.assertEqual(resp.getStatus(), 304) self.assertEqual(data, b'') # modified since t_mod = rfc1123_date(now - 100) e['HTTP_IF_MODIFIED_SINCE'] = t_mod out = BytesIO() resp = HTTPResponse(stdout=out) req = HTTPRequest(sys.stdin, e, resp) data = self.file.index_html(req, resp) self.assertEqual(resp.getStatus(), 200) self.assertEqual(data, bytes(self.file.data))
def __set_http_headers(self): """Set HTTP headers on the reply. """ response = self.request.response expires = time.time() + CACHE_TIME # set response headers response.setHeader('Content-Type', self.context.content_type) response.setHeader('Content-Length', self.context.content_length) response.setHeader('Cache-Control', 'public,max-age=%s' % CACHE_TIME) response.setHeader('Last-Modified', rfc1123_date(self.context.lmt)) response.setHeader('Expires', rfc1123_date(expires))
def __call__(self): # Download the asset. response = self.request.response response.setHeader( 'Content-Disposition', 'inline;filename=%s' % urllib.quote( self.context.filename.encode('utf-8'))) response.setHeader( 'Content-Length', '%s' % self.context.length) response.setHeader( 'Content-Type', self.context.mimetype) # For caching, acquired one level. response.setHeader( 'Last-Modified', rfc1123_date(self.context.modified())) # We don't support advanced streaming (yet). response.setHeader( 'Accept-Ranges', None) if self.context.data is not None: return self.context.data # We have a stream, nice ! chunk = self.context.stream.read(CHUNK_SIZE) while chunk: response.write(chunk) chunk = self.context.stream.read(CHUNK_SIZE) return ''
def _if_modified_since_request_handler(self, REQUEST, RESPONSE): # HTTP If-Modified-Since header handling: return True if # we can handle this request by returning a 304 response header = REQUEST.get_header('If-Modified-Since', None) if header is not None: header = header.split(';')[0] # Some proxies seem to send invalid date strings for this # header. If the date string is not valid, we ignore it # rather than raise an error to be generally consistent # with common servers such as Apache (which can usually # understand the screwy date string as a lucky side effect # of the way they parse it). # This happens to be what RFC2616 tells us to do in the face of an # invalid date. try: mod_since = int(DateTime(header).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 > 0 and last_mod <= mod_since: RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime)) RESPONSE.setHeader('Content-Type', self.content_type) RESPONSE.setHeader('Accept-Ranges', 'bytes') RESPONSE.setStatus(304) return True
def HEAD(self, REQUEST, RESPONSE): """Retrieve resource information without a response body.""" self.dav__init(REQUEST, RESPONSE) content_type = None if hasattr(self, 'content_type'): content_type = absattr(self.content_type) if content_type is None: url = urlfix(REQUEST['URL'], 'HEAD') name = unquote([_f for _f in url.split('/') if _f][-1]) content_type, encoding = mimetypes.guess_type(name) if content_type is None: if hasattr(self, 'default_content_type'): content_type = absattr(self.default_content_type) if content_type is None: content_type = 'application/octet-stream' RESPONSE.setHeader('Content-Type', content_type.lower()) if hasattr(aq_base(self), 'get_size'): RESPONSE.setHeader('Content-Length', absattr(self.get_size)) if hasattr(self, '_p_mtime'): mtime = rfc1123_date(self._p_mtime) RESPONSE.setHeader('Last-Modified', mtime) if hasattr(aq_base(self), 'http__etag'): etag = self.http__etag(readonly=1) if etag: RESPONSE.setHeader('Etag', etag) RESPONSE.setStatus(200) return RESPONSE
def index_html(self, REQUEST, RESPONSE): """ The default view of the contents of a File or Image. Returns the contents of the file or image. Also, sets the Content-Type HTTP header to the objects content type. """ if self._if_modified_since_request_handler(REQUEST, RESPONSE): # we were able to handle this by returning a 304 # unfortunately, because the HTTP cache manager uses the cache # API, and because 304 responses are required to carry the Expires # header for HTTP/1.1, we need to call ZCacheable_set here. # This is nonsensical for caches other than the HTTP cache manager # unfortunately. self.ZCacheable_set(None) return b'' if self.precondition and hasattr(self, str(self.precondition)): # Grab whatever precondition was defined and then # execute it. The precondition will raise an exception # if something violates its terms. c = getattr(self, str(self.precondition)) if hasattr(c, 'isDocTemp') and c.isDocTemp: c(REQUEST['PARENTS'][1], REQUEST) else: c() if self._range_request_handler(REQUEST, RESPONSE): # we served a chunk of content in response to a range request. return b'' RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime)) RESPONSE.setHeader('Content-Type', self.content_type) RESPONSE.setHeader('Content-Length', self.size) RESPONSE.setHeader('Accept-Ranges', 'bytes') if self.ZCacheable_isCachingEnabled(): result = self.ZCacheable_get(default=None) if result is not None: # We will always get None from RAMCacheManager and HTTP # Accelerated Cache Manager but we will get # something implementing the IStreamIterator interface # from a "FileCacheManager" return result self.ZCacheable_set(None) data = self.data if isinstance(data, bytes): RESPONSE.setBase(None) return data while data is not None: RESPONSE.write(data.data) data = data.next return b''
def __init__(self, path, name): self.path = path f = open(path, 'rb') data = f.read() f.close() self.content_type, enc = guess_content_type(path, data) self.__name__ = name self.lmt = float(os.path.getmtime(path)) or time() self.lmh = rfc1123_date(self.lmt)
def __init__(self, path, name): self.path = path f = open(path, 'rb') self.data = f.read() f.close() self.content_type, enc = guess_content_type(path, self.data) self.__name__ = name self.lmt = float(os.path.getmtime(path)) or time.time() self.lmh = rfc1123_date(self.lmt)
def _fetchlm(self): now = time() if now - self.lmt_last_checked > self.lmt_check_period: self.lmt_last_checked = now lmt = float(self.context.getLastMod()) or now lmh = rfc1123_date(lmt) d = self._last_mod = dict(lmt = lmt, lmh = lmh) ##print "***** LMT reread", d else: d = self._last_mod return d
def render(self): """See `IFeed`.""" expires = rfc1123_date(time.time() + self.max_age) if self.date_updated is not None: last_modified = rfc1123_date( time.mktime(self.date_updated.timetuple())) else: last_modified = rfc1123_date(time.time()) response = self.request.response response.setHeader('Expires', expires) response.setHeader('Cache-Control', 'max-age=%d' % self.max_age) response.setHeader('X-Cache-Control', 'max-age=%d' % self.max_age) response.setHeader('Last-Modified', last_modified) if self.format == 'atom': return self.renderAtom() elif self.format == 'html': return self.renderHTML() else: raise UnsupportedFeedFormat("Format %s is not supported" % self.format)
def dav__init(self, request, response): # We are allowed to accept a url w/o a trailing slash # for a collection, but are supposed to provide a # hint to the client that it should be using one. # [WebDAV, 5.2] pathinfo = request.get('PATH_INFO', '') if pathinfo and pathinfo[-1] != '/': location = '%s/' % request['URL1'] response.setHeader('Content-Location', location) response.setHeader('Date', rfc1123_date(), 1) # Initialize ETag header self.http__etag()
def dav__init(self, request, response): # Init expected HTTP 1.1 / WebDAV headers which are not # currently set by the base response object automagically. # # We sniff for a ZServer response object, because we don't # want to write duplicate headers (since ZS writes Date # and Connection itself). if not hasattr(response, '_server_version'): response.setHeader('Date', rfc1123_date(), 1) # Initialize ETag header self.http__etag() # HTTP Range support if HTTPRangeInterface.providedBy(self): response.setHeader('Accept-Ranges', 'bytes') else: response.setHeader('Accept-Ranges', 'none')
def updateCookieCredentials(self, request, response, user, login): """ Set a cookie to authenticate again people after a change of credentials. """ now = int(time.time()) timestamp = now % self.lifetime session = self._get_session(request) session.set('login', login) session.set('user', user) session.set('timestamp', timestamp) service = getUtility(ISecretService) cookie = service.digest( str(IClientId(request)), login, (now - timestamp) / self.lifetime) response.setCookie( self.cookie_name, cookie, path=self._get_cookie_path(request), expires=rfc1123_date(now + self.lifetime), http_only=True, secure=self.cookie_secure)
def GET(self): request = self.request response = request.response resource = self.resource try: modified = ICMFDublinCore(resource).modified except TypeError: modified = datetime.datetime.now() lmt = long(time.mktime(modified.timetuple())) header = request.getHeader('If-Modified-Since', None) if header is not None: header = header.split(';')[0] try: mod_since=long(timeFromDateTimeString(header)) except: mod_since=None if mod_since is not None: if lmt > 0 and lmt <= mod_since: response.setStatus(304) return '' response.setHeader('Last-Modified', rfc1123_date(lmt)) return resource.render(request, attr=self.attr)
def dav__getlastmodified(self): return rfc1123_date(self.v_self()._p_mtime)
def updated_rfc(self): lmt = zope_datetime.time(self.updated.isoformat()) return zope_datetime.rfc1123_date(lmt)
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, bytes): 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', f'multipart/{draftprefix}byteranges;' f' boundary={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, bytes): 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
def expire(self): return rfc1123_date(time.time() + 12 * 3600)
def createLastModifiedDate(self, offset=0): from zope.datetime import rfc1123_date return rfc1123_date(self.file._p_mtime + offset)
def testrfc1123_date(self): from zope.datetime import rfc1123_date self.assertEqual(rfc1123_date(time("2002-01-12T01:01:01.234Z")), "Sat, 12 Jan 2002 01:01:01 GMT")