tor-browser

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

request-tracker.py (6469B)


      1 import json
      2 import mimetypes
      3 import os
      4 
      5 from fledge.tentative.resources import fledge_http_server_util
      6 import wptserve.stash
      7 from wptserve.utils import isomorphic_decode, isomorphic_encode
      8 
      9 
     10 # Test server that tracks requests it has previously seen, keyed by a token.
     11 #
     12 # All requests have a "dispatch" field indicating what to do, and a "uuid"
     13 # field which should be unique for each test, to avoid tests that fail to
     14 # clean up after themselves, or that are running concurrently, from interfering
     15 # with other tests.
     16 #
     17 # Each uuid has a stash entry with a dictionary with the following entries:
     18 #     "trackedRequests" is a list of all observed requested URLs with a
     19 #         dispatch of "track_get" or "track_post". POSTS are in the format
     20 #         "<url>, body: <body>".
     21 #     "trackedHeaders" is an object mapping HTTP header names to lists
     22 #         of received HTTP header values for a single request with a
     23 #         dispatch of "track_headers".
     24 #     "errors" is a list of an errors that occurred.
     25 #
     26 # A dispatch of "tracked_data" will return all tracked information associated
     27 # with the uuid, as a JSON string. The "errors" field should be checked by
     28 # the caller before checking other fields.
     29 #
     30 # A dispatch of "clean_up" will delete all information associated with the uuid.
     31 def main(request, response):
     32    # Don't cache responses, since tests expect duplicate requests to always
     33    # reach the server.
     34    response.headers.set(b"Cache-Control", b"no-store")
     35 
     36    dispatch = request.GET.first(b"dispatch", None)
     37    uuid = request.GET.first(b"uuid", None)
     38 
     39    # If we're used as a trusted scoring signals handler, our params are
     40    # smuggled in via renderURLs. We won't have dispatch and uuid provided
     41    # directly then.
     42    if dispatch is None and uuid is None:
     43        try:
     44            signals_params = fledge_http_server_util.decode_trusted_scoring_signals_params(request)
     45            for urlList in signals_params.urlLists:
     46                for renderUrl in urlList["urls"]:
     47                    try:
     48                        signalsParams = fledge_http_server_util.decode_render_url_signals_params(renderUrl)
     49                    except ValueError as ve:
     50                        return simple_response(request, response, 500,
     51                                               b"InternalError", str(ve))
     52                for signalsParam in signalsParams:
     53                    if signalsParam.startswith("dispatch:"):
     54                        dispatch = signalsParam.split(':', 1)[1].encode("utf-8")
     55                    elif signalsParam.startswith("uuid:"):
     56                        uuid = signalsParam.split(':', 1)[1].encode("utf-8")
     57        except ValueError:
     58            # It doesn't look like a trusted scoring signals request, so
     59            # never mind.
     60            pass
     61 
     62    if not uuid or not dispatch:
     63        return simple_response(request, response, 404, b"Not found",
     64                               b"Invalid query parameters")
     65 
     66    stash = request.server.stash
     67    with stash.lock:
     68        # Take ownership of stashed entry, if any. This removes the entry of the
     69        # stash.
     70        server_state = stash.take(uuid) or {"trackedRequests": [], "errors": [], "trackedHeaders": None}
     71 
     72        # Clear the entire stash. No work to do, since stash entry was already
     73        # removed.
     74        if dispatch == b"clean_up":
     75            return simple_response(request, response, 200, b"OK",
     76                                   b"cleanup complete")
     77 
     78        # Return the list of entries in the stash. Need to add data back to the
     79        # stash first.
     80        if dispatch == b"tracked_data":
     81            stash.put(uuid, server_state)
     82            return simple_response(request, response, 200, b"OK",
     83                                   json.dumps(server_state))
     84 
     85        # Tracks a request that's expected to be a GET.
     86        if dispatch == b"track_get":
     87            if request.method != "GET":
     88                server_state["errors"].append(
     89                    request.url + " has wrong method: " + request.method)
     90            else:
     91                server_state["trackedRequests"].append(request.url)
     92 
     93            stash.put(uuid, server_state)
     94            return simple_response(request, response, 200, b"OK", b"")
     95 
     96        # Tracks a request that's expected to be a POST.
     97        # In addition to the method, check the Content-Type, which is currently
     98        # always text/plain. The request body is stored in trackedRequests.
     99        if dispatch == b"track_post":
    100            contentType = request.headers.get(b"Content-Type", b"missing")
    101            if request.method != "POST":
    102                server_state["errors"].append(
    103                    request.url + " has wrong method: " + request.method)
    104            elif not contentType.startswith(b"text/plain"):
    105                server_state["errors"].append(
    106                    request.url + " has wrong Content-Type: " +
    107                    contentType.decode("utf-8"))
    108            else:
    109                server_state["trackedRequests"].append(
    110                    request.url + ", body: " + request.body.decode("utf-8"))
    111            stash.put(uuid, server_state)
    112            return simple_response(request, response, 200, b"OK", b"")
    113 
    114        # Tracks request headers for a single request.
    115        if dispatch == b"track_headers":
    116            if server_state["trackedHeaders"] != None:
    117                server_state["errors"].append("Second track_headers request received.")
    118            else:
    119                server_state["trackedHeaders"] = fledge_http_server_util.headers_to_ascii(request.headers)
    120 
    121            stash.put(uuid, server_state)
    122            return simple_response(request, response, 200, b"OK", b"")
    123 
    124        # Report unrecognized dispatch line.
    125        server_state["errors"].append(
    126            request.url + " request with unknown dispatch value received: " +
    127            dispatch.decode("utf-8"))
    128        stash.put(uuid, server_state)
    129        return simple_response(request, response, 404, b"Not Found",
    130                               b"Unrecognized dispatch parameter: " + dispatch)
    131 
    132 def simple_response(request, response, status_code, status_message, body,
    133                    content_type=b"text/plain"):
    134    response.status = (status_code, status_message)
    135    response.headers.set(b"Content-Type", content_type)
    136    # Force refetch on reuse, so multiple requests to tracked URLs are all visible.
    137    response.headers.set(b"Cache-control", b"no-store")
    138    return body