summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/beacon/resources/beacon.py
blob: d81bfb1ac68084f619027a869c491d6188fe0c63 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import json

from wptserve.utils import isomorphic_decode

def main(request, response):
    """Helper handler for Beacon tests.

    It handles two forms of requests:

    STORE:
        A URL with a query string of the form 'cmd=store&id=<token>'.

        Stores the receipt of a sendBeacon() request along with its validation
        result, returning HTTP 200 OK.

        if "preflightExpected"  exists in the query, this handler responds to
        CORS preflights.

    STAT:
        A URL with a query string of the form 'cmd=stat&id=<token>'.

        Retrieves the results of test for the given id and returns them as a
        JSON array and HTTP 200 OK status code. Due to the eventual read-once
        nature of the stash, results for a given test are only guaranteed to be
        returned once, though they may be returned multiple times.

        An entry may contain following members.
            - error: An error string. null if there is no error.
            - type: The content-type header of the request "(missing)" if there
                    is no content-type header in the request.

        Example response bodies:
            - [{error: null, type: "text/plain;charset=UTF8"}]
            - [{error: "some validation details"}]
            - []

    Common parameters:
        cmd - the command, 'store' or 'stat'.
        id - the unique identifier of the test.
    """

    id = request.GET.first(b"id")
    command = request.GET.first(b"cmd").lower()

    # Append CORS headers if needed.
    if b"origin" in request.GET:
        response.headers.set(b"Access-Control-Allow-Origin",
                             request.GET.first(b"origin"))
    if b"credentials" in request.GET:
        response.headers.set(b"Access-Control-Allow-Credentials",
                             request.GET.first(b"credentials"))

    # Handle the 'store' and 'stat' commands.
    if command == b"store":
        error = None

        # Only store the actual POST requests, not any preflight/OPTIONS
        # requests we may get.
        if request.method == u"POST":
            payload = b""
            contentType = request.headers[b"Content-Type"] \
                if b"Content-Type" in request.headers else b"(missing)"
            if b"form-data" in contentType:
                if b"payload" in request.POST:
                    # The payload was sent as a FormData.
                    payload = request.POST.first(b"payload")
                else:
                    # A FormData was sent with an empty payload.
                    pass
            else:
                # The payload was sent as either a string, Blob, or BufferSource.
                payload = request.body

            payload_parts = list(filter(None, payload.split(b":")))
            if len(payload_parts) > 0:
                payload_size = int(payload_parts[0])

                # Confirm the payload size sent matches with the number of
                # characters sent.
                if payload_size != len(payload):
                    error = u"expected %d characters but got %d" % (
                        payload_size, len(payload))
                else:
                    # Confirm the payload contains the correct characters.
                    for i in range(len(payload)):
                        if i <= len(payload_parts[0]):
                            continue
                        c = payload[i:i+1]
                        if c != b"*":
                            error = u"expected '*' at index %d but got '%s''" % (
                                i, isomorphic_decode(c))
                            break

            # Store the result in the stash so that it can be retrieved
            # later with a 'stat' command.
            request.server.stash.put(id, {
                u"error": error,
                u"type": isomorphic_decode(contentType)
            })
        elif request.method == u"OPTIONS":
            # If we expect a preflight, then add the cors headers we expect,
            # otherwise log an error as we shouldn't send a preflight for all
            # requests.
            if b"preflightExpected" in request.GET:
                response.headers.set(b"Access-Control-Allow-Headers",
                                     b"content-type")
                response.headers.set(b"Access-Control-Allow-Methods", b"POST")
            else:
                error = u"Preflight not expected."
                request.server.stash.put(id, {u"error": error})
    elif command == b"stat":
        test_data = request.server.stash.take(id)
        results = [test_data] if test_data else []

        response.headers.set(b"Content-Type", b"text/plain")
        response.content = json.dumps(results)
    else:
        response.status = 400  # BadRequest