def test_dump_options_header(self): assert http.dump_options_header("foo", {"bar": 42}) == "foo; bar=42" assert http.dump_options_header("foo", { "bar": 42, "fizz": None }) in ( "foo; bar=42; fizz", "foo; fizz; bar=42", )
def _set_charset(self, charset): header = self.headers.get('content-type') ct, options = parse_options_header(header) if not ct: raise TypeError('Cannot set charset if Content-Type header is missing.') options['charset'] = charset self.headers['Content-Type'] = dump_options_header(ct, options)
def _set_charset(self, charset): header = self.headers.get("content-type") ct, options = parse_options_header(header) if not ct: raise TypeError("Cannot set charset if Content-Type " "header is missing.") options["charset"] = charset self.headers["Content-Type"] = dump_options_header(ct, options)
def _set_charset(self, charset): # noinspection PyUnresolvedReferences header = self.headers.get('content-type') ct, options = parse_options_header(header) if not ct: raise TypeError('Cannot set charset if Content-Type ' 'header is missing.') options['charset'] = charset self.headers['Content-Type'] = dump_options_header(ct, options)
def _set_charset(self, charset): warnings.warn( "'werkzeug.contrib.wrappers.DynamicCharsetResponseMixin'" " is deprecated as of version 0.15 and will be removed in" " version 1.0.", DeprecationWarning, stacklevel=2, ) header = self.headers.get('content-type') ct, options = parse_options_header(header) if not ct: raise TypeError('Cannot set charset if Content-Type ' 'header is missing.') options['charset'] = charset self.headers['Content-Type'] = dump_options_header(ct, options)
def on_update(d): self.headers["Content-Type"] = dump_options_header( self.mimetype, d)
def on_update(d: CallbackDict) -> None: self.headers["Content-Type"] = dump_options_header(self.mimetype, d)
def _options_header_vkw(value, kw): if not kw: return value return dump_options_header(value, dict(((k.replace('_', '-'), v) for k, v in kw.items())))
def test_dump_options_header(self): assert http.dump_options_header('foo', {'bar': 42}) == \ 'foo; bar=42' assert http.dump_options_header('foo', {'bar': 42, 'fizz': None}) in \ ('foo; bar=42; fizz', 'foo; fizz; bar=42')
def _on_update(value: Dict[str, Any]) -> None: self.headers["Content-Type"] = dump_options_header( self.mimetype, value)
def handle_request(filepath=''): """Handle an HTTP request (HEAD, GET, POST). """ # replace SCRIPT_NAME with the custom if set if config['app']['base']: request.environ['SCRIPT_NAME'] = config['app']['base'] query = request.values action = query.get('a', default='view') action = query.get('action', default=action) format = query.get('f') format = query.get('format', default=format) # handle authorization auth_result = handle_authorization(action=action, format=format) if auth_result is not None: return auth_result # determine primary variables # # filepath: the URL path below app base (not percent encoded) # localpath: the file system path corresponding to filepath # localtargetpath: localpath with symbolic link resolved # mimetype: the mimetype from localtargetpath # archivefile: the file system path of the ZIP archive file, or None # subarchivepath: the URL path below archivefile (not percent encoded) localpath = os.path.abspath( os.path.join(runtime['root'], filepath.strip('/\\'))) localtargetpath = os.path.realpath(localpath) archivefile, subarchivepath = get_archive_path(filepath, localpath) mimetype, _ = mimetypes.guess_type(localtargetpath) # handle action if action == 'static': if format: return http_error(400, "Action not supported.", format=format) for i in runtime['statics']: f = os.path.join(i, filepath) if os.path.lexists(f): return static_file(filepath, root=i) else: return http_error(404) elif action == 'source': if format: return http_error(400, "Action not supported.", format=format) if archivefile: response = handle_subarchive_path( os.path.realpath(archivefile), subarchivepath, mimetype, list_directory=False) else: response = static_file(filepath, root=runtime['root'], mimetype=mimetype) # show as inline plain text # @TODO: Chromium (80) seems to ignore header mimetype for certain types # like image and zip encoding = query.get('e', 'utf-8') encoding = query.get('encoding', default=encoding) response.headers.set('Content-Type', 'text/plain; charset=' + quote(encoding)) response.headers.set('Content-Disposition', 'inline') return response elif action in ('exec', 'browse'): if not is_local_access(): return http_error(400, "Command can only run on local device.", format=format) if not os.path.lexists(localpath): return http_error(404, "File does not exist.", format=format) if action == 'browse': util.view_in_explorer(localpath) elif action == 'exec': util.launch(localpath) if format: return http_response('Command run successfully.', format=format) return http_response(status=204) elif action == 'token': return http_response(token_handler.acquire(), format=format) elif action == 'list': if not format: return http_error(400, "Action not supported.", format=format) if os.path.isdir(localpath): recursive = query.get('recursive', type=bool) return handle_directory_listing(localtargetpath, recursive=recursive, format=format) return http_error(400, "This is not a directory.", format=format) elif action == 'config': if not format: return http_error(400, "Action not supported.", format=format) data = config.dump_object() # filter values for better security data = {k: v for k, v in data.items() if k in ('app', 'book')} data['app'] = { k: v for k, v in data['app'].items() if k in ('name', 'theme') } # add and rewrite values for client to better know the server data['app']['base'] = request.script_root data['app']['is_local'] = is_local_access() data['VERSION'] = __version__ data['WSB_DIR'] = WSB_DIR data['WSB_LOCAL_CONFIG'] = WSB_LOCAL_CONFIG return http_response(data, format=format) elif action == 'edit': if format: return http_error(400, "Action not supported.", format=format) if os.path.lexists(localpath) and not os.path.isfile(localpath): return http_error(400, "Found a non-file here.", format=format) if archivefile: with zipfile.ZipFile(archivefile, 'r') as zip: try: info = zip.getinfo(subarchivepath) except: body = b'' else: body = zip.read(info) else: try: with open(localpath, 'rb') as f: body = f.read() f.close() except FileNotFoundError: body = b'' encoding = query.get('e') encoding = query.get('encoding', default=encoding) try: body = body.decode(encoding or 'UTF-8') except (LookupError, UnicodeDecodeError): encoding = 'ISO-8859-1' body = body.decode(encoding) body = render_template( 'edit.html', sitename=runtime['name'], is_local=is_local_access(), base=request.script_root, path=request.path, body=body, encoding=encoding, ) return http_response(body, format=format) elif action == 'editx': if format: return http_error(400, "Action not supported.", format=format) if os.path.lexists(localpath) and not os.path.isfile(localpath): return http_error(400, "Found a non-file here.", format=format) if not mimetype in ("text/html", "application/xhtml+xml"): return http_error(400, "This is not an HTML file.", format=format) if archivefile: with zipfile.ZipFile(archivefile, 'r') as zip: try: info = zip.getinfo(subarchivepath) except: return http_error(404, "File does not exist.", format=format) else: if not os.path.lexists(localpath): return http_error(404, "File does not exist.", format=format) body = render_template( 'editx.html', sitename=runtime['name'], is_local=is_local_access(), base=request.script_root, path=request.path, ) return http_response(body, format=format) elif action in ('lock', 'unlock', 'mkdir', 'save', 'delete', 'move', 'copy'): if request.method != 'POST': headers = { 'Allow': 'POST', } return http_error(405, 'Method "{}" not allowed.'.format( request.method), format=format, headers=headers) # validate and revoke token token = query.get('token') or '' if not token_handler.validate(token): return http_error(400, 'Invalid access token.', format=format) token_handler.delete(token) # validate localpath if action not in ('lock', 'unlock'): if os.path.abspath(localpath) == runtime['root']: return http_error(403, "Unable to operate the root directory.", format=format) # validate targetpath if action in ('lock', 'unlock'): name = query.get('name') if name is None: return http_error(400, "Lock name is not specified.", format=format) targetpath = os.path.join(runtime['locks'], name) if not targetpath.startswith(os.path.join( runtime['locks'], '')): return http_error(400, 'Invalid lock name "{}".'.format(name), format=format) # handle action # action lock # name: name of the lock file. # chkt: recheck until the lock file not exist or fail out when time out. # chks: how long to treat the lock file as stale. if action == 'lock': check_stale = query.get('chks', 300, type=int) check_timeout = query.get('chkt', 5, type=int) check_expire = time.time() + check_timeout check_delta = min(check_timeout, 0.1) while True: try: os.makedirs(targetpath) except FileExistsError: t = time.time() if t >= check_expire or not os.path.isdir(targetpath): return http_error( 500, 'Unable to acquire lock "{}".'.format(name), format=format) try: lock_expire = os.stat( targetpath).st_mtime + check_stale except FileNotFoundError: # Lock removed by another process during the short interval. # Try acquire again. continue if t >= lock_expire: # Lock expired. Touch rather than remove and make for atomicity. try: Path(targetpath).touch() except: traceback.print_exc() return http_error( 500, 'Unable to regenerate stale lock "{}".'. format(name), format=format) else: break time.sleep(check_delta) except: traceback.print_exc() return http_error( 500, 'Unable to create lock "{}".'.format(name), format=format) else: break elif action == 'unlock': try: os.rmdir(targetpath) except FileNotFoundError: pass except: traceback.print_exc() return http_error( 500, 'Unable to remove lock "{}".'.format(name), format=format) elif action == 'mkdir': if os.path.lexists(localpath) and not os.path.isdir(localpath): return http_error(400, "Found a non-directory here.", format=format) if archivefile: try: zip = zipfile.ZipFile(archivefile, 'a') subarchivepath = subarchivepath + '/' try: info = zip.getinfo(subarchivepath) except KeyError: # subarchivepath does not exist info = zipfile.ZipInfo(subarchivepath, time.localtime()) zip.writestr(info, b'', compress_type=zipfile.ZIP_STORED) except: traceback.print_exc() return http_error(500, "Unable to write to this ZIP file.", format=format) else: try: os.makedirs(localpath, exist_ok=True) except OSError: traceback.print_exc() return http_error(500, "Unable to create a directory here.", format=format) elif action == 'save': if os.path.lexists( localpath) and not os.path.isfile(localpath): return http_error(400, "Found a non-file here.", format=format) if archivefile: try: zip0 = zip = zipfile.ZipFile(archivefile, 'a') try: # if subarchivepath exists, open a new zip file for writing. try: info = zip.getinfo(subarchivepath) except KeyError: info = zipfile.ZipInfo(subarchivepath, time.localtime()) else: info.date_time = time.localtime() temp_path = archivefile + '.' + str(time_ns()) zip = zipfile.ZipFile(temp_path, 'w') try: # write to the zip file file = request.files.get('upload') if file is not None: with zip.open(info, 'w', force_zip64=True) as fp: stream = file.stream while True: s = stream.read(8192) if not s: break fp.write(s) fp.close() else: bytes = query.get('text', '').encode('ISO-8859-1') zip.writestr( info, bytes, compress_type=zipfile.ZIP_DEFLATED, compresslevel=9) # copy zip0 content to zip if zip is not zip0: for info in zip0.infolist(): if info.filename == subarchivepath: continue zip.writestr( info, zip0.read(info), compress_type=info.compress_type, compresslevel=None if info.compress_type == zipfile.ZIP_STORED else 9) except: # remove the generated zip file if writing fails if zip is not zip0: zip.close() os.remove(zip.filename) raise else: # replace zip0 with the generated zip file if zip is not zip0: zip0.close() zip.close() temp_path = archivefile + '.' + str( time_ns() + 1) os.rename(archivefile, temp_path) os.rename(zip.filename, archivefile) os.remove(temp_path) finally: zip0.close() except: traceback.print_exc() return http_error(500, "Unable to write to this ZIP file.", format=format) else: try: os.makedirs(os.path.dirname(localpath), exist_ok=True) except: traceback.print_exc() return http_error(500, "Unable to write to this path.", format=format) try: file = request.files.get('upload') if file is not None: file.save(localpath) else: bytes = query.get('text', '').encode('ISO-8859-1') with open(localpath, 'wb') as f: f.write(bytes) f.close() except: traceback.print_exc() return http_error(500, "Unable to write to this file.", format=format) elif action == 'delete': if archivefile: try: zip0 = zipfile.ZipFile(archivefile, 'r') temp_path = archivefile + '.' + str(time_ns()) zip = zipfile.ZipFile(temp_path, 'w') try: deleted = False for info in zip0.infolist(): if (info.filename == subarchivepath or info.filename.startswith( subarchivepath + '/')): deleted = True continue zip.writestr( info, zip0.read(info), compress_type=info.compress_type, compresslevel=None if info.compress_type == zipfile.ZIP_STORED else 9) except: # remove the generated zip file if writing fails zip.close() os.remove(zip.filename) raise else: zip0.close() zip.close() if not deleted: os.remove(zip.filename) return http_error( 404, "Entry does not exist in this ZIP file.", format=format) # replace zip0 with the generated zip file temp_path = archivefile + '.' + str(time_ns() + 1) os.rename(archivefile, temp_path) os.rename(zip.filename, archivefile) os.remove(temp_path) except: traceback.print_exc() return http_error(500, "Unable to write to this ZIP file.", format=format) else: if not os.path.lexists(localpath): return http_error(404, "File does not exist.", format=format) if os.path.islink(localpath): try: os.remove(localpath) except: traceback.print_exc() return http_error(500, "Unable to delete this link.", format=format) elif os.path.isfile(localpath): try: os.remove(localpath) except: traceback.print_exc() return http_error(500, "Unable to delete this file.", format=format) elif os.path.isdir(localpath): try: try: # try rmdir for a possible windows directory junction, # which is not detected by os.path.islink os.rmdir(localpath) except OSError: # directory not empty shutil.rmtree(localpath) except: traceback.print_exc() return http_error( 500, "Unable to delete this directory.", format=format) elif action == 'move': if archivefile: return http_error(400, "File is inside an archive file.", format=format) if not os.path.lexists(localpath): return http_error(404, "File does not exist.", format=format) target = query.get('target') if target is None: return http_error(400, 'Target is not specified.', format=format) targetpath = os.path.normpath( os.path.join(runtime['root'], target.strip('/'))) if not targetpath.startswith(os.path.join(runtime['root'], '')): return http_error( 403, "Unable to operate beyond the root directory.", format=format) if os.path.lexists(targetpath): return http_error( 400, 'Found something at target "{}".'.format(target), format=format) ta, tsa = get_archive_path(target, targetpath) if ta: return http_error(400, "Move target is inside an archive file.", format=format) os.makedirs(os.path.dirname(targetpath), exist_ok=True) try: os.rename(localpath, targetpath) except: traceback.print_exc() return http_error( 500, 'Unable to move to target "{}".'.format(target), format=format) elif action == 'copy': if archivefile: return http_error(400, "File is inside an archive file.", format=format) if not os.path.lexists(localpath): return http_error(404, "File does not exist.", format=format) target = query.get('target') if target is None: return http_error(400, 'Target is not specified.', format=format) targetpath = os.path.normpath( os.path.join(runtime['root'], target.strip('/'))) if not targetpath.startswith(os.path.join(runtime['root'], '')): return http_error( 403, "Unable to operate beyond the root directory.", format=format) if os.path.lexists(targetpath): return http_error( 400, 'Found something at target "{}".'.format(target), format=format) ta, tsa = get_archive_path(target, targetpath) if ta: return http_error(400, "Copy target is inside an archive file.", format=format) os.makedirs(os.path.dirname(targetpath), exist_ok=True) try: try: shutil.copytree(localpath, targetpath) except NotADirectoryError: shutil.copy2(localpath, targetpath) except: traceback.print_exc() return http_error( 500, 'Unable to copy to target "{}".'.format(target), format=format) if format: return http_response('Command run successfully.', format=format) return http_response(status=204) # "view" or undefined actions elif action == 'view': # show file information for other output formats if format: info = util.file_info(localpath) data = { 'name': info.name, 'type': info.type, 'size': info.size, 'last_modified': info.last_modified, 'mime': mimetype, } return http_response(data, format=format) # handle directory if os.path.isdir(localpath): return handle_directory_listing(localtargetpath) # handle file elif os.path.isfile(localpath): # view archive file if mimetype in ("application/html+zip", "application/x-maff"): return handle_archive_viewing(localtargetpath, mimetype) # view markdown if mimetype == "text/markdown": return handle_markdown_output(filepath, localtargetpath) # convert meta refresh to 302 redirect if localtargetpath.lower().endswith('.htm'): target = util.parse_meta_refresh(localtargetpath).target if target is not None: # Keep several chars as javascript encodeURI do, # plus "%" as target may have already been escaped. new_url = urljoin( request.url, quote(target, ";,/?:@&=+$-_.!~*'()#%")) return redirect(new_url) # show static file for other cases response = static_file(filepath, root=runtime['root'], mimetype=mimetype) # handle sub-archive path elif archivefile: response = handle_subarchive_path( os.path.realpath(archivefile), subarchivepath, mimetype) else: return http_error(404) # don't include charset m, p = parse_options_header(response.headers.get('Content-Type')) try: del p['charset'] except KeyError: pass response.headers.set('Content-Type', dump_options_header(m, p)) return response # unknown action else: return http_error(400, "Action not supported.", format=format)
def on_update(d): self.headers['Content-Type'] = \ dump_options_header(self.mimetype, d)
def on_update(d): self.headers['Content-Type'] = dump_options_header(self.mimetype, d)
def test_dump_options_header(self): assert http.dump_options_header("foo", {"bar": 42}) == "foo; bar=42" assert http.dump_options_header("foo", {"bar": 42, "fizz": None}) in ("foo; bar=42; fizz", "foo; fizz; bar=42")
def on_update(d: t.Dict[str, str]) -> None: self.headers["Content-Type"] = dump_options_header(self.mimetype, d)