From 2dfa41c9a571cc838c6fe937c504d90f50636523 Mon Sep 17 00:00:00 2001 From: Kevin Morris Date: Mon, 7 Feb 2022 00:49:34 -0800 Subject: [PATCH] feat(rpc): support POST method Signed-off-by: Kevin Morris --- aurweb/routers/rpc.py | 62 ++++++++++++++++++++++++++++++++----------- doc/rpc.txt | 16 +++++++++++ test/test_rpc.py | 13 +++++++++ 3 files changed, 75 insertions(+), 16 deletions(-) diff --git a/aurweb/routers/rpc.py b/aurweb/routers/rpc.py index aae47cfa..17eb377c 100644 --- a/aurweb/routers/rpc.py +++ b/aurweb/routers/rpc.py @@ -7,7 +7,7 @@ from urllib.parse import unquote import orjson -from fastapi import APIRouter, Query, Request, Response +from fastapi import APIRouter, Form, Query, Request, Response from fastapi.responses import JSONResponse from aurweb import defaults @@ -65,20 +65,13 @@ def parse_args(request: Request): JSONP_EXPR = re.compile(r'^[a-zA-Z0-9()_.]{1,128}$') -@router.get("/rpc.php/") # Temporary! Remove on 03/04 -@router.get("/rpc.php") # Temporary! Remove on 03/04 -@router.get("/rpc/") -@router.get("/rpc") -async def rpc(request: Request, - v: Optional[int] = Query(default=None), - type: Optional[str] = Query(default=None), - by: Optional[str] = Query(default=defaults.RPC_SEARCH_BY), - arg: Optional[str] = Query(default=None), - args: Optional[List[str]] = Query(default=[], alias="arg[]"), - callback: Optional[str] = Query(default=None)): - - if not request.url.query: - return documentation() +async def rpc_request(request: Request, + v: Optional[int] = None, + type: Optional[str] = None, + by: Optional[str] = defaults.RPC_SEARCH_BY, + arg: Optional[str] = None, + args: Optional[List[str]] = [], + callback: Optional[str] = None): # Create a handle to our RPC class. rpc = RPC(version=v, type=type) @@ -100,7 +93,14 @@ async def rpc(request: Request, # Prepare list of arguments for input. If 'arg' was given, it'll # be a list with one element. - arguments = parse_args(request) + arguments = [] + if request.url.query: + arguments = parse_args(request) + else: + if arg: + arguments.append(arg) + arguments += args + data = rpc.handle(by=by, args=arguments) # Serialize `data` into JSON in a sorted fashion. This way, our @@ -128,3 +128,33 @@ async def rpc(request: Request, content = f"/**/{callback}({content.decode()})" return Response(content, headers=headers) + + +@router.get("/rpc.php/") # Temporary! Remove on 03/04 +@router.get("/rpc.php") # Temporary! Remove on 03/04 +@router.get("/rpc/") +@router.get("/rpc") +async def rpc(request: Request, + v: Optional[int] = Query(default=None), + type: Optional[str] = Query(default=None), + by: Optional[str] = Query(default=defaults.RPC_SEARCH_BY), + arg: Optional[str] = Query(default=None), + args: Optional[List[str]] = Query(default=[], alias="arg[]"), + callback: Optional[str] = Query(default=None)): + if not request.url.query: + return documentation() + return await rpc_request(request, v, type, by, arg, args, callback) + + +@router.get("/rpc.php/") # Temporary! Remove on 03/04 +@router.get("/rpc.php") # Temporary! Remove on 03/04 +@router.post("/rpc/") +@router.post("/rpc") +async def rpc_post(request: Request, + v: Optional[int] = Form(default=None), + type: Optional[str] = Form(default=None), + by: Optional[str] = Form(default=defaults.RPC_SEARCH_BY), + arg: Optional[str] = Form(default=None), + args: Optional[List[str]] = Form(default=[], alias="arg[]"), + callback: Optional[str] = Form(default=None)): + return await rpc_request(request, v, type, by, arg, args, callback) diff --git a/doc/rpc.txt b/doc/rpc.txt index f0a28183..ebd7e485 100644 --- a/doc/rpc.txt +++ b/doc/rpc.txt @@ -28,6 +28,22 @@ Package information can be obtained by issuing HTTP GET requests of the form +/rpc?v=5&type=info&arg[]=_pkg1_&arg[]=_pkg2_&...+ where _pkg1_, _pkg2_, ... are the names of packages to retrieve package details for. +Request Types +------------- + +The GET method here parses arguments in an odd way due to `v=5` historically +supporting this ordering. Later versions will remove support for this kind +of parsing, and the POST method is our first step toward solving it. + +* `GET` + - Search arguments are constructed using the last found argument(s). + If the last related argument is `arg[]`, we collect arguments from + end to start until we hit a non-`arg[]` argument. If the last related + argument is `arg`, it used as the one and only argument. +* `POST` + - Search arguments are constructed using `[arg] + args` where + `args == arg[]`. + Examples -------- diff --git a/test/test_rpc.py b/test/test_rpc.py index 6063a26f..7847899c 100644 --- a/test/test_rpc.py +++ b/test/test_rpc.py @@ -782,3 +782,16 @@ def test_rpc_jsonp_callback(client: TestClient): response = request.get("/rpc", params=params) assert response.headers.get("content-type") == "application/json" assert response.json().get("error") == "Invalid callback name." + + +def test_rpc_post(client: TestClient, packages: List[Package]): + data = { + "v": 5, + "type": "info", + "arg": "big-chungus", + "arg[]": ["chungy-chungus"] + } + with client as request: + resp = request.post("/rpc", data=data) + assert resp.status_code == int(HTTPStatus.OK) + assert resp.json().get("resultcount") == 2