tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

reports.py (4672B)


      1 """Methods for the report-event-attribution and report-aggregate-attribution endpoints"""
      2 import json
      3 from typing import List, Optional, Tuple
      4 import urllib.parse
      5 
      6 from wptserve.request import Request
      7 from wptserve.stash import Stash
      8 from wptserve.utils import isomorphic_decode, isomorphic_encode
      9 
     10 # Key used to access the reports in the stash.
     11 REPORTS = "4691a2d7fca5430fb0f33b1bd8a9d388"
     12 REDIRECT = "9250f93f-2c05-4aae-83b9-2817b0e18b4e"
     13 
     14 CLEAR_STASH = isomorphic_encode("clear_stash")
     15 CONFIG_REDIRECT = isomorphic_encode("redirect_to")
     16 
     17 Header = Tuple[str, str]
     18 Status = Tuple[int, str]
     19 Response = Tuple[Status, List[Header], str]
     20 
     21 def decode_headers(headers: dict) -> dict:
     22  """Decodes the headers from wptserve.
     23 
     24  wptserve headers are encoded like
     25  {
     26    encoded(key): [encoded(value1), encoded(value2),...]
     27  }
     28  This method decodes the above using the wptserve.utils.isomorphic_decode
     29  method
     30  """
     31  return {
     32      isomorphic_decode(key): [isomorphic_decode(el) for el in value
     33                              ] for key, value in headers.items()
     34  }
     35 
     36 def get_request_origin(request: Request) -> str:
     37  return "%s://%s" % (request.url_parts.scheme,
     38                      request.url_parts.netloc)
     39 
     40 def configure_redirect(request, origin) -> None:
     41  with request.server.stash.lock:
     42      request.server.stash.put(REDIRECT, origin)
     43      return None
     44 
     45 def get_report_redirect_url(request):
     46  with request.server.stash.lock:
     47      origin = request.server.stash.take(REDIRECT)
     48      if origin is None:
     49         return None
     50      origin_parts = urllib.parse.urlsplit(origin)
     51      parts = request.url_parts
     52      new_parts = origin_parts._replace(path=bytes(parts.path, 'utf-8'))
     53      return urllib.parse.urlunsplit(new_parts)
     54 
     55 def handle_post_report(request: Request, headers: List[Header]) -> Response:
     56  """Handles POST request for reports.
     57 
     58  Retrieves the report from the request body and stores the report in the
     59  stash. If clear_stash is specified in the query params, clears the stash.
     60  """
     61  if request.GET.get(CLEAR_STASH):
     62    clear_stash(request.server.stash)
     63    return (200, "OK"), headers, json.dumps({
     64        "code": 200,
     65        "message": "Stash successfully cleared.",
     66    })
     67 
     68  redirect_origin = request.GET.get(CONFIG_REDIRECT)
     69  if redirect_origin:
     70    configure_redirect(request, redirect_origin)
     71    return (200, "OK"), headers, json.dumps({
     72        "code": 200,
     73        "message": "Redirect successfully configured.",
     74    })
     75 
     76  redirect_url = get_report_redirect_url(request)
     77  if redirect_url is not None:
     78    headers.append(("Location", redirect_url))
     79    return (308, "Permanent Redirect"), headers, json.dumps({
     80        "code": 308
     81    })
     82 
     83  store_report(
     84      request.server.stash, get_request_origin(request), {
     85          "body": request.body.decode("utf-8"),
     86          "headers": decode_headers(request.headers)
     87      })
     88  return (201, "OK"), headers, json.dumps({
     89      "code": 201,
     90      "message": "Report successfully stored."
     91  })
     92 
     93 
     94 def handle_get_reports(request: Request, headers: List[Header]) -> Response:
     95  """Handles GET request for reports.
     96 
     97  Retrieves and returns all reports from the stash.
     98  """
     99  reports = take_reports(request.server.stash, get_request_origin(request))
    100  headers.append(("Access-Control-Allow-Origin", "*"))
    101  return (200, "OK"), headers, json.dumps({
    102      "code": 200,
    103      "reports": reports,
    104  })
    105 
    106 
    107 def store_report(stash: Stash, origin: str, report: str) -> None:
    108  """Stores the report in the stash. Report here is a JSON."""
    109  with stash.lock:
    110    reports_dict = stash.take(REPORTS)
    111    if not reports_dict:
    112      reports_dict = {}
    113    reports = reports_dict.get(origin, [])
    114    reports.append(report)
    115    reports_dict[origin] = reports
    116    stash.put(REPORTS, reports_dict)
    117  return None
    118 
    119 def clear_stash(stash: Stash) -> None:
    120  "Clears the stash."
    121  stash.take(REPORTS)
    122  stash.take(REDIRECT)
    123  return None
    124 
    125 def take_reports(stash: Stash, origin: str) -> List[str]:
    126  """Takes all the reports from the stash and returns them."""
    127  with stash.lock:
    128    reports_dict = stash.take(REPORTS)
    129    if not reports_dict:
    130      reports_dict = {}
    131 
    132    reports = reports_dict.pop(origin, [])
    133    stash.put(REPORTS, reports_dict)
    134  return reports
    135 
    136 
    137 def handle_reports(request: Request) -> Response:
    138  """Handles request to get or store reports."""
    139  headers = [("Content-Type", "application/json")]
    140  if request.method == "POST":
    141    return handle_post_report(request, headers)
    142  if request.method == "GET":
    143    return handle_get_reports(request, headers)
    144  return (405, "Method Not Allowed"), headers, json.dumps({
    145      "code": 405,
    146      "message": "Only GET or POST methods are supported."
    147  })