def renderDirectory(self, request: TwistedRequest, zipFile: ZipFile, node: 'ZipTreeNode') -> object: """Serve a directory from a ZIP file. """ # URLs for directory entries should end with a slash. if not request.path.endswith(b'/'): path = (request.postpath or request.prepath)[-1] return redirectTo(path + b'/', request) # Serve index.html at directory URL. entries = node.children index = entries.get('index.html') if isinstance(index, ZipInfo): return self.renderFile(request, zipFile, index) # If a ZIP contains a single file or single top-level directory, # redirect to that. if len(request.postpath) == 1: if len(entries) == 1: (name, entry), = entries.items() path = name.encode() if isinstance(entry, ZipTreeNode): path += b'/' return redirectTo(path, request) request.setResponseCode(500) request.setHeader(b'Content-Type', b'text/plain; charset=UTF-8') return b'ZIP directory listing not yet implemented\n'
def render_GET(self, request: Request) -> str: send_cors(request) args = get_args(request, ("token", "sid", "client_secret")) resp = self.do_validate_request(request) if "success" in resp and resp["success"]: msg = "Verification successful! Please return to your Matrix client to continue." if "next_link" in args: next_link = args["next_link"] request.setResponseCode(302) request.setHeader("Location", next_link) else: request.setResponseCode(400) msg = ( "Verification failed: you may need to request another verification text" ) brand = self.sydent.brand_from_request(request) # self.sydent.config.http.verify_response_template is deprecated if self.sydent.config.http.verify_response_template is None: templateFile = self.sydent.get_branded_template( brand, "verify_response_template.html", ) else: templateFile = self.sydent.config.http.verify_response_template request.setHeader("Content-Type", "text/html") return open(templateFile).read() % {"message": msg}
async def _async_render_GET(self, request: Request) -> None: set_cors_headers(request) request.setHeader( b"Content-Security-Policy", b"sandbox;" b" default-src 'none';" b" script-src 'none';" b" plugin-types application/pdf;" b" style-src 'unsafe-inline';" b" media-src 'self';" b" object-src 'self';", ) request.setHeader( b"Referrer-Policy", b"no-referrer", ) server_name, media_id, name = parse_media_id(request) if server_name == self.server_name: await self.media_repo.get_local_media(request, media_id, name) else: allow_remote = parse_boolean(request, "allow_remote", default=True) if not allow_remote: logger.info( "Rejecting request for remote media %s/%s due to allow_remote", server_name, media_id, ) respond_404(request) return await self.media_repo.get_remote_media(request, server_name, media_id, name)
def render_GET(self, request: Request) -> bytes: args = get_args(request, ("nextLink", ), required=False) resp = None try: resp = self.do_validate_request(request) except Exception: pass if resp and "success" in resp and resp["success"]: msg = "Verification successful! Please return to your Matrix client to continue." if "nextLink" in args: next_link = args["nextLink"] if not next_link.startswith("file:///"): request.setResponseCode(302) request.setHeader("Location", next_link) else: msg = "Verification failed: you may need to request another verification email" brand = self.sydent.brand_from_request(request) # self.sydent.config.http.verify_response_template is deprecated if self.sydent.config.http.verify_response_template is None: templateFile = self.sydent.get_branded_template( brand, "verify_response_template.html", ) else: templateFile = self.sydent.config.http.verify_response_template request.setHeader("Content-Type", "text/html") res = open(templateFile).read() % {"message": msg} return res.encode("UTF-8")
def render_OPTIONS(self, request: Request) -> bytes: request.setResponseCode(204) request.setHeader(b"Content-Length", b"0") set_cors_headers(request) return b""
def inner(self: Res, request: Request) -> bytes: """ Runs a web handler function with the given request and parameters, then converts its result into JSON and returns it. If an error happens, also sets the HTTP response code. :param self: The current object. :param request: The request to process. :return: The JSON payload to send as a response to the request. """ try: request.setHeader("Content-Type", "application/json") return dict_to_json_bytes(f(self, request)) except MatrixRestError as e: request.setResponseCode(e.httpStatus) return dict_to_json_bytes({"errcode": e.errcode, "error": e.error}) except Exception: logger.exception("Exception processing request") request.setHeader("Content-Type", "application/json") request.setResponseCode(500) return dict_to_json_bytes({ "errcode": "M_UNKNOWN", "error": "Internal Server Error", })
def render(self, request: TwistedRequest) -> bytes: body = super().render(request) request.setHeader( b'WWW-Authenticate', b'Basic realm="%s"' % self.realm.encode('ascii') ) return body
def respond_with_html_bytes(request: Request, code: int, html_bytes: bytes) -> None: """ Sends HTML (encoded as UTF-8 bytes) as the response to the given request. Note that this adds clickjacking protection headers and finishes the request. Args: request: The http request to respond to. code: The HTTP response code. html_bytes: The HTML bytes to use as the response body. """ # could alternatively use request.notifyFinish() and flip a flag when # the Deferred fires, but since the flag is RIGHT THERE it seems like # a waste. if request._disconnected: logger.warning( "Not sending response to request %s, already disconnected.", request ) return None request.setResponseCode(code) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%d" % (len(html_bytes),)) # Ensure this content cannot be embedded. set_clickjacking_protection_headers(request) request.write(html_bytes) finish_request(request)
def return_html_error( f: failure.Failure, request: Request, error_template: Union[str, jinja2.Template], ) -> None: """Sends an HTML error page corresponding to the given failure. Handles RedirectException and other CodeMessageExceptions (such as SynapseError) Args: f: the error to report request: the failing request error_template: the HTML template. Can be either a string (with `{code}`, `{msg}` placeholders), or a jinja2 template """ if f.check(CodeMessageException): # mypy doesn't understand that f.check asserts the type. cme: CodeMessageException = f.value # type: ignore code = cme.code msg = cme.msg if isinstance(cme, RedirectException): logger.info("%s redirect to %s", request, cme.location) request.setHeader(b"location", cme.location) request.cookies.extend(cme.cookies) elif isinstance(cme, SynapseError): logger.info("%s SynapseError: %s - %s", request, code, msg) else: logger.error( "Failed handle request %r", request, exc_info=(f.type, f.value, f.getTracebackObject()), # type: ignore[arg-type] ) elif f.check(CancelledError): code = HTTP_STATUS_REQUEST_CANCELLED msg = "Request cancelled" if not request._disconnected: logger.error( "Got cancellation before client disconnection when handling request %r", request, exc_info=(f.type, f.value, f.getTracebackObject()), # type: ignore[arg-type] ) else: code = HTTPStatus.INTERNAL_SERVER_ERROR msg = "Internal server error" logger.error( "Failed handle request %r", request, exc_info=(f.type, f.value, f.getTracebackObject()), # type: ignore[arg-type] ) if isinstance(error_template, str): body = error_template.format(code=code, msg=html.escape(msg)) else: body = error_template.render(code=code, msg=msg) respond_with_html(request, code, body)
def render_GET(self, request: Request) -> bytes: if not self._serve_server_wellknown: request.setResponseCode(404) request.setHeader(b"Content-Type", b"text/plain") return b"404. Is anything ever truly *well* known?\n" request.setHeader(b"Content-Type", b"application/json") return self._response
def putDone( cls, result: None, # pylint: disable=unused-argument request: TwistedRequest) -> None: request.setResponseCode(201) request.setHeader(b'Content-Type', b'text/plain; charset=UTF-8') request.write(b'Artifact stored\n') request.finish()
def set_corp_headers(request: Request) -> None: """Set the CORP headers so that javascript running in a web browsers can embed the resource returned from this request when their client requires the `Cross-Origin-Embedder-Policy: require-corp` header. Args: request: The http request to add the CORP header to. """ request.setHeader(b"Cross-Origin-Resource-Policy", b"cross-origin")
def render_GET(self, request: server.Request): # noqa: N802 """Render the database status in plain text.""" if not self.is_client_permitted(request): request.setResponseCode(403) return b'Access denied' result_txt = DatabaseStatusRequest().generate_status() request.setHeader(b'Content-Type', b'text/plain; charset=utf-8') return result_txt.encode('utf-8')
def putFailed(cls, fail: Failure, request: TwistedRequest) -> None: ex = fail.value if isinstance(ex, ValueError): request.setResponseCode(415) request.setHeader(b'Content-Type', b'text/plain; charset=UTF-8') request.write((f'{ex}\n').encode()) request.finish() else: request.processingFailed(fail)
def render_GET(self, request: Request) -> bytes: set_cors_headers(request) r = self._well_known_builder.get_well_known() if not r: request.setResponseCode(404) request.setHeader(b"Content-Type", b"text/plain") return b".well-known not available" logger.debug("returning: %s", r) request.setHeader(b"Content-Type", b"application/json") return json_encoder.encode(r).encode("utf-8")
def _return_html_error(code: int, msg: str, request: Request): """Sends an HTML error page""" body = HTML_ERROR_TEMPLATE.format(code=code, msg=html.escape(msg)).encode("utf-8") request.setResponseCode(code) request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Length", b"%i" % (len(body), )) request.write(body) try: request.finish() except RuntimeError as e: logger.info("Connection disconnected before response was written: %r", e)
def set_clickjacking_protection_headers(request: Request) -> None: """ Set headers to guard against clickjacking of embedded content. This sets the X-Frame-Options and Content-Security-Policy headers which instructs browsers to not allow the HTML of the response to be embedded onto another page. Args: request: The http request to add the headers to. """ request.setHeader(b"X-Frame-Options", b"DENY") request.setHeader(b"Content-Security-Policy", b"frame-ancestors 'none';")
def respond_with_json( request: Request, code: int, json_object: Any, send_cors: bool = False, pretty_print: bool = False, canonical_json: bool = True, ): """Sends encoded JSON in response to the given request. Args: request: The http request to respond to. code: The HTTP response code. json_object: The object to serialize to JSON. send_cors: Whether to send Cross-Origin Resource Sharing headers https://fetch.spec.whatwg.org/#http-cors-protocol pretty_print: Whether to include indentation and line-breaks in the resulting JSON bytes. canonical_json: Whether to use the canonicaljson algorithm when encoding the JSON bytes. Returns: twisted.web.server.NOT_DONE_YET if the request is still active. """ # could alternatively use request.notifyFinish() and flip a flag when # the Deferred fires, but since the flag is RIGHT THERE it seems like # a waste. if request._disconnected: logger.warning( "Not sending response to request %s, already disconnected.", request) return None if pretty_print: encoder = iterencode_pretty_printed_json else: if canonical_json or synapse.events.USE_FROZEN_DICTS: encoder = iterencode_canonical_json else: encoder = _encode_json_bytes request.setResponseCode(code) request.setHeader(b"Content-Type", b"application/json") request.setHeader(b"Cache-Control", b"no-cache, no-store, must-revalidate") if send_cors: set_cors_headers(request) _ByteProducer(request, encoder(json_object)) return NOT_DONE_YET
def serveZipEntry(cls, inp: IO[bytes], info: ZipInfo, request: TwistedRequest) -> 'FileProducer': """Serves a deflate-compressed ZIP file entry as a gzip file.""" request.setHeader(b'Content-Encoding', b'gzip') # Seek to start of deflate stream. offset = info.header_offset inp.seek(offset + 26) nameLen, extraLen = cls._variableHeaderLengths.unpack(inp.read(4)) inp.seek(offset + 30 + nameLen + extraLen) size = info.compress_size footer = cls._gzipFooter.pack(info.CRC, info.file_size) return cls._serve(cls.gzipBlockGen(inp, size, footer), request)
def return_html_error( f: failure.Failure, request: Request, error_template: Union[str, jinja2.Template], ) -> None: """Sends an HTML error page corresponding to the given failure. Handles RedirectException and other CodeMessageExceptions (such as SynapseError) Args: f: the error to report request: the failing request error_template: the HTML template. Can be either a string (with `{code}`, `{msg}` placeholders), or a jinja2 template """ if f.check(CodeMessageException): cme = f.value code = cme.code msg = cme.msg if isinstance(cme, RedirectException): logger.info("%s redirect to %s", request, cme.location) request.setHeader(b"location", cme.location) request.cookies.extend(cme.cookies) elif isinstance(cme, SynapseError): logger.info("%s SynapseError: %s - %s", request, code, msg) else: logger.error( "Failed handle request %r", request, exc_info=(f.type, f.value, f.getTracebackObject()), ) else: code = HTTPStatus.INTERNAL_SERVER_ERROR msg = "Internal server error" logger.error( "Failed handle request %r", request, exc_info=(f.type, f.value, f.getTracebackObject()), ) if isinstance(error_template, str): body = error_template.format(code=code, msg=html.escape(msg)) else: body = error_template.render(code=code, msg=msg) respond_with_html(request, code, body)
def render_GET(self, request: Request): # $ requestHandler request.addCookie( "key", "value") # $ CookieWrite CookieName="key" CookieValue="value" request.addCookie( k="key", v="value") # $ CookieWrite CookieName="key" CookieValue="value" val = "key2=value" request.cookies.append(val) # $ CookieWrite CookieRawHeader=val request.responseHeaders.addRawHeader("key", "value") request.setHeader( "Set-Cookie", "key3=value3" ) # $ MISSING: CookieWrite CookieRawHeader="key3=value3" return b"" # $ HttpResponse mimetype=text/html responseBody=b""
async def on_GET(self, request: Request, medium: str) -> None: if medium != "email": raise SynapseError( 400, "This medium is currently not supported for registration") if self.config.email.threepid_behaviour_email == ThreepidBehaviour.OFF: if self.config.email.local_threepid_handling_disabled_due_to_email_config: logger.warning( "User registration via email has been disabled due to lack of email config" ) raise SynapseError( 400, "Email-based registration is disabled on this server") sid = parse_string(request, "sid", required=True) client_secret = parse_string(request, "client_secret", required=True) assert_valid_client_secret(client_secret) token = parse_string(request, "token", required=True) # Attempt to validate a 3PID session try: # Mark the session as valid next_link = await self.store.validate_threepid_session( sid, client_secret, token, self.clock.time_msec()) # Perform a 302 redirect if next_link is set if next_link: if next_link.startswith("file:///"): logger.warning( "Not redirecting to next_link as it is a local file: address" ) else: request.setResponseCode(302) request.setHeader("Location", next_link) finish_request(request) return None # Otherwise show the success template html = self.config.email.email_registration_template_success_html_content status_code = 200 except ThreepidValidationError as e: status_code = e.code # Show a failure page with a reason template_vars = {"failure_reason": e.msg} html = self._failure_email_template.render(**template_vars) respond_with_html(request, status_code, html)
async def on_GET(self, request: Request) -> None: if self.config.email.threepid_behaviour_email == ThreepidBehaviour.OFF: if self.config.email.local_threepid_handling_disabled_due_to_email_config: logger.warning( "Adding emails have been disabled due to lack of an email config" ) raise SynapseError( 400, "Adding an email to your account is disabled on this server" ) elif self.config.email.threepid_behaviour_email == ThreepidBehaviour.REMOTE: raise SynapseError( 400, "This homeserver is not validating threepids. Use an identity server " "instead.", ) sid = parse_string(request, "sid", required=True) token = parse_string(request, "token", required=True) client_secret = parse_string(request, "client_secret", required=True) assert_valid_client_secret(client_secret) # Attempt to validate a 3PID session try: # Mark the session as valid next_link = await self.store.validate_threepid_session( sid, client_secret, token, self.clock.time_msec() ) # Perform a 302 redirect if next_link is set if next_link: request.setResponseCode(302) request.setHeader("Location", next_link) finish_request(request) return None # Otherwise show the success template html = self.config.email.email_add_threepid_template_success_html_content status_code = 200 except ThreepidValidationError as e: status_code = e.code # Show a failure page with a reason template_vars = {"failure_reason": e.msg} html = self._failure_email_template.render(**template_vars) respond_with_html(request, status_code, html)
async def _async_render_POST(self, request: Request) -> Tuple[int, bytes]: sid = parse_string(request, "sid", required=True) token = parse_string(request, "token", required=True) client_secret = parse_string(request, "client_secret", required=True) # Attempt to validate a 3PID session try: # Mark the session as valid next_link = await self.store.validate_threepid_session( sid, client_secret, token, self.clock.time_msec() ) # Perform a 302 redirect if next_link is set if next_link: if next_link.startswith("file:///"): logger.warning( "Not redirecting to next_link as it is a local file: address" ) else: next_link_bytes = next_link.encode("utf-8") request.setHeader("Location", next_link_bytes) return ( 302, ( b'You are being redirected to <a src="%s">%s</a>.' % (next_link_bytes, next_link_bytes) ), ) # Otherwise show the success template html_bytes = self._email_password_reset_template_success_html.encode( "utf-8" ) status_code = 200 except ThreepidValidationError as e: status_code = e.code # Show a failure page with a reason template_vars = {"failure_reason": e.msg} html_bytes = self._failure_email_template.render(**template_vars).encode( "utf-8" ) return status_code, html_bytes
def renderFile(self, request: TwistedRequest, zipFile: ZipFile, info: ZipInfo) -> object: """Serve a file entry from a ZIP file. """ # Determine content type. contentType, encoding = guess_type(info.filename, strict=False) if encoding is not None: # We want contents to be served as-is, not automatically # decoded by the browser. contentType = 'application/' + encoding if contentType is None: contentType = 'application/octet-stream' request.setHeader(b'Content-Type', contentType.encode()) request.setHeader(b'Content-Disposition', b'inline') # Serve data in compressed form if user agent accepts it. if info.compress_type == ZIP_DEFLATED: accept = AcceptedEncodings.parse( request.getHeader('accept-encoding')) decompress = 4.0 * accept['gzip'] < accept['identity'] if not decompress: request.setHeader('Content-Encoding', 'gzip') else: decompress = True if decompress: FileProducer.servePlain(zipFile.open(info), request) else: FileProducer.serveZipEntry(self.zipPath.open(), info, request) return NOT_DONE_YET
def respond_with_json_bytes( request: Request, code: int, json_bytes: bytes, send_cors: bool = False, ): """Sends encoded JSON in response to the given request. Args: request: The http request to respond to. code: The HTTP response code. json_bytes: The json bytes to use as the response body. send_cors: Whether to send Cross-Origin Resource Sharing headers https://fetch.spec.whatwg.org/#http-cors-protocol Returns: twisted.web.server.NOT_DONE_YET if the request is still active. """ request.setResponseCode(code) request.setHeader(b"Content-Type", b"application/json") request.setHeader(b"Content-Length", b"%d" % (len(json_bytes),)) request.setHeader(b"Cache-Control", b"no-cache, no-store, must-revalidate") if send_cors: set_cors_headers(request) # note that this is zero-copy (the bytesio shares a copy-on-write buffer with # the original `bytes`). bytes_io = BytesIO(json_bytes) producer = NoRangeStaticProducer(request, bytes_io) producer.start() return NOT_DONE_YET
def respond_with_json_bytes( request: Request, code: int, json_bytes: bytes, send_cors: bool = False, ) -> Optional[int]: """Sends encoded JSON in response to the given request. Args: request: The http request to respond to. code: The HTTP response code. json_bytes: The json bytes to use as the response body. send_cors: Whether to send Cross-Origin Resource Sharing headers https://fetch.spec.whatwg.org/#http-cors-protocol Returns: twisted.web.server.NOT_DONE_YET if the request is still active. """ if request._disconnected: logger.warning( "Not sending response to request %s, already disconnected.", request ) return None request.setResponseCode(code) request.setHeader(b"Content-Type", b"application/json") request.setHeader(b"Content-Length", b"%d" % (len(json_bytes),)) request.setHeader(b"Cache-Control", b"no-cache, no-store, must-revalidate") if send_cors: set_cors_headers(request) _write_bytes_to_request(request, json_bytes) return NOT_DONE_YET
async def render(f: AsyncRenderer[Res], self: Res, request: Request) -> None: request.setHeader("Content-Type", "application/json") try: result = await f(self, request) request.write(dict_to_json_bytes(result)) except MatrixRestError as e: request.setResponseCode(e.httpStatus) request.write( dict_to_json_bytes({ "errcode": e.errcode, "error": e.error })) except Exception: logger.exception("Request processing failed") request.setResponseCode(500) request.write( dict_to_json_bytes({ "errcode": "M_UNKNOWN", "error": "Internal Server Error" })) request.finish()
def render_PUT(self, request: TwistedRequest) -> object: path = self.path if path.isfile(): request.setResponseCode(409) request.setHeader(b'Content-Type', b'text/plain; charset=UTF-8') return b'Artifacts cannot be overwritten\n' # Note: Twisted buffers the entire upload into 'request.content' # prior to calling our render method. # There doesn't seem to be a clean way to handle streaming # uploads in Twisted; we'd have to set site.requestFactory # to a request implementation that overrides gotLength() or # handleContentChunk(), both of which are documented as # "not intended for users". # Do the actual store in a separate thread, so we don't have to worry # about slow operations hogging the reactor thread. deferToThread(self._storeArtifact, request.content, path) \ .addCallback(self.putDone, request) \ .addErrback(self.putFailed, request) return NOT_DONE_YET
def set_cors_headers(request: Request): """Set the CORS headers so that javascript running in a web browsers can use this API Args: request: The http request to add CORS to. """ request.setHeader(b"Access-Control-Allow-Origin", b"*") request.setHeader(b"Access-Control-Allow-Methods", b"GET, HEAD, POST, PUT, DELETE, OPTIONS") request.setHeader( b"Access-Control-Allow-Headers", b"Origin, X-Requested-With, Content-Type, Accept, Authorization, Date", )