import json from urllib.parse import unquote_plus from fledge.tentative.resources import fledge_http_server_util # Script to generate trusted bidding signals. The response depends on the # keys and interestGroupNames - some result in entire response failures, others # affect only their own value. Keys are preferentially used over # interestGroupName, since keys are composible, but some tests need to cover # there being no keys. def main(request, response): hostname = None keys = None interestGroupNames = None # Manually parse query params. Can't use request.GET because it unescapes as well as splitting, # and commas mean very different things from escaped commas. for param in request.url_parts.query.split("&"): pair = param.split("=", 1) if len(pair) != 2: return fail(response, "Bad query parameter: " + param) # Browsers should escape query params consistently. if "%20" in pair[1]: return fail(response, "Query parameter should escape using '+': " + param) # Hostname can't be empty. The empty string can be a key or interest group name, though. if pair[0] == "hostname" and hostname == None and len(pair[1]) > 0: hostname = pair[1] continue if pair[0] == "keys" and keys == None: keys = list(map(unquote_plus, pair[1].split(","))) continue if pair[0] == "interestGroupNames" and interestGroupNames == None: interestGroupNames = list(map(unquote_plus, pair[1].split(","))) continue if pair[0] == "slotSize" or pair[0] == "allSlotsRequestedSizes": continue return fail(response, "Unexpected query parameter: " + param) # "interestGroupNames" and "hostname" are mandatory. if not hostname: return fail(response, "hostname missing") if not interestGroupNames: return fail(response, "interestGroupNames missing") response.status = (200, b"OK") # The JSON representation of this is used as the response body. This does # not currently include a "perInterestGroupData" object. responseBody = {"keys": {}} # Set when certain special keys are observed, used in place of the JSON # representation of `responseBody`, when set. body = None contentType = "application/json" adAuctionAllowed = "true" dataVersion = None if keys: for key in keys: value = "default value" if key == "close-connection": # Close connection without writing anything, to simulate a # network error. The write call is needed to avoid writing the # default headers. response.writer.write("") response.close_connection = True return elif key.startswith("replace-body:"): # Replace entire response body. Continue to run through other # keys, to allow them to modify request headers. body = key.split(':', 1)[1] elif key.startswith("data-version:"): dataVersion = key.split(':', 1)[1] elif key == "http-error": response.status = (404, b"Not found") elif key == "no-content-type": contentType = None elif key == "wrong-content-type": contentType = 'text/plain' elif key == "bad-ad-auction-allowed": adAuctionAllowed = "sometimes" elif key == "ad-auction-not-allowed": adAuctionAllowed = "false" elif key == "no-ad-auction-allow": adAuctionAllowed = None elif key == "no-value": continue elif key == "wrong-value": responseBody["keys"]["another-value"] = "another-value" continue elif key == "null-value": value = None elif key == "num-value": value = 1 elif key == "string-value": value = "1" elif key == "array-value": value = [1, "foo", None] elif key == "object-value": value = {"a":"b", "c":["d"]} elif key == "interest-group-names": value = json.dumps(interestGroupNames) elif key == "hostname": value = request.GET.first(b"hostname", b"not-found").decode("ASCII") elif key == "headers": value = fledge_http_server_util.headers_to_ascii(request.headers) elif key == "slotSize": value = request.GET.first(b"slotSize", b"not-found").decode("ASCII") elif key == "allSlotsRequestedSizes": value = request.GET.first(b"allSlotsRequestedSizes", b"not-found").decode("ASCII") responseBody["keys"][key] = value if "data-version" in interestGroupNames: dataVersion = "4" if contentType: response.headers.set("Content-Type", contentType) if adAuctionAllowed: response.headers.set("Ad-Auction-Allowed", adAuctionAllowed) if dataVersion: response.headers.set("Data-Version", dataVersion) response.headers.set("Ad-Auction-Bidding-Signals-Format-Version", "2") if body != None: return body return json.dumps(responseBody) def fail(response, body): response.status = (400, "Bad Request") response.headers.set(b"Content-Type", b"text/plain") return body