mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
fix(rpc): perform regex match against callback name
Since we're in the hot path, a constant re.compiled JSONP_EXPR is defined for checks against the callback. Additionally, reorganized `content_type` and `content` to avoid performing a DB query when we encounter a regex mismatch. Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
parent
12b4269ba8
commit
2cc44e8f28
2 changed files with 24 additions and 11 deletions
|
@ -1,4 +1,5 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import re
|
||||||
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
@ -61,6 +62,9 @@ def parse_args(request: Request):
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
JSONP_EXPR = re.compile(r'^[a-zA-Z0-9()_.]{1,128}$')
|
||||||
|
|
||||||
|
|
||||||
@router.get("/rpc")
|
@router.get("/rpc")
|
||||||
async def rpc(request: Request,
|
async def rpc(request: Request,
|
||||||
v: Optional[int] = Query(default=None),
|
v: Optional[int] = Query(default=None),
|
||||||
|
@ -78,6 +82,16 @@ async def rpc(request: Request,
|
||||||
return JSONResponse(rpc.error("Rate limit reached"),
|
return JSONResponse(rpc.error("Rate limit reached"),
|
||||||
status_code=int(HTTPStatus.TOO_MANY_REQUESTS))
|
status_code=int(HTTPStatus.TOO_MANY_REQUESTS))
|
||||||
|
|
||||||
|
# If `callback` was provided, produce a text/javascript response
|
||||||
|
# valid for the jsonp callback. Otherwise, by default, return
|
||||||
|
# application/json containing `output`.
|
||||||
|
content_type = "application/json"
|
||||||
|
if callback:
|
||||||
|
if not re.match(JSONP_EXPR, callback):
|
||||||
|
return rpc.error("Invalid callback name.")
|
||||||
|
|
||||||
|
content_type = "text/javascript"
|
||||||
|
|
||||||
# Prepare list of arguments for input. If 'arg' was given, it'll
|
# Prepare list of arguments for input. If 'arg' was given, it'll
|
||||||
# be a list with one element.
|
# be a list with one element.
|
||||||
arguments = parse_args(request)
|
arguments = parse_args(request)
|
||||||
|
@ -92,21 +106,14 @@ async def rpc(request: Request,
|
||||||
md5.update(content)
|
md5.update(content)
|
||||||
etag = md5.hexdigest()
|
etag = md5.hexdigest()
|
||||||
|
|
||||||
# If `callback` was provided, produce a text/javascript response
|
|
||||||
# valid for the jsonp callback. Otherwise, by default, return
|
|
||||||
# application/json containing `output`.
|
|
||||||
# Note: Being the API hot path, `content` is not defaulted to
|
|
||||||
# avoid copying the JSON string in the case callback is provided.
|
|
||||||
content_type = "application/json"
|
|
||||||
if callback:
|
|
||||||
print("callback called")
|
|
||||||
content_type = "text/javascript"
|
|
||||||
content = f"/**/{callback}({content.decode()})"
|
|
||||||
|
|
||||||
# The ETag header expects quotes to surround any identifier.
|
# The ETag header expects quotes to surround any identifier.
|
||||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
|
||||||
headers = {
|
headers = {
|
||||||
"Content-Type": content_type,
|
"Content-Type": content_type,
|
||||||
"ETag": f'"{etag}"'
|
"ETag": f'"{etag}"'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if callback:
|
||||||
|
content = f"/**/{callback}({content.decode()})"
|
||||||
|
|
||||||
return Response(content, headers=headers)
|
return Response(content, headers=headers)
|
||||||
|
|
|
@ -624,3 +624,9 @@ def test_rpc_jsonp_callback():
|
||||||
"/rpc?v=5&type=search&arg=big&callback=jsonCallback")
|
"/rpc?v=5&type=search&arg=big&callback=jsonCallback")
|
||||||
assert response.headers.get("content-type") == "text/javascript"
|
assert response.headers.get("content-type") == "text/javascript"
|
||||||
assert re.search(r'^/\*\*/jsonCallback\(.*\)$', response.text) is not None
|
assert re.search(r'^/\*\*/jsonCallback\(.*\)$', response.text) is not None
|
||||||
|
|
||||||
|
# Test an invalid callback name; we get an application/json error.
|
||||||
|
response = make_request(
|
||||||
|
"/rpc?v=5&type=search&arg=big&callback=jsonCallback!")
|
||||||
|
assert response.headers.get("content-type") == "application/json"
|
||||||
|
assert response.json().get("error") == "Invalid callback name."
|
||||||
|
|
Loading…
Add table
Reference in a new issue