tor-browser

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

beacon.py (4804B)


      1 import json
      2 
      3 from wptserve.utils import isomorphic_decode
      4 
      5 def main(request, response):
      6    """Helper handler for Beacon tests.
      7 
      8    It handles two forms of requests:
      9 
     10    STORE:
     11        A URL with a query string of the form 'cmd=store&id=<token>'.
     12 
     13        Stores the receipt of a sendBeacon() request along with its validation
     14        result, returning HTTP 200 OK.
     15 
     16        if "preflightExpected"  exists in the query, this handler responds to
     17        CORS preflights.
     18 
     19    STAT:
     20        A URL with a query string of the form 'cmd=stat&id=<token>'.
     21 
     22        Retrieves the results of test for the given id and returns them as a
     23        JSON array and HTTP 200 OK status code. Due to the eventual read-once
     24        nature of the stash, results for a given test are only guaranteed to be
     25        returned once, though they may be returned multiple times.
     26 
     27        An entry may contain following members.
     28            - error: An error string. null if there is no error.
     29            - type: The content-type header of the request "(missing)" if there
     30                    is no content-type header in the request.
     31 
     32        Example response bodies:
     33            - [{error: null, type: "text/plain;charset=UTF8"}]
     34            - [{error: "some validation details"}]
     35            - []
     36 
     37    Common parameters:
     38        cmd - the command, 'store' or 'stat'.
     39        id - the unique identifier of the test.
     40    """
     41 
     42    id = request.GET.first(b"id")
     43    command = request.GET.first(b"cmd").lower()
     44 
     45    # Append CORS headers if needed.
     46    if b"origin" in request.GET:
     47        response.headers.set(b"Access-Control-Allow-Origin",
     48                             request.GET.first(b"origin"))
     49    if b"credentials" in request.GET:
     50        response.headers.set(b"Access-Control-Allow-Credentials",
     51                             request.GET.first(b"credentials"))
     52 
     53    # Handle the 'store' and 'stat' commands.
     54    if command == b"store":
     55        error = None
     56 
     57        # Only store the actual POST requests, not any preflight/OPTIONS
     58        # requests we may get.
     59        if request.method == u"POST":
     60            payload = b""
     61            contentType = request.headers[b"Content-Type"] \
     62                if b"Content-Type" in request.headers else b"(missing)"
     63            if b"form-data" in contentType:
     64                if b"payload" in request.POST:
     65                    # The payload was sent as a FormData.
     66                    payload = request.POST.first(b"payload")
     67                else:
     68                    # A FormData was sent with an empty payload.
     69                    pass
     70            else:
     71                # The payload was sent as either a string, Blob, or BufferSource.
     72                payload = request.body
     73 
     74            payload_parts = list(filter(None, payload.split(b":")))
     75            if len(payload_parts) > 1:
     76                payload_size = int(payload_parts[0])
     77 
     78                # Confirm the payload size sent matches with the number of
     79                # characters sent.
     80                if payload_size != len(payload):
     81                    error = u"expected %d characters but got %d" % (
     82                        payload_size, len(payload))
     83                else:
     84                    # Confirm the payload contains the correct characters.
     85                    for i in range(len(payload)):
     86                        if i <= len(payload_parts[0]):
     87                            continue
     88                        c = payload[i:i+1]
     89                        if c != b"*":
     90                            error = u"expected '*' at index %d but got '%s''" % (
     91                                i, isomorphic_decode(c))
     92                            break
     93 
     94            # Store the result in the stash so that it can be retrieved
     95            # later with a 'stat' command.
     96            request.server.stash.put(id, {
     97                u"error": error,
     98                u"type": isomorphic_decode(contentType)
     99            })
    100        elif request.method == u"OPTIONS":
    101            # If we expect a preflight, then add the cors headers we expect,
    102            # otherwise log an error as we shouldn't send a preflight for all
    103            # requests.
    104            if b"preflightExpected" in request.GET:
    105                response.headers.set(b"Access-Control-Allow-Headers",
    106                                     b"content-type")
    107                response.headers.set(b"Access-Control-Allow-Methods", b"POST")
    108            else:
    109                error = u"Preflight not expected."
    110                request.server.stash.put(id, {u"error": error})
    111    elif command == b"stat":
    112        test_data = request.server.stash.take(id)
    113        results = [test_data] if test_data else []
    114 
    115        response.headers.set(b"Content-Type", b"text/plain")
    116        response.content = json.dumps(results)
    117    else:
    118        response.status = 400  # BadRequest