1#!/usr/bin/env python 2 3import mimetypes 4import os 5 6from wptserve.utils import isomorphic_decode, isomorphic_encode 7 8# Test server that tracks the last partition_id was used with each connection for each uuid, and 9# lets consumers query if multiple different partition_ids have been been used for any socket. 10# 11# Server assumes that ports aren't reused, so a client address and a server port uniquely identify 12# a connection. If that constraint is ever violated, the test will be flaky. No sockets being 13# closed for the duration of the test is sufficient to ensure that, though even if sockets are 14# closed, the OS should generally prefer to use new ports for new connections, if any are 15# available. 16def main(request, response): 17 response.headers.set(b"Cache-Control", b"no-store") 18 dispatch = request.GET.first(b"dispatch", None) 19 uuid = request.GET.first(b"uuid", None) 20 partition_id = request.GET.first(b"partition_id", None) 21 22 if not uuid or not dispatch or not partition_id: 23 return simple_response(request, response, 404, b"Not found", b"Invalid query parameters") 24 25 # Unless nocheck_partition is true, check partition_id against server_state, and update server_state. 26 stash = request.server.stash 27 test_failed = False 28 request_count = 0; 29 connection_count = 0; 30 if request.GET.first(b"nocheck_partition", None) != b"True": 31 # Need to grab the lock to access the Stash, since requests are made in parallel. 32 with stash.lock: 33 # Don't use server hostname here, since H2 allows multiple hosts to reuse a connection. 34 # Server IP is not currently available, unfortunately. 35 address_key = isomorphic_encode(str(request.client_address) + u"|" + str(request.url_parts.port)) 36 server_state = stash.take(uuid) or {b"test_failed": False, 37 b"request_count": 0, b"connection_count": 0} 38 request_count = server_state[b"request_count"] 39 request_count += 1 40 server_state[b"request_count"] = request_count 41 if address_key in server_state: 42 if server_state[address_key] != partition_id: 43 server_state[b"test_failed"] = True 44 else: 45 connection_count = server_state[b"connection_count"] 46 connection_count += 1 47 server_state[b"connection_count"] = connection_count 48 server_state[address_key] = partition_id 49 test_failed = server_state[b"test_failed"] 50 stash.put(uuid, server_state) 51 52 origin = request.headers.get(b"Origin") 53 if origin: 54 response.headers.set(b"Access-Control-Allow-Origin", origin) 55 response.headers.set(b"Access-Control-Allow-Credentials", b"true") 56 57 if request.method == u"OPTIONS": 58 return handle_preflight(request, response) 59 60 if dispatch == b"fetch_file": 61 return handle_fetch_file(request, response, partition_id, uuid) 62 63 if dispatch == b"check_partition": 64 status = request.GET.first(b"status", 200) 65 if test_failed: 66 return simple_response(request, response, status, b"OK", b"Multiple partition IDs used on a socket") 67 body = b"ok" 68 if request.GET.first(b"addcounter", False): 69 body += (". Request was sent " + str(request_count) + " times. " + 70 str(connection_count) + " connections were created.").encode('utf-8') 71 return simple_response(request, response, status, b"OK", body) 72 73 if dispatch == b"clean_up": 74 stash.take(uuid) 75 if test_failed: 76 return simple_response(request, response, 200, b"OK", b"Test failed, but cleanup completed.") 77 return simple_response(request, response, 200, b"OK", b"cleanup complete") 78 79 return simple_response(request, response, 404, b"Not Found", b"Unrecognized dispatch parameter: " + dispatch) 80 81def handle_preflight(request, response): 82 response.status = (200, b"OK") 83 response.headers.set(b"Access-Control-Allow-Methods", b"GET") 84 response.headers.set(b"Access-Control-Allow-Headers", b"header-to-force-cors") 85 response.headers.set(b"Access-Control-Max-Age", b"86400") 86 return b"Preflight request" 87 88def simple_response(request, response, status_code, status_message, body, content_type=b"text/plain"): 89 response.status = (status_code, status_message) 90 response.headers.set(b"Content-Type", content_type) 91 return body 92 93def handle_fetch_file(request, response, partition_id, uuid): 94 subresource_origin = request.GET.first(b"subresource_origin", None) 95 rel_path = request.GET.first(b"path", None) 96 97 # This needs to be passed on to subresources so they all have access to it. 98 include_credentials = request.GET.first(b"include_credentials", None) 99 if not subresource_origin or not rel_path or not include_credentials: 100 return simple_response(request, response, 404, b"Not found", b"Invalid query parameters") 101 102 cur_path = os.path.realpath(isomorphic_decode(__file__)) 103 base_path = os.path.abspath(os.path.join(os.path.dirname(cur_path), os.pardir, os.pardir, os.pardir)) 104 path = os.path.abspath(os.path.join(base_path, isomorphic_decode(rel_path))) 105 106 # Basic security check. 107 if not path.startswith(base_path): 108 return simple_response(request, response, 404, b"Not found", b"Invalid path") 109 110 sandbox = request.GET.first(b"sandbox", None) 111 if sandbox == b"true": 112 response.headers.set(b"Content-Security-Policy", b"sandbox allow-scripts") 113 114 file = open(path, mode="rb") 115 body = file.read() 116 file.close() 117 118 subresource_path = b"/" + isomorphic_encode(os.path.relpath(isomorphic_decode(__file__), base_path)).replace(b'\\', b'/') 119 subresource_params = b"?partition_id=" + partition_id + b"&uuid=" + uuid + b"&subresource_origin=" + subresource_origin + b"&include_credentials=" + include_credentials 120 body = body.replace(b"SUBRESOURCE_PREFIX:", subresource_origin + subresource_path + subresource_params) 121 122 other_origin = request.GET.first(b"other_origin", None) 123 if other_origin: 124 body = body.replace(b"OTHER_PREFIX:", other_origin + subresource_path + subresource_params) 125 126 mimetypes.init() 127 mimetype_pair = mimetypes.guess_type(path) 128 mimetype = mimetype_pair[0] 129 130 if mimetype == None or mimetype_pair[1] != None: 131 return simple_response(request, response, 500, b"Server Error", b"Unknown MIME type") 132 return simple_response(request, response, 200, b"OK", body, mimetype) 133