def test_ims(self): """ SendFile: If-Modified-Since""" request.environ['HTTP_IF_MODIFIED_SINCE'] = bottle.http_date(time.time()) res = static_file(basename, root=root) self.assertEqual(304, res.status_code) self.assertEqual(int(os.stat(__file__).st_mtime), parse_date(res.headers['Last-Modified'])) self.assertAlmostEqual(int(time.time()), parse_date(res.headers['Date'])) request.environ['HTTP_IF_MODIFIED_SINCE'] = bottle.http_date(100) self.assertEqual(open(__file__,'rb').read(), static_file(basename, root=root).body.read())
def test_ims(self): """ SendFile: If-Modified-Since""" request.environ["HTTP_IF_MODIFIED_SINCE"] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) res = static_file(os.path.basename(__file__), root="./") self.assertEqual(304, res.status) self.assertEqual(int(os.stat(__file__).st_mtime), parse_date(res.headers["Last-Modified"])) self.assertAlmostEqual(int(time.time()), parse_date(res.headers["Date"])) request.environ["HTTP_IF_MODIFIED_SINCE"] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(100)) self.assertEqual(open(__file__, "rb").read(), static_file(os.path.basename(__file__), root="./").output.read())
def test_ims(self): """ SendFile: If-Modified-Since""" request.environ['HTTP_IF_MODIFIED_SINCE'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) res = static_file(os.path.basename(__file__), root='./') self.assertEqual(304, res.status) self.assertEqual(int(os.stat(__file__).st_mtime), parse_date(res.headers['Last-Modified'])) self.assertAlmostEqual(int(time.time()), parse_date(res.headers['Date'])) request.environ['HTTP_IF_MODIFIED_SINCE'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(100)) self.assertEqual(open(__file__,'rb').read(), static_file(os.path.basename(__file__), root='./').output.read())
def test_ims(self): """ SendFile: If-Modified-Since""" request.environ['HTTP_IF_MODIFIED_SINCE'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) res = static_file(os.path.basename(__file__), root='./') self.assertEqual(304, res.status_code) self.assertEqual(int(os.stat(__file__).st_mtime), parse_date(res.headers['Last-Modified'])) self.assertAlmostEqual(int(time.time()), parse_date(res.headers['Date'])) request.environ['HTTP_IF_MODIFIED_SINCE'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(100)) self.assertEqual(open(__file__,'rb').read(), static_file(os.path.basename(__file__), root='./').body.read())
def render_js(filename): """ Render javascript template to insert a translation :param filename: name of the file to be translated """ headers = {} # check the template file path = bottle.SimpleTemplate.search("javascript/%s" % filename, bottle.TEMPLATE_PATH) if not path: return bottle.HTTPError(404, "File does not exist.") # test last modification date (mostly copied from bottle.py) stats = os.stat(path) lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime)) headers['Last-Modified'] = lm ims = bottle.request.environ.get('HTTP_IF_MODIFIED_SINCE') if ims: ims = bottle.parse_date(ims.split(";")[0].strip()) if ims is not None and ims >= int(stats.st_mtime): headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) return bottle.HTTPResponse(status=304, **bottle.response.headers) # set the content type to javascript headers['Content-Type'] = "application/javascript; charset=UTF-8" body = bottle.template("javascript/%s" % filename) # TODO if you are sadistic enough you can try to minify the content return bottle.HTTPResponse(body, **headers)
def test_rfc850(self): """DateParser: RFC 850 format""" ts = time.time() t = time.gmtime(ts) rs = time.strftime("%%s, %d-%%s-%y %H:%M:%S GMT", t) % (weekday_full[t.tm_wday], month_abbr[t.tm_mon]) self.assertEqual(int(ts), int(parse_date(rs)))
def test_asctime(self): """DateParser: asctime format""" ts = time.time() t = time.gmtime(ts) rs = time.strftime("%%s %%s %d %H:%M:%S %Y", t) % (weekday_abbr[t.tm_wday], month_abbr[t.tm_mon]) self.assertEqual(int(ts), int(parse_date(rs)))
def get_thumbnail(userfolder, file_name): filename = check_path(os.path.join(get_userfolder_path(userfolder), IMG_DIR), file_name) headers = {} stats = os.stat(filename) lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime)) response.set_header('Last-Modified', lm) ims = request.environ.get('HTTP_IF_MODIFIED_SINCE') if ims: ims = parse_date(ims.split(";")[0].strip()) if ims is not None and ims >= int(stats.st_mtime): headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) return HTTPResponse(status=304, **headers) response.content_type = 'image/png' img = Image.open(filename, 'r') img.thumbnail((70, 70), Image.ANTIALIAS) buffered = IO() img.save(buffered, format='PNG') ret = buffered.getvalue() response.set_header('Content-Length', len(ret)) return ret
def handle_zip_directory_listing(zip, archivefile, subarchivepath, format=None): """List contents in a directory. """ # ensure directory has trailing '/' pathname = get_pathname() if not pathname.endswith('/'): parts = request.urlparts new_parts = (parts[0], parts[1], quote(pathname) + '/', parts[3], parts[4]) new_url = urlunsplit(new_parts) return redirect(new_url) from bottle import parse_date, tob headers = {} # check 304 stats = os.stat(archivefile) headers['Last-Modified'] = email.utils.formatdate(stats.st_mtime, usegmt=True) headers['Cache-Control'] = 'no-cache' getenv = request.environ.get etag = '%d:%d:%d:%d:%s' % (stats.st_dev, stats.st_ino, stats.st_mtime, stats.st_size, archivefile) etag = hashlib.sha1(tob(etag)).hexdigest() headers['ETag'] = etag check = getenv('HTTP_IF_NONE_MATCH') if check and check == etag: return HTTPResponse(status=304, **headers) if not check: ims = getenv('HTTP_IF_MODIFIED_SINCE') if ims: ims = parse_date(ims.split(";")[0].strip()) if ims is not None and ims >= int(stats.st_mtime): return HTTPResponse(status=304, **headers) subentries = util.zip_listdir(zip, subarchivepath) try: body = template( 'index.tpl', sitename=runtime['name'], is_local=is_local_access(), base=get_base(), path=request.path, subarchivepath=subarchivepath, subentries=subentries, ) return HTTPResponse(body, **headers) except util.ZipDirNotFoundError: return http_error(404, "File does not exist.", format=format)
def test_expires_header(self): import datetime response = BaseResponse() now = datetime.datetime.now() response.expires = now self.assertEqual(0, int((response.expires - now).total_seconds())) now2 = datetime.datetime.utcfromtimestamp( parse_date(response.headers['Expires'])) self.assertEqual(0, int((now - now2).total_seconds()))
def wrapper(*args, **kwargs): ims = request.environ.get("HTTP_IF_MODIFIED_SINCE") if ims: ims = parse_date(ims.split(";", 1)[0].strip()) if ims is not None and ims > state().last_updated_float: return HTTPResponse( status=304, Date=datetime.utcnow().strftime("%a, %d %b %y %T GMT")) return handler_f(*args, **kwargs)
def data(): resp = "{}" headers = dict() last = 0 sensors = [fetch.SENSOR_1_NAME, fetch.SENSOR_2_NAME] for fn in sensors: lu = fetch.lastupdate(fn) if not lu: continue if not last: last = lu else: last = max(last, lu) lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(last)) headers['Last-Modified'] = lm ims = bottle.request.environ.get('HTTP_IF_MODIFIED_SINCE') if ims: ims = bottle.parse_date(ims.split(";")[0].strip()) if ims is not None and ims >= last: headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) return bottle.HTTPResponse(status=304, **headers) resp = {} # ensure we match readouts for fn in sensors: data = fetch.rrdfetch(fn) if "time" not in resp: resp["time"] = data["time"] resp[fn] = data # does time diverge? time1 = set(resp[sensors[0]]["time"]) time2 = set(resp[sensors[1]]["time"]) for fn in sensors: sensordata = resp[fn] sensordata.pop("time") # Remove outliers for name in sensordata.keys(): data = sensordata[name] if name == "volt": eps = 0.01 else: eps = 2 outliers = dbscan(data, eps, 3) for i in outliers: data[i] = None # todo: time might diverge +/- one tick #if time1 != time2: # print(time1-time2, time2-time1) # return bottle.HTTPResponse(status=500) resp = json.dumps(resp).encode("utf-8") headers['Content-Length'] = len(resp) return bottle.HTTPResponse(resp, **headers)
def test_expires_header(self): import datetime response = BaseResponse() now = datetime.datetime.now() response.expires = now def seconds(a, b): td = max(a,b) - min(a,b) return td.days*360*24 + td.seconds self.assertEqual(0, seconds(response.expires, now)) now2 = datetime.datetime.utcfromtimestamp( parse_date(response.headers['Expires'])) self.assertEqual(0, seconds(now, now2))
def gzip_cache(path): """ Another GZIP handler for Bottle functions. This may be used to cache the files statically on the disc on given `path`. If the browser accepts GZIP and there is file at ``path + ".gz"``, this file is returned, correct headers are set (Content-Encoding, Last-Modified, Content-Length, Date and so on). If the browser doesn't accept GZIP or there is no ``.gz`` file at same path, normal file is returned. Args: path (str): Path to the cached file. Returns: obj: Opened file. """ accept_enc = request.get_header("Accept-Encoding") if accept_enc and "gzip" in accept_enc and os.path.exists(path + ".gz"): path = path + ".gz" response.set_header("Content-Encoding", "gzip") stats = os.stat(path) headers = dict() headers['Content-Length'] = stats.st_size headers['Last-Modified'] = time.strftime( "%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime) ) # I need to set `headers` dict for optional HTTPResponse use, but also set # hedears using `response.set_header()` for normal use for key, val in headers.iteritems(): response.set_header(key, val) modified_since = request.environ.get('HTTP_IF_MODIFIED_SINCE') if modified_since: modified_since = parse_date(modified_since.split(";")[0].strip()) if modified_since is not None and modified_since >= int(stats.st_mtime): headers['Date'] = time.strftime( "%a, %d %b %Y %H:%M:%S GMT", time.gmtime() ) return HTTPResponse(status=304, **headers) return open(path)
def test_expires_header(self): import datetime response = BaseResponse() now = datetime.datetime.now() response.expires = now def seconds(a, b): td = max(a, b) - min(a, b) return td.days * 360 * 24 + td.seconds self.assertEqual(0, seconds(response.expires, now)) now2 = datetime.datetime.utcfromtimestamp( parse_date(response.headers['Expires'])) self.assertEqual(0, seconds(now, now2))
def send_file(content, filename, size, timestamp): """ Send a file represented by file object The code is partly based on ``bottle.static_file``. :param content: file-like object :param filename: filename to use :param size: file size in bytes :param timestamp: file's timestamp seconds since epoch """ headers = {} ctype = get_mimetype(filename) if ctype.startswith('text/'): # We expect and assume all text files are encoded UTF-8. It's # broadcaster's job to ensure this is true. ctype += '; charset=%s' % CHARSET # Set basic headers headers['Content-Type'] = ctype headers['Content-Length'] = size headers['Last-Modified'] = format_ts(timestamp) # Check if If-Modified-Since header is in request and respond early if so modsince = request.environ.get('HTTP_IF_MODIFIED_SINCE') modsince = modsince and parse_date(modsince.split(';')[0].strip()) if modsince is not None and modsince >= timestamp: headers['Date'] = format_ts() return HTTPResponse(status=304, **headers) if request.method == 'HEAD': # Request is a HEAD, so remove any content body content = '' headers['Accept-Ranges'] = 'bytes' ranges = request.environ.get('HTTP_RANGE') if ranges: ranges = list(parse_range_header(ranges, size)) if not ranges: return HTTPError(416, "Request Range Not Satisfiable") start, end = ranges[0] headers['Content-Range'] = 'bytes %d-%d/%d' % (start, end - 1, size) headers['Content-Length'] = str(end - start) content = iter_read_range(content, start, end - start) return HTTPResponse(content, **headers)
def handle_markdown_output(filepath, filename): """Output processed markdown. """ from bottle import parse_date, tob headers = {} # check 304 stats = os.stat(filename) headers['Last-Modified'] = email.utils.formatdate(stats.st_mtime, usegmt=True) getenv = request.environ.get etag = '%d:%d:%d:%d:%s' % (stats.st_dev, stats.st_ino, stats.st_mtime, stats.st_size, filename) etag = hashlib.sha1(tob(etag)).hexdigest() headers['ETag'] = etag check = getenv('HTTP_IF_NONE_MATCH') if check and check == etag: return HTTPResponse(status=304, **headers) if not check: ims = getenv('HTTP_IF_MODIFIED_SINCE') if ims: ims = parse_date(ims.split(";")[0].strip()) if ims is not None and ims >= int(stats.st_mtime): return HTTPResponse(status=304, **headers) # output processed content with open(filename, 'r', encoding='UTF-8') as f: body = f.read() f.close() body = template( 'markdown', sitename=runtime['name'], is_local=is_local_access(), base=get_base(), path=request.path, content=commonmark.commonmark(body), ) return HTTPResponse(body, **headers)
def custom_static_file(filename, root, mimetype='auto', download=False, custom_headers=None): if custom_headers is None: custom_headers = {} root = os.path.abspath(root) + os.sep filename = os.path.abspath(os.path.join(root, filename.strip('/\\'))) header = dict() if not filename.startswith(root): return HTTPError(403, "Access denied.") if not os.path.exists(filename) or not os.path.isfile(filename): return HTTPError(404, "File does not exist.") if not os.access(filename, os.R_OK): return HTTPError(403, "You do not have permission to access this file.") if mimetype == 'auto': mimetype, encoding = mimetypes.guess_type(filename) if mimetype: header['Content-Type'] = mimetype if encoding: header['Content-Encoding'] = encoding elif mimetype: header['Content-Type'] = mimetype if download: download = os.path.basename(filename if download == True else download) header['Content-Disposition'] = 'attachment; filename="%s"' % download stats = os.stat(filename) header['Content-Length'] = stats.st_size lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime)) header['Last-Modified'] = lm ims = request.environ.get('HTTP_IF_MODIFIED_SINCE') if ims: ims = parse_date(ims.split(";")[0].strip()) if ims is not None and ims >= int(stats.st_mtime): header['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) return HTTPResponse(status=304, header=header) if custom_headers: header.update(custom_headers) body = '' if request.method == 'HEAD' else open(filename, 'rb') return HTTPResponse(body, header=header)
def get_item_file(self, id): item = self.lib.get_item(id) if item is None: return HTTPError(404, 'File does not exist.') if not os.access(item.path, os.R_OK): return HTTPError(403, 'You do not have permission to access this file.') stats = os.stat(item.path) headers = { 'Content-Type': 'application/octet-stream', 'Content-Length': stats.st_size, 'Last-Modified': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime)), 'Date': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) } ims = request.environ.get('HTTP_IF_MODIFIED_SINCE') if ims: ims = parse_date(ims.split(";")[0].strip()) if ims is not None and ims >= int(stats.st_mtime): return HTTPResponse(status=304, **headers) body = '' if request.method == 'HEAD' else open(item.path, 'rb') headers["Accept-Ranges"] = "bytes" range_header = request.environ.get('HTTP_RANGE') if range_header: ranges = list(parse_range_header(range_header, stats.st_size)) if not ranges: return HTTPError(416, "Requested Range Not Satisfiable") offset, end = ranges[0] headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end - 1, stats.st_size) headers["Content-Length"] = str(end - offset) if body: body = _file_iter_range(body, offset, end - offset) return HTTPResponse(body, status=206, **headers) return HTTPResponse(body, **headers)
def serve(self, root_path, download=False, charset='UTF-8'): filename = os.path.join(root_path, self.filepath) headers = dict() if not os.path.exists(filename) or not os.path.isfile(filename): return False, 404, "File does not exist." if not os.access(filename, os.R_OK): return False, 403, "You do not have permission to access this file." mimetype, encoding = mimetypes.guess_type(filename) if encoding: headers['Content-Encoding'] = encoding if mimetype: if mimetype[:5] == 'text/' and charset and 'charset' not in mimetype: mimetype += '; charset=%s' % charset headers['Content-Type'] = mimetype if download: download = os.path.basename(filename if download == True else download) headers['Content-Disposition'] = 'attachment; filename="%s"' % download stats = os.stat(filename) headers['Content-Length'] = stats.st_size lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime)) headers['Last-Modified'] = lm ims = self.environ.get('HTTP_IF_MODIFIED_SINCE') if ims: ims = bottle.parse_date(ims.split(";")[0].strip()) if ims is not None and ims >= int(stats.st_mtime): headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) return True, '', 304, headers if self.method == 'HEAD': body = '' else: with open(filename, 'rb') as f: body = f.read() return True, body, None, headers
def serve_picture(): renew_picture() _picture = picture # from bottle.static_file headers = dict() headers['Content-Type'] = 'image/jpeg' headers['Content-Length'] = clen = len(_picture.content) lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(_picture.time)) headers['Last-Modified'] = lm ims = request.get_header('If-Modified-Since') if not ims: ims = request.environ.get('HTTP_IF_MODIFIED_SINCE') if ims: ims = parse_date(ims.split(";")[0].strip()) if ims is not None and ims >= int(_picture.time): headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) return HTTPResponse(status=304, **headers) body = '' if request.method == 'HEAD' else _picture.content return HTTPResponse(body, **headers)
def static_file(filename, root, mimetype="auto", download=False, charset="UTF-8"): """ Open a file in a safe way and return :exc:`HTTPResponse` with status code 200, 305, 403 or 404. The ``Content-Type``, ``Content-Encoding``, ``Content-Length`` and ``Last-Modified`` headers are set if possible. Special support for ``If-Modified-Since``, ``Range`` and ``HEAD`` requests. :param filename: Name or path of the file to send. :param root: Root path for file lookups. Should be an absolute directory path. :param mimetype: Defines the content-type header (default: guess from file extension) :param download: If True, ask the browser to open a `Save as...` dialog instead of opening the file with the associated program. You can specify a custom filename as a string. If not specified, the original filename is used (default: False). :param charset: The charset to use for files with a ``text/*`` mime-type. (default: UTF-8) """ root = os.path.abspath(root) + os.sep filename = os.path.abspath(os.path.join(root, filename.strip("/\\"))) headers = dict() if not filename.startswith(root): return HTTPError(403, "Access denied.") if not os.path.exists(filename) or not os.path.isfile(filename): return HTTPError(404, "File does not exist.") if not os.access(filename, os.R_OK): return HTTPError(403, "You do not have permission to access this file.") if mimetype == "auto": mimetype, encoding = mimetypes.guess_type(filename) if encoding: headers["Content-Encoding"] = encoding if mimetype: if mimetype[:5] == "text/" and charset and "charset" not in mimetype: mimetype += "; charset=%s" % charset headers["Content-Type"] = mimetype if download: download = os.path.basename(filename if download == True else download) headers["Content-Disposition"] = 'attachment; filename="%s"' % download stats = os.stat(filename) headers["Content-Length"] = clen = stats.st_size lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime)) headers["Last-Modified"] = lm ### Added here: headers["Cache-Control"] = "max-age=3600, public" ims = request.environ.get("HTTP_IF_MODIFIED_SINCE") if ims: ims = parse_date(ims.split(";")[0].strip()) if ims is not None and ims >= int(stats.st_mtime): headers["Date"] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) return HTTPResponse(status=304, **headers) body = "" if request.method == "HEAD" else open(filename, "rb") headers["Accept-Ranges"] = "bytes" ranges = request.environ.get("HTTP_RANGE") if "HTTP_RANGE" in request.environ: ranges = list(parse_range_header(request.environ["HTTP_RANGE"], clen)) if not ranges: return HTTPError(416, "Requested Range Not Satisfiable") offset, end = ranges[0] headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end - 1, clen) headers["Content-Length"] = str(end - offset) if body: body = _file_iter_range(body, offset, end - offset) return HTTPResponse(body, status=206, **headers) return HTTPResponse(body, **headers)
def send_fileobj(filename, root, guessmime=True, mimetype=None, download=False): """ replacement for bottle's static_file with more support for sending string, or file object like StringIO special keyword for root : ':string', ':file' when passing other data through filename """ header = dict() if root in (':string',':file') : fileobj = filename elif root==':json' : fileobj = bottle.json_dumps(filename) mimetype = 'application/json' if fileobj : filename = download if isinstance(download,basestring) else 'noname' else : root = os.path.abspath(root) + os.sep filename = os.path.abspath(os.path.join(root, filename.strip('/\\'))) if not filename.startswith(root): return bottle.HTTPError(403, "Access denied.") if not os.path.exists(filename) or not os.path.isfile(filename): return bottle.HTTPError(404, "File does not exist.") if not os.access(filename, os.R_OK): return bottle.HTTPError(403, "You do not have permission to access this file.") if not mimetype and guessmime: header['Content-Type'] = bottle.mimetypes.guess_type(filename)[0] else: header['Content-Type'] = mimetype if mimetype else 'text/plain' if download == True: download = os.path.basename(filename) if download: header['Content-Disposition'] = 'attachment; filename="%s"' % download if fileobj: l = None if isinstance(fileobj,basestring): l = fileobj.__sizeof__() - type(fileobj)().__sizeof__() elif hasattr(fileobj,'len') : l = fileobj.len elif hasattr(fileobj,'tell') and hasattr(fileobj,'seek') : old = fileobj.tell() fileobj.seek(0,2) l = fileobj.tell() fileobj.seek(old) if l : header['Content-Length'] = l else: stats = os.stat(filename) lm = bottle.time.strftime("%a, %d %b %Y %H:%M:%S GMT", bottle.time.gmtime(stats.st_mtime)) header['Last-Modified'] = lm ims = bottle.request.environ.get('HTTP_IF_MODIFIED_SINCE') if ims: ims = ims.split(";")[0].strip() # IE sends "<date>; length=146" ims = bottle.parse_date(ims) if ims is not None and ims >= int(stats.st_mtime): header['Date'] = bottle.time.strftime("%a, %d %b %Y %H:%M:%S GMT", bottle.time.gmtime()) return bottle.HTTPResponse(status=304, header=header) header['Content-Length'] = stats.st_size if bottle.request.method == 'HEAD': return bottle.HTTPResponse('', header=header) if not fileobj : fileobj = open(filename, 'rb') return bottle.HTTPResponse(fileobj, header=header)
def test_rfc1123(self): """DateParser: RFC 1123 format""" ts = time.time() rs = bottle.http_date(ts) self.assertEqual(int(ts), int(parse_date(rs)))
def test_rfc1123(self): """DateParser: RFC 1123 format""" ts = time.time() rs = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(ts)) self.assertEqual(int(ts), int(parse_date(rs)))
def test_asctime(self): """DateParser: asctime format""" ts = time.time() rs = time.strftime("%a %b %d %H:%M:%S %Y", time.gmtime(ts)) self.assertEqual(int(ts), int(parse_date(rs)))
def test_bad(self): """DateParser: Bad format""" self.assertEqual(None, parse_date('Bad 123'))
def send_file(fd, filename=None, size=None, timestamp=None, ctype=None, charset=CHARSET, attachment=False, wrapper=DEFAULT_WRAPPER): """ Send a file represented by file object This function constcuts a HTTPResponse object that uses a file descriptor as response body. The file descriptor is suppled as ``fd`` argument and it must have a ``read()`` method. ``ValueError`` is raised when this is not the case. It supports `byte serving`_ using Range header, and makes the best effort to set all appropriate headers. It also supports HEAD queries. Because we are dealing with file descriptors and not physical files, the user must also supply the file metadata such as filename, size, and timestamp. The ``filename`` argument is an arbitrary filename. It is used to guess the content type, and also to set the content disposition in case of attachments. The ``size`` argument is the payload size in bytes. If it is omitted, the content length header is not set, and byte serving does not work. The ``timestamp`` argument is the number of seconds since Unix epoch when the file was created or last modified. If this argument is omitted, If-Modified-Since request headers cannot be honored. To explicitly specify the content type, the ``ctype`` argument can be used. This should be a valid MIME type of the payload. Default encoding (used as charset parameter in Content-Type header) is 'UTF-8'. This can be overridden by using the ``charset`` argument. The ``attachment`` argumnet can be set to ``True`` to add the Content-Dispositon response header. Value of the header is then set to the filename. The ``wrapper`` argument is used to wrap the file descriptor when doing byte serving. The default is to use ``fdsend.rangewrapper.RangeWrapper`` class, but there are alternatives as ``fdsend.rangewrapper.range_iter`` and ``bottle._file_iter_range``. The wrappers provided by this package are written to specifically handle file handles that do not have a ``seek()`` method. If this is not your case, you may safely use the bottle's wrapper. The primary difference between ``fdsend.rangewrapper.RangeWrapper`` and ``fdsend.rangewrapper.range_iter`` is that the former returns a file-like object with ``read()`` method, which may or may not increase performance when used on a WSGI server that supports ``wsgi.file_wrapper`` feature. The latter returns an iterator and the response is returned as is without the use of a ``file_wrapper``. This may have some benefits when it comes to memory usage. Benchmarking and profiling is the best way to determine which wrapper you want to use, or you need to implement your own. To implement your own wrapper, you need to create a callable or a class that takes the following arguments: - file descriptor - offset (in bytes from start of the file) - length (total number of bytes in the range) The return value of the wrapper must be either an iterable or file-like object that implements ``read()`` and ``close()`` methods with the usual semantics. The code is partly based on ``bottle.static_file``. .. _byte serving: https://tools.ietf.org/html/rfc2616#page-138 """ if not hasattr(fd, 'read'): raise ValueError("Object '{}' has no read() method".format(fd)) headers = {} status = 200 if not ctype and filename is not None: ctype, enc = mimetypes.guess_type(filename) if enc: headers['Content-Encoding'] = enc if ctype: if ctype.startswith('text/'): # We expect and assume all text files are encoded UTF-8. It's # broadcaster's job to ensure this is true. ctype += '; charset=%s' % charset headers['Content-Type'] = ctype if size: headers['Content-Length'] = size headers['Accept-Ranges'] = 'bytes' if timestamp: headers['Last-Modified'] = format_ts(timestamp) # Check if If-Modified-Since header is in request and respond early modsince = request.environ.get('HTTP_IF_MODIFIED_SINCE') print(modsince) modsince = modsince and parse_date(modsince.split(';')[0].strip()) if modsince is not None and modsince >= timestamp: headers['Date'] = format_ts() return HTTPResponse(status=304, **headers) if attachment and filename: headers['Content-Disposition'] = 'attachment; filename="%s"' % filename if request.method == 'HEAD': # Request is a HEAD, so remove any fd body fd = '' ranges = request.environ.get('HTTP_RANGE') if size and ranges: ranges = list(parse_range_header(ranges, size)) if not ranges: return HTTPError(416, 'Request Range Not Satisfiable') start, end = ranges[0] headers['Content-Range'] = 'bytes %d-%d/%d' % (start, end - 1, size) length = end - start headers['Content-Length'] = str(length) fd = wrapper(fd, start, length) status = 206 return HTTPResponse(fd, status=status, **headers)
def test_rfc850(self): """DateParser: RFC 850 format""" ts = time.time() rs = time.strftime("%A, %d-%b-%y %H:%M:%S GMT", time.gmtime(ts)) self.assertEqual(int(ts), int(parse_date(rs)))
def static_file(filename, root, mimetype=True, download=False, charset='UTF-8', etag=None): """ Open a file in a safe way and return an instance of :exc:`HTTPResponse` that can be sent back to the client. :param filename: Name or path of the file to send, relative to ``root``. :param root: Root path for file lookups. Should be an absolute directory path. :param mimetype: Provide the content-type header (default: guess from file extension) :param download: If True, ask the browser to open a `Save as...` dialog instead of opening the file with the associated program. You can specify a custom filename as a string. If not specified, the original filename is used (default: False). :param charset: The charset for files with a ``text/*`` mime-type. (default: UTF-8) :param etag: Provide a pre-computed ETag header. If set to ``False``, ETag handling is disabled. (default: auto-generate ETag header) While checking user input is always a good idea, this function provides additional protection against malicious ``filename`` parameters from breaking out of the ``root`` directory and leaking sensitive information to an attacker. Read-protected files or files outside of the ``root`` directory are answered with ``403 Access Denied``. Missing files result in a ``404 Not Found`` response. Conditional requests (``If-Modified-Since``, ``If-None-Match``) are answered with ``304 Not Modified`` whenever possible. ``HEAD`` and ``Range`` requests (used by download managers to check or continue partial downloads) are also handled automatically. """ root = os.path.join(os.path.abspath(root), '') filename = os.path.abspath(os.path.join(root, filename.strip('/\\'))) headers = dict() if not filename.startswith(root): return HTTPError(403, "Access denied.") if not os.path.exists(filename) or not os.path.isfile(filename): return HTTPError(404, "File does not exist.") if not os.access(filename, os.R_OK): return HTTPError( 403, "You do not have permission to access this file.") if mimetype is True: if download and download is not True: mimetype, encoding = mimetypes.guess_type(download) else: mimetype, encoding = mimetypes.guess_type(filename) if encoding: headers['Content-Encoding'] = encoding if mimetype: if (mimetype[:5] == 'text/' or mimetype == 'application/javascript')\ and charset and 'charset' not in mimetype: mimetype += '; charset=%s' % charset headers['Content-Type'] = mimetype if download: download = os.path.basename( filename if download is True else download) headers[ 'Content-Disposition'] = 'attachment; filename="%s"' % download stats = os.stat(filename) headers['Content-Length'] = clen = stats.st_size headers['Last-Modified'] = email.utils.formatdate(stats.st_mtime, usegmt=True) headers['Date'] = email.utils.formatdate(time.time(), usegmt=True) getenv = request.environ.get if etag is None: etag = '%d:%d:%d:%d:%s' % (stats.st_dev, stats.st_ino, stats.st_mtime, clen, filename) etag = hashlib.sha1(tob(etag)).hexdigest() if etag: headers['ETag'] = etag check = getenv('HTTP_IF_NONE_MATCH') if check and check == etag: return HTTPResponse(status=304, **headers) if not (etag and check): ims = getenv('HTTP_IF_MODIFIED_SINCE') if ims: ims = parse_date(ims.split(";")[0].strip()) if ims is not None and ims >= int(stats.st_mtime): return HTTPResponse(status=304, **headers) body = '' if request.method == 'HEAD' else open(filename, 'rb') headers["Accept-Ranges"] = "bytes" range_header = getenv('HTTP_RANGE') if range_header: ranges = list(parse_range_header(range_header, clen)) if not ranges: return HTTPError(416, "Requested Range Not Satisfiable") offset, end = ranges[0] headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end - 1, clen) headers["Content-Length"] = str(end - offset) if body: body = _file_iter_range(body, offset, end - offset) return HTTPResponse(body, status=206, **headers) return HTTPResponse(body, **headers)
def handle_subarchive_path(archivefile, subarchivepath, mimetype, encoding, download=False, charset='UTF-8', etag=None, format=None): """Show content of a path in a zip file. """ from bottle import parse_range_header, parse_date, _file_iter_range, tob if not os.access(archivefile, os.R_OK): return http_error(403, "You do not have permission to access this file.", format=format) try: zip = zipfile.ZipFile(archivefile) except: return http_error(500, "Unable to open the ZIP file.", format=format) try: # KeyError is raised if subarchivepath does not exist info = zip.getinfo(subarchivepath) except KeyError: # subarchivepath does not exist # possibility a missing directory entry? return handle_zip_directory_listing(zip, archivefile, subarchivepath) fh = zip.open(subarchivepath, 'r') headers = dict() if encoding: headers['Content-Encoding'] = encoding if mimetype is True: if download and download is not True: mimetype, encoding = mimetypes.guess_type(download) else: mimetype, encoding = mimetypes.guess_type(subarchivepath) if encoding: headers['Content-Encoding'] = encoding if mimetype: if (mimetype[:5] == 'text/' or mimetype == 'application/javascript')\ and charset and 'charset' not in mimetype: mimetype += '; charset=%s' % charset headers['Content-Type'] = mimetype if download: download = os.path.basename( subarchivepath if download is True else download) headers['Content-Disposition'] = 'attachment; filename="%s"' % download headers['Content-Length'] = clen = info.file_size lm = info.date_time epoch = int( time.mktime((lm[0], lm[1], lm[2], lm[3], lm[4], lm[5], 0, 0, -1))) headers['Last-Modified'] = email.utils.formatdate(epoch, usegmt=True) headers['Date'] = email.utils.formatdate(time.time(), usegmt=True) getenv = request.environ.get if etag is None: etag = '%d:%d:%s' % (epoch, clen, subarchivepath) etag = hashlib.sha1(tob(etag)).hexdigest() if etag: headers['ETag'] = etag check = getenv('HTTP_IF_NONE_MATCH') if check and check == etag: return HTTPResponse(status=304, **headers) if not (etag and check): ims = getenv('HTTP_IF_MODIFIED_SINCE') if ims: ims = parse_date(ims.split(";")[0].strip()) if ims is not None and ims >= int(epoch): return HTTPResponse(status=304, **headers) body = '' if request.method == 'HEAD' else fh headers["Accept-Ranges"] = "bytes" range_header = getenv('HTTP_RANGE') if range_header: ranges = list(parse_range_header(range_header, clen)) if not ranges: return http_error(416, "Requested Range Not Satisfiable") offset, end = ranges[0] headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end - 1, clen) headers["Content-Length"] = str(end - offset) if body: body = _file_iter_range(body, offset, end - offset) return HTTPResponse(body, status=206, **headers) return HTTPResponse(body, **headers)
def last_mod_header(self, lm): self.lm = lm self.lm_epoch = bottle.parse_date(lm) or 0
def post_cc_data(self, session): headers = dict() # data shall always send 'If-Modified-Since' header ... ims = bottle.request.environ.get('HTTP_IF_MODIFIED_SINCE') headers['Last-Modified'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(time.time())) headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) if ims: ims = bottle.parse_date(ims.split(";")[0].strip()) #print(session['node']) # print(session.id + ' / position -> ') try: node = self.nodes[session['node']] except KeyError: self.sessions.delete_session(session) raise bottle.HTTPError(400) status = session['status'] # print(status) if status != 'cc_ok': # if node.config.connect is False: # return bottle.HTTPResponse(202) # Accepted # This raises in case of issues self.connect_card_to_node(session, node) # Being here there's a valid connection! session['status'] = 'cc_ok' if node.controller and not node.controller.is_alive(): # Try to reconnect - once! node.controller.reconnect() # This is an issue... if not node.controller or not node.controller.is_alive(): raise bottle.HTTPError(404) # ... ret = {} # create an uuid unique to a fp # to allow the cc client to distinguish cards for cumulation fp = None with contextlib.suppress(Exception): fp = node.controller.fingerprint if fp is not None and len(fp) == 40: if fp not in self.fingerprints: self.fingerprints[fp] = uuid.uuid4().hex ret['representing'] = self.fingerprints[fp] # ret['style'] = 'readonly' ret['dd'] = '' ret['label'] = node.nickname ret['version'] = node.controller.version_short # if ims and ims < self.vm.Tor.last_modified: ret['latest'] = self.version.Tor.stable # ret['latest'] = '0.4.0.6' ret['versionflag'] = node.controller.version_current node_mode = 'Client' if node.controller.get_conf('BridgeRelay', None) == '1': node_mode = 'Bridge' elif node.controller.get_conf('ORPort', None): node_mode = 'Relay' ret['mode'] = node_mode if True == True: # the first flag is always the placeholder for the nodes mode data # ret['flags'] = ['mode'] ret['flags'] = [] if node_mode == 'Bridge': # node.controller.flags fails for a bridge! try: oo = node.onionoo d = oo.details('flags') ret['flags'].extend(d) except: pass else: f = node.controller.flags if f is not None and len(f) > 0 and f[0] != 'unknown': ret['flags'].extend(f) # We add an icon in case of Hibernation! try: accs = node.controller.get_accounting_stats() except: pass else: if accs.status != "awake": ret['flags'].append(accs.status) else: ret['flags'] = [ 'mode', 'Authority', 'BadExit', 'BadDirectory', 'Exit', 'Fast', 'Guard', 'HSDir', 'Named', 'Stable', 'Running', 'Unnamed', 'Valid', 'V2Dir', 'V3Dir', 'soft', 'hard', 'unknown' ] ret['details'] = True last_ts = session['lastTS'] rv = node.bandwidth.get_data(interval='1s', since_timestamp=last_ts) # print(len(rv)) if len(rv) > 0: ret['bw'] = rv session['lastTS'] = time.time() * 1000 # Connection conn = '' conn += 'h' if node.config.is_default_node else '' conn += 'p' if node.controller.auth_password else '' conn += 'c' if node.controller.with_cookie else '' conn += 'x' if node.controller.via_proxy else '' ret['conn'] = conn # set the headers for header, value in headers.items(): # print(header, value) bottle.response.set_header(header, value) return json.JSONEncoder().encode(ret)
def send_file(content, filename, size=None, timestamp=None): """ Convert file data into an HTTP response. This method is used when the file data does not exist on disk, such as when it is dynamically generated. Because the file does not exist on disk, the basic metadata which is usually read from the file itself must be supplied as arguments. The ``filename`` argument is the supposed filename of the file data. It is only used to set the Content-Type header, and you may safely pass in just the extension with leading period. The ``size`` argument is the payload size in bytes. For streaming files, this can be particularly important as the ranges are calculated baed on content length. If ``size`` is omitted, then support for ranges is not advertise and ranges are never returned. ``timestamp`` is expected to be in seconds since UNIX epoch, and is used to calculate Last-Modified HTTP headers, as well as handle If-Modified-Since header. If omitted, current time is used, and If-Modified-Since is never checked. .. note:: The returned response is a completely new response object. Modifying the reponse object in the current request context is not going to affect the object returned by this function. You should modify the object returned by this function instead. Example:: def some_handler(): import StringIO f = StringIO.StringIO('foo') return send_file(f, 'file.txt', 3, 1293281312) The code is partly based on ``bottle.static_file``, with the main difference being the use of file-like objects instead of files on disk. """ headers = {} ctype = get_mimetype(filename) if ctype.startswith('text/'): # We expect and assume all text files are encoded UTF-8. It's # user's job to ensure this is true. ctype += '; charset=UTF-8' # Set basic headers headers['Content-Type'] = ctype if size: headers['Content-Length'] = size headers['Last-Modified'] = format_ts(timestamp) # Check if If-Modified-Since header is in request and respond early if so if timestamp: modsince = request.environ.get('HTTP_IF_MODIFIED_SINCE') modsince = modsince and parse_date(modsince.split(';')[0].strip()) if modsince is not None and modsince >= timestamp: headers['Date'] = format_ts() return HTTPResponse(status=304, **headers) if request.method == 'HEAD': # Request is a HEAD, so remove any content body content = '' if size: headers['Accept-Ranges'] = 'bytes' ranges = request.environ.get('HTTP_RANGE') if ranges and size: ranges = list(parse_range_header(ranges, size)) if not ranges: return HTTPError(416, "Request Range Not Satisfiable") start, end = ranges[0] headers['Content-Range'] = 'bytes %d-%d/%d' % (start, end - 1, size) headers['Content-Length'] = str(end - start) content = iter_read_range(content, start, end - start) return HTTPResponse(content, **headers)
def git_static_file(filename, mimetype='auto', download=False, charset='UTF-8'): """ This method is derived from bottle.static_file: Open [a file] and return :exc:`HTTPResponse` with status code 200, 305, 403 or 404. The ``Content-Type``, ``Content-Encoding``, ``Content-Length`` and ``Last-Modified`` headers are set if possible. Special support for ``If-Modified-Since`` [...]. :param filename: Name or path of the file to send. :param mimetype: Defines the content-type header (default: guess from file extension) :param download: If True, ask the browser to open a `Save as...` dialog instead of opening the file with the associated program. You can specify a custom filename as a string. If not specified, the original filename is used (default: False). :param charset: The charset to use for files with a ``text/*`` mime-type. (default: UTF-8) """ # root = os.path.abspath(root) + os.sep # filename = os.path.abspath(pathjoin(root, filename.strip('/\\'))) filename = filename.strip('/\\') headers = dict() FS = request.app.config['pgs.FS'] # if not filename.startswith(root): # return HTTPError(403, "Access denied.") if not FS.exists(filename): return HTTPError(404, "Not found.") # if not os.access(filename, os.R_OK): # return HTTPError(403, "You do not have permission to access this file.") if mimetype == 'auto': if download and download is not True: mimetype, encoding = mimetypes.guess_type(download) else: mimetype, encoding = mimetypes.guess_type(filename) if encoding: headers['Content-Encoding'] = encoding if mimetype: if mimetype[:5] == 'text/' and charset and 'charset' not in mimetype: mimetype += '; charset=%s' % charset headers['Content-Type'] = mimetype if download: download = os.path.basename(filename if download else download) headers['Content-Disposition'] = 'attachment; filename="%s"' % download # stats = os.stat(filename) info = FS.getinfo(filename) headers['Content-Length'] = clen = info['size'] lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(info['modified_time'])) headers['Last-Modified'] = lm ims = request.environ.get('HTTP_IF_MODIFIED_SINCE') if ims: ims = parse_date(ims.split(";")[0].strip()) mtime = info['modified_time'] if mtime and ims is not None and ims >= int(mtime): headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) return HTTPResponse(status=304, **headers) body = '' if request.method == 'HEAD' else FS.get_fileobj(filename) clen # headers["Accept-Ranges"] = "bytes" # ranges = request.environ.get('HTTP_RANGE') # if 'HTTP_RANGE' in request.environ: # ranges = list(parse_range_header(request.environ['HTTP_RANGE'], clen)) # if not ranges: # return HTTPError(416, "Requested Range Not Satisfiable") # offset, end = ranges[0] # headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end - 1, clen) # headers["Content-Length"] = str(end - offset) # if body: body = _file_iter_range(body, offset, end - offset) # return HTTPResponse(body, status=206, **headers) return HTTPResponse(body, **headers)
def post_cc_ping(self, session): headers = { 'Last-Modified': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(self.cc.last_modified)), 'Date': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) } # ping shall always send 'If-Modified-Since' header ... ims = bottle.request.environ.get('HTTP_IF_MODIFIED_SINCE', None) if ims is None: # As said: ping shall always send I-M-S return bottle.HTTPError(400) ims = bottle.parse_date(ims.split(";")[0].strip()) if ims >= int(self.cc.last_modified): return bottle.HTTPResponse(status=304, **headers) # set the headers for header, value in headers.items(): # print(header, value) bottle.response.set_header(header, value) # ['cards'] holds the session.id's for the cards of this session if session['cards'] is None: # First Card = DefaultNode card = self.sessions.create_session(bottle.request, 'cc_new') card['node'] = 'theonionbox' session['cards'] = [card.id] # if the frame (cc) session knows a password, this is the one for the default node! card['password'] = session.get('password', None) cards = [session['cards'][0]] # walk through the sections defined in the cc configuration file for section in self.cc: # The session = card representing this section card = None # the card's node node = None # check if there's a card = session, that holds a node with the name of this configuration section for card_id in session['cards']: s = self.sessions.get_session(card_id, bottle.request) if s is not None: # If s IS None, the session is expired! node = self.nodes[s['node']] if section.name == node.config.name: card = s break if card is not None: # if the config changed... if int(section.last_modified) > ims: # disco the node node = self.nodes[card['node']] node.disconnect() # and delete the session = card self.sessions.delete_session(card) card = None # card will now be recreated & node reconnected # If there's None, create a new session = a new card if card is None: # get the node representing this section node = self.nodes.get_name(section.name) # if node is None: create it from section! if node is None: id = self.nodes.node_add(section) node = self.nodes.get(id) # then create a new session & connect both if node is not None: card = self.sessions.create_session( bottle.request, 'cc_new') if card is not None: card['node'] = node.id card['controlled_by'] = session.id card['password'] = section.password # session['cards'].append(card.id) if card is not None: cards.append(card.id) # This eliminates all expired sessions from ['cards'] session['cards'] = cards # Now json everything... and return it! return json.JSONEncoder().encode({'cards': cards})