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 verifySignature(request: TwistedRequest, payload: bytes, secret: bytes ) -> bool: remoteSig = request.getHeader('X-Gogs-Signature') if not remoteSig: return False localSig = hmac.new(secret, payload, 'sha256').hexdigest() return hmac.compare_digest(localSig.casefold(), remoteSig.casefold())
async def _async_render_POST(self, request: Request) -> None: requester = await self.auth.get_user_by_req(request) # TODO: The checks here are a bit late. The content will have # already been uploaded to a tmp file at this point content_length = request.getHeader("Content-Length") if content_length is None: raise SynapseError(msg="Request must specify a Content-Length", code=400) if int(content_length) > self.max_upload_size: raise SynapseError( msg="Upload request body is too large", code=413, errcode=Codes.TOO_LARGE, ) upload_name = parse_string(request, b"filename", encoding=None) if upload_name: try: upload_name = upload_name.decode("utf8") except UnicodeDecodeError: raise SynapseError(msg="Invalid UTF-8 filename parameter: %r" % (upload_name), code=400) # If the name is falsey (e.g. an empty byte string) ensure it is None. else: upload_name = None headers = request.requestHeaders if headers.hasHeader(b"Content-Type"): content_type_headers = headers.getRawHeaders(b"Content-Type") assert content_type_headers # for mypy media_type = content_type_headers[0].decode("ascii") else: raise SynapseError(msg="Upload request missing 'Content-Type'", code=400) # if headers.hasHeader(b"Content-Disposition"): # disposition = headers.getRawHeaders(b"Content-Disposition")[0] # TODO(markjh): parse content-dispostion try: content = request.content # type: IO # type: ignore content_uri = await self.media_repo.create_content( media_type, upload_name, content, content_length, requester.user) except SpamMediaException: # For uploading of media we want to respond with a 400, instead of # the default 404, as that would just be confusing. raise SynapseError(400, "Bad content") logger.info("Uploaded content with URI %r", content_uri) respond_with_json(request, 200, {"content_uri": content_uri}, send_cors=True)
def render_GET(self, request: Request): # $ requestHandler # see https://twistedmatrix.com/documents/21.2.0/api/twisted.web.server.Request.html ensure_tainted( request, # $ tainted request.uri, # $ tainted request.path, # $ tainted request.prepath, # $ tainted request.postpath, # $ tainted # file-like request.content, # $ tainted request.content.read(), # $ MISSING: tainted # Dict[bytes, List[bytes]] (for query args) request.args, # $ tainted request.args[b"key"], # $ tainted request.args[b"key"][0], # $ tainted request.args.get(b"key"), # $ tainted request.args.get(b"key")[0], # $ tainted request.received_cookies, # $ tainted request.received_cookies["key"], # $ tainted request.received_cookies.get("key"), # $ tainted request.getCookie(b"key"), # $ tainted # twisted.web.http_headers.Headers # see https://twistedmatrix.com/documents/21.2.0/api/twisted.web.http_headers.Headers.html request.requestHeaders, # $ tainted request.requestHeaders.getRawHeaders("key"), # $ MISSING: tainted request.requestHeaders.getRawHeaders("key") [0], # $ MISSING: tainted request.requestHeaders.getAllRawHeaders(), # $ MISSING: tainted list(request.requestHeaders.getAllRawHeaders() ), # $ MISSING: tainted request.getHeader("key"), # $ tainted request.getAllHeaders(), # $ tainted request.getAllHeaders()["key"], # $ tainted request.user, # $ tainted request.getUser(), # $ tainted request.password, # $ tainted request.getPassword(), # $ tainted request.host, # $ tainted request.getHost(), # $ tainted request.getRequestHostname(), # $ tainted ) # technically user-controlled, but unlikely to lead to vulnerabilities. ensure_not_tainted(request.method, ) # not tainted at all ensure_not_tainted( # outgoing things request.cookies, request.responseHeaders, )
def render_OPTIONS(self, request: TwistedRequest) -> bytes: # Generic HTTP options. request.setHeader(b'Allow', b'GET, HEAD, OPTIONS') # CORS options. origin = request.getHeader(b'Origin') if origin is not None: # Grant all requested headers. requestedHeaders = request.getHeader( b'Access-Control-Request-Headers') or b'' request.setHeader(b'Access-Control-Allow-Headers', requestedHeaders) # The information returned does not expire, but the sanboxed URL # to which it applies does, so caching it beyond the sanbox key # timeout is pointless. request.setHeader(b'Access-Control-Max-Age', b'%d' % ArtifactSandbox.keyTimeout) # Reply without content. request.setResponseCode(204) request.setHeader(b'Content-Length', b'0') return b''
def verifySignature(request: TwistedRequest, payload: bytes, secret: bytes) -> bool: sigHeader = request.getHeader('X-Hub-Signature') if not sigHeader: return False try: hashName, remoteSig = sigHeader.split('=', 1) except ValueError: return False if hashName != 'sha1': # Only accept the SHA1 hash function that GitHub currently uses. return False localSig = hmac.new(secret, payload, hashName).hexdigest() return hmac.compare_digest(localSig.casefold(), remoteSig.casefold())
def tokenFromRequest(request: Request) -> Optional[str]: """Extract token from header of query parameter. :param request: The request to look for an access token in. :return: The token or None if not found """ token = None # check for Authorization header first authHeader = request.getHeader("Authorization") if authHeader is not None and authHeader.startswith("Bearer "): token = authHeader[len("Bearer "):] # no? try access_token query param if token is None: args = get_args(request, ("access_token", ), required=False) token = args.get("access_token") return token
def render_GET(self, request: TwistedRequest) -> object: path = self.path contentType, contentEncoding = guess_type(path.basename(), strict=False) if contentType is None: contentType = 'application/octet-stream' elif contentType.startswith('text/'): # Encoding autodetection in browsers is pretty poor, so we're # likely better off forcing UTF-8. Most gzipped text files are # logs and we tell the wrappers to output in UTF-8. # TODO: Perform an encoding detection on upload, or just preserve # the Content-Type header of the PUT request, if present. # TODO: Convert to UTF-8 if the user agent requests it using # the Accept-Charset header. MDN says modern browsers omit # this header, but a non-browser client could set it to # indicate it is only willing to deal with UTF-8. # If no particular encoding is requested, just serve the # file as it was uploaded. contentType += '; charset=UTF-8' 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 contentEncoding is None: decompress = False else: accept = AcceptedEncodings.parse( request.getHeader('accept-encoding')) decompress = 4.0 * accept[contentEncoding] < accept['identity'] if not decompress: request.setHeader('Content-Encoding', contentEncoding) if decompress: # Note: Passing 'fileobj' to GzipFile appears more elegant, # but that stream isn't closed when GzipFile is closed. stream = openGzip(path.path) else: stream = path.open() FileProducer.servePlain(stream, request) return NOT_DONE_YET
def getChildWithDefault(self, name: bytes, request: TwistedRequest) -> IResource: # Prevent leaking sandbox key to external sites. request.setHeader(b'Referrer-Policy', b'origin-when-cross-origin') # Repeat sandbox rules, in case artifact is viewed outside iframe. request.setHeader(b'Content-Security-Policy', b'sandbox %s;' % SANDBOX_RULES.encode()) origin = request.getHeader(b'Origin') # Handle anonymous guest access. if name == b'anon' and self.project.anonguest: if origin is not None: request.setHeader(b'Access-Control-Allow-Origin', b'*') return SandboxedResource(self.baseDir, ()) # Verify that request came from null origin. if origin is not None: if origin == b'null': request.setHeader(b'Access-Control-Allow-Origin', b'null') else: return AccessDeniedResource( 'Sandboxed content requested from non-null origin') try: key = name.decode('ascii') except UnicodeDecodeError: return ClientErrorResource('Key contains invalid characters') try: path = self._activeKeys[key] except KeyError: # Key does not exist or is no longer valid. # Redirect to non-sandboxed path to acquire new key. return Redirect(b'/'.join([b'..'] * (len(request.postpath) + 1) + request.postpath)) else: return SandboxedResource(self.baseDir, path.path)
def getHeader(self, name, default=None): """Get a request header. If it is not defined use the default as fallback""" return Request.getHeader(self, name) or default
def render(self, request: server.Request) -> bytes: # Deny by default. request.setResponseCode(401) # Get session cookie value if any. sessionid = request.getCookie(self.cookie) if sessionid is not None: if sessionid in self.sessions: request.setResponseCode(200) self.log.info("Session: Validation succeeded") return b"" else: self.log.info("Session: Invalid session id") # Token is passed as a query parameter in the original URL. origurl = http.urlparse(request.getHeader(self.header)) query = http.parse_qs(origurl.query) args = query.get(self.param, []) if len(args) != 1: self.log.error("Request: Token {param} missing", param=self.param) return b"" try: token = jwt.JWT(key=self.key, jwt=args[0].decode()) except (jwt.JWTExpired, jwt.JWTNotYetValid, jwt.JWTMissingClaim, jwt.JWTInvalidClaimValue, jwt.JWTInvalidClaimFormat, jwt.JWTMissingKeyID, jwt.JWTMissingKey) as error: self.log.error("JWT token: {error}", error=error) return b"" except Exception: self.log.failure("JWT token: Unknown exception") return b"" try: claims = json.loads(token.claims) except json.JSONDecodeError as error: self.log.failure("JWT token: Claims {error}", error=error) return b"" # Collect session parameters from claims. sessparams = claims.get("session", {}) kwargs = { "expires": sessparams.get("expires", None), "domain": sessparams.get("domain", None), "path": sessparams.get("path", None), "secure": sessparams.get("secure", None), "httpOnly": sessparams.get("httpOnly", None), "sameSite": sessparams.get("sameSite", None), } # Use maxAge for session ttl if it is present, convert it into a str # type as required by the addCookie call. if "maxAge" in sessparams: kwargs["max_age"] = str(sessparams["maxAge"]) sessttl = int(sessparams["maxAge"]) else: sessttl = self.sessttl # Generate a new session id and remember it. Also clean it up after # ttl seconds. sessionid = secrets.token_urlsafe(nbytes=16).encode() self.sessions.add(sessionid) reactor.callLater(sessttl, self._session_remove, sessionid) self.log.info("Session: Created, num sessions: {sessions}", sessions=len(self.sessions)) # Set cookie in the browser. request.addCookie(self.cookie, sessionid, **kwargs) request.setResponseCode(200) self.log.info("JWT token: Validation succeeded") return b""
def getEvent(request: TwistedRequest) -> WebhookEvents: if request.getHeader('X-Gogs-Event') == 'push': return WebhookEvents.PUSH else: return WebhookEvents.UNSUPPORTED
def getEvent(request: TwistedRequest) -> WebhookEvents: eventName = request.getHeader('X-GitHub-Event') return { 'ping': WebhookEvents.PING, 'push': WebhookEvents.PUSH, }.get(eventName, WebhookEvents.UNSUPPORTED)