| OLD | NEW |
| (Empty) | |
| 1 #!/usr/bin/env python |
| 2 # coding: utf-8 |
| 3 |
| 4 # Copyright 2014 The Crashpad Authors. All rights reserved. |
| 5 # |
| 6 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 # you may not use this file except in compliance with the License. |
| 8 # You may obtain a copy of the License at |
| 9 # |
| 10 # http://www.apache.org/licenses/LICENSE-2.0 |
| 11 # |
| 12 # Unless required by applicable law or agreed to in writing, software |
| 13 # distributed under the License is distributed on an "AS IS" BASIS, |
| 14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 # See the License for the specific language governing permissions and |
| 16 # limitations under the License. |
| 17 |
| 18 """A one-shot testing webserver. |
| 19 |
| 20 When invoked, this server will write a short integer to stdout, indiciating on |
| 21 which port the server is listening. It will then read one integer from stdin, |
| 22 indiciating the response code to be sent in response to a request. It also reads |
| 23 16 characters from stdin, which, after having "\r\n" appended, will form the |
| 24 response body in a successful response (one with code 200). The server will |
| 25 process one HTTP request, deliver the prearranged response to the client, and |
| 26 write the entire request to stdout. It will then terminate. |
| 27 |
| 28 This server is written in Python since it provides a simple HTTP stack, and |
| 29 because parsing Chunked encoding is safer and easier in a memory-safe language. |
| 30 This could easily have been written in C++ instead. |
| 31 """ |
| 32 |
| 33 import BaseHTTPServer |
| 34 import struct |
| 35 import sys |
| 36 |
| 37 class BufferedReadFile(object): |
| 38 """A File-like object that stores all read contents into a buffer.""" |
| 39 |
| 40 def __init__(self, real_file): |
| 41 self.file = real_file |
| 42 self.buffer = "" |
| 43 |
| 44 def read(self, size=-1): |
| 45 buf = self.file.read(size) |
| 46 self.buffer += buf |
| 47 return buf |
| 48 |
| 49 def readline(self, size=-1): |
| 50 buf = self.file.readline(size) |
| 51 self.buffer += buf |
| 52 return buf |
| 53 |
| 54 def flush(self): |
| 55 self.file.flush() |
| 56 |
| 57 def close(self): |
| 58 self.file.close() |
| 59 |
| 60 |
| 61 class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
| 62 # Everything to be written to stdout is collected into this string. It can’t |
| 63 # be written to stdout until after the HTTP transaction is complete, because |
| 64 # stdout is a pipe being read by a test program that’s also the HTTP client. |
| 65 # The test program expects to complete the entire HTTP transaction before it |
| 66 # even starts reading this script’s stdout. If the stdout pipe buffer fills up |
| 67 # during an HTTP transaction, deadlock would result. |
| 68 raw_request = '' |
| 69 |
| 70 response_code = 500 |
| 71 response_body = '' |
| 72 |
| 73 def handle_one_request(self): |
| 74 # Wrap the rfile in the buffering file object so that the raw header block |
| 75 # can be written to stdout after it is parsed. |
| 76 self.rfile = BufferedReadFile(self.rfile) |
| 77 BaseHTTPServer.BaseHTTPRequestHandler.handle_one_request(self) |
| 78 |
| 79 def do_POST(self): |
| 80 RequestHandler.raw_request = self.rfile.buffer |
| 81 self.rfile.buffer = '' |
| 82 |
| 83 if self.headers.get('Transfer-Encoding', '') == 'Chunked': |
| 84 body = self.handle_chunked_encoding() |
| 85 else: |
| 86 length = int(self.headers.get('Content-Length', -1)) |
| 87 body = self.rfile.read(length) |
| 88 |
| 89 RequestHandler.raw_request += body |
| 90 |
| 91 self.send_response(self.response_code) |
| 92 self.end_headers() |
| 93 if self.response_code == 200: |
| 94 self.wfile.write(self.response_body) |
| 95 self.wfile.write('\r\n') |
| 96 |
| 97 def handle_chunked_encoding(self): |
| 98 """This parses a "Transfer-Encoding: Chunked" body in accordance with |
| 99 RFC 7230 §4.1. This returns the result as a string. |
| 100 """ |
| 101 body = '' |
| 102 chunk_size = self.read_chunk_size() |
| 103 while chunk_size > 0: |
| 104 # Read the body. |
| 105 data = self.rfile.read(chunk_size) |
| 106 chunk_size -= len(data) |
| 107 body += data |
| 108 |
| 109 # Finished reading this chunk. |
| 110 if chunk_size == 0: |
| 111 # Read through any trailer fields. |
| 112 trailer_line = self.rfile.readline() |
| 113 while trailer_line.strip() != '': |
| 114 trailer_line = self.rfile.readline() |
| 115 |
| 116 # Read the chunk size. |
| 117 chunk_size = self.read_chunk_size() |
| 118 return body |
| 119 |
| 120 def read_chunk_size(self): |
| 121 # Read the whole line, including the \r\n. |
| 122 chunk_size_and_ext_line = self.rfile.readline() |
| 123 # Look for a chunk extension. |
| 124 chunk_size_end = chunk_size_and_ext_line.find(';') |
| 125 if chunk_size_end == -1: |
| 126 # No chunk extensions; just encounter the end of line. |
| 127 chunk_size_end = chunk_size_and_ext_line.find('\r') |
| 128 if chunk_size_end == -1: |
| 129 self.send_response(400) # Bad request. |
| 130 return -1 |
| 131 return int(chunk_size_and_ext_line[:chunk_size_end], base=16) |
| 132 |
| 133 |
| 134 def Main(): |
| 135 if sys.platform == 'win32': |
| 136 import os, msvcrt |
| 137 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) |
| 138 |
| 139 # Start the server. |
| 140 server = BaseHTTPServer.HTTPServer(('127.0.0.1', 0), RequestHandler) |
| 141 |
| 142 # Write the port as an unsigned short to the parent process. |
| 143 sys.stdout.write(struct.pack('=H', server.server_address[1])) |
| 144 sys.stdout.flush() |
| 145 |
| 146 # Read the desired test response code as an unsigned short and the desired |
| 147 # response body as a 16-byte string from the parent process. |
| 148 RequestHandler.response_code, RequestHandler.response_body = \ |
| 149 struct.unpack('=H16s', sys.stdin.read(struct.calcsize('=H16s'))) |
| 150 |
| 151 # Handle the request. |
| 152 server.handle_request() |
| 153 |
| 154 # Share the entire request with the test program, which will validate it. |
| 155 sys.stdout.write(RequestHandler.raw_request) |
| 156 sys.stdout.flush() |
| 157 |
| 158 if __name__ == '__main__': |
| 159 Main() |
| OLD | NEW |