2014-10-31 12:17:32 -04:00
|
|
|
|
#!/usr/bin/env python
|
|
|
|
|
# coding: utf-8
|
|
|
|
|
|
|
|
|
|
# Copyright 2014 The Crashpad Authors. All rights reserved.
|
|
|
|
|
#
|
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
|
#
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
#
|
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
|
|
|
|
"""A one-shot testing webserver.
|
|
|
|
|
|
2015-02-05 18:05:40 -05:00
|
|
|
|
When invoked, this server will write a short integer to stdout, indiciating on
|
|
|
|
|
which port the server is listening. It will then read one integer from stdin,
|
|
|
|
|
indiciating the response code to be sent in response to a request. It also reads
|
2015-11-16 13:39:01 -05:00
|
|
|
|
16 characters from stdin, which, after having "\r\n" appended, will form the
|
2015-02-05 18:05:40 -05:00
|
|
|
|
response body in a successful response (one with code 200). The server will
|
|
|
|
|
process one HTTP request, deliver the prearranged response to the client, and
|
|
|
|
|
write the entire request to stdout. It will then terminate.
|
2014-10-31 12:17:32 -04:00
|
|
|
|
|
|
|
|
|
This server is written in Python since it provides a simple HTTP stack, and
|
2017-02-08 16:16:06 -05:00
|
|
|
|
because parsing chunked encoding is safer and easier in a memory-safe language.
|
2014-10-31 12:17:32 -04:00
|
|
|
|
This could easily have been written in C++ instead.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import BaseHTTPServer
|
|
|
|
|
import struct
|
|
|
|
|
import sys
|
2017-02-15 19:54:19 -05:00
|
|
|
|
import zlib
|
2014-10-31 12:17:32 -04:00
|
|
|
|
|
|
|
|
|
class BufferedReadFile(object):
|
|
|
|
|
"""A File-like object that stores all read contents into a buffer."""
|
|
|
|
|
|
|
|
|
|
def __init__(self, real_file):
|
|
|
|
|
self.file = real_file
|
|
|
|
|
self.buffer = ""
|
|
|
|
|
|
|
|
|
|
def read(self, size=-1):
|
|
|
|
|
buf = self.file.read(size)
|
|
|
|
|
self.buffer += buf
|
|
|
|
|
return buf
|
|
|
|
|
|
|
|
|
|
def readline(self, size=-1):
|
|
|
|
|
buf = self.file.readline(size)
|
|
|
|
|
self.buffer += buf
|
|
|
|
|
return buf
|
|
|
|
|
|
|
|
|
|
def flush(self):
|
|
|
|
|
self.file.flush()
|
|
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
|
self.file.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
2015-08-18 17:52:12 -04:00
|
|
|
|
# Everything to be written to stdout is collected into this string. It can’t
|
|
|
|
|
# be written to stdout until after the HTTP transaction is complete, because
|
|
|
|
|
# stdout is a pipe being read by a test program that’s also the HTTP client.
|
|
|
|
|
# The test program expects to complete the entire HTTP transaction before it
|
|
|
|
|
# even starts reading this script’s stdout. If the stdout pipe buffer fills up
|
|
|
|
|
# during an HTTP transaction, deadlock would result.
|
|
|
|
|
raw_request = ''
|
|
|
|
|
|
2014-10-31 12:17:32 -04:00
|
|
|
|
response_code = 500
|
2015-02-05 18:05:40 -05:00
|
|
|
|
response_body = ''
|
2014-10-31 12:17:32 -04:00
|
|
|
|
|
|
|
|
|
def handle_one_request(self):
|
|
|
|
|
# Wrap the rfile in the buffering file object so that the raw header block
|
|
|
|
|
# can be written to stdout after it is parsed.
|
|
|
|
|
self.rfile = BufferedReadFile(self.rfile)
|
|
|
|
|
BaseHTTPServer.BaseHTTPRequestHandler.handle_one_request(self)
|
|
|
|
|
|
|
|
|
|
def do_POST(self):
|
2015-08-18 17:52:12 -04:00
|
|
|
|
RequestHandler.raw_request = self.rfile.buffer
|
2014-10-31 12:17:32 -04:00
|
|
|
|
self.rfile.buffer = ''
|
|
|
|
|
|
2017-02-08 16:16:06 -05:00
|
|
|
|
if self.headers.get('Transfer-Encoding', '').lower() == 'chunked':
|
2017-02-08 22:03:54 -05:00
|
|
|
|
if 'Content-Length' in self.headers:
|
|
|
|
|
raise AssertionError
|
2014-10-31 12:17:32 -04:00
|
|
|
|
body = self.handle_chunked_encoding()
|
|
|
|
|
else:
|
|
|
|
|
length = int(self.headers.get('Content-Length', -1))
|
|
|
|
|
body = self.rfile.read(length)
|
|
|
|
|
|
2017-02-15 19:54:19 -05:00
|
|
|
|
if self.headers.get('Content-Encoding', '').lower() == 'gzip':
|
|
|
|
|
# 15 is the value of |wbits|, which should be at the maximum possible
|
|
|
|
|
# value to ensure that any gzip stream can be decoded. The offset of 16
|
|
|
|
|
# specifies that the stream to decompress will be formatted with a gzip
|
|
|
|
|
# wrapper.
|
|
|
|
|
body = zlib.decompress(body, 16 + 15)
|
|
|
|
|
|
2015-08-18 17:52:12 -04:00
|
|
|
|
RequestHandler.raw_request += body
|
|
|
|
|
|
2014-10-31 12:17:32 -04:00
|
|
|
|
self.send_response(self.response_code)
|
2015-01-26 13:31:35 -08:00
|
|
|
|
self.end_headers()
|
2015-02-05 18:05:40 -05:00
|
|
|
|
if self.response_code == 200:
|
|
|
|
|
self.wfile.write(self.response_body)
|
|
|
|
|
self.wfile.write('\r\n')
|
2015-01-26 13:31:35 -08:00
|
|
|
|
|
2014-10-31 12:17:32 -04:00
|
|
|
|
def handle_chunked_encoding(self):
|
|
|
|
|
"""This parses a "Transfer-Encoding: Chunked" body in accordance with
|
|
|
|
|
RFC 7230 §4.1. This returns the result as a string.
|
|
|
|
|
"""
|
|
|
|
|
body = ''
|
|
|
|
|
chunk_size = self.read_chunk_size()
|
|
|
|
|
while chunk_size > 0:
|
|
|
|
|
# Read the body.
|
|
|
|
|
data = self.rfile.read(chunk_size)
|
|
|
|
|
chunk_size -= len(data)
|
|
|
|
|
body += data
|
|
|
|
|
|
|
|
|
|
# Finished reading this chunk.
|
|
|
|
|
if chunk_size == 0:
|
|
|
|
|
# Read through any trailer fields.
|
|
|
|
|
trailer_line = self.rfile.readline()
|
|
|
|
|
while trailer_line.strip() != '':
|
|
|
|
|
trailer_line = self.rfile.readline()
|
|
|
|
|
|
|
|
|
|
# Read the chunk size.
|
|
|
|
|
chunk_size = self.read_chunk_size()
|
|
|
|
|
return body
|
|
|
|
|
|
|
|
|
|
def read_chunk_size(self):
|
|
|
|
|
# Read the whole line, including the \r\n.
|
|
|
|
|
chunk_size_and_ext_line = self.rfile.readline()
|
|
|
|
|
# Look for a chunk extension.
|
|
|
|
|
chunk_size_end = chunk_size_and_ext_line.find(';')
|
|
|
|
|
if chunk_size_end == -1:
|
|
|
|
|
# No chunk extensions; just encounter the end of line.
|
|
|
|
|
chunk_size_end = chunk_size_and_ext_line.find('\r')
|
|
|
|
|
if chunk_size_end == -1:
|
|
|
|
|
self.send_response(400) # Bad request.
|
|
|
|
|
return -1
|
|
|
|
|
return int(chunk_size_and_ext_line[:chunk_size_end], base=16)
|
|
|
|
|
|
2017-02-08 16:16:06 -05:00
|
|
|
|
def log_request(self, code='-', size='-'):
|
|
|
|
|
# The default implementation logs these to sys.stderr, which is just noise.
|
|
|
|
|
pass
|
|
|
|
|
|
2014-10-31 12:17:32 -04:00
|
|
|
|
|
|
|
|
|
def Main():
|
2015-01-26 13:31:35 -08:00
|
|
|
|
if sys.platform == 'win32':
|
|
|
|
|
import os, msvcrt
|
|
|
|
|
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
|
|
|
|
|
|
2014-10-31 12:17:32 -04:00
|
|
|
|
# Start the server.
|
|
|
|
|
server = BaseHTTPServer.HTTPServer(('127.0.0.1', 0), RequestHandler)
|
|
|
|
|
|
|
|
|
|
# Write the port as an unsigned short to the parent process.
|
|
|
|
|
sys.stdout.write(struct.pack('=H', server.server_address[1]))
|
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
2015-02-05 18:05:40 -05:00
|
|
|
|
# Read the desired test response code as an unsigned short and the desired
|
2015-11-16 13:39:01 -05:00
|
|
|
|
# response body as a 16-byte string from the parent process.
|
2015-02-05 18:05:40 -05:00
|
|
|
|
RequestHandler.response_code, RequestHandler.response_body = \
|
2015-11-16 13:39:01 -05:00
|
|
|
|
struct.unpack('=H16s', sys.stdin.read(struct.calcsize('=H16s')))
|
2014-10-31 12:17:32 -04:00
|
|
|
|
|
|
|
|
|
# Handle the request.
|
|
|
|
|
server.handle_request()
|
|
|
|
|
|
2015-08-18 17:52:12 -04:00
|
|
|
|
# Share the entire request with the test program, which will validate it.
|
|
|
|
|
sys.stdout.write(RequestHandler.raw_request)
|
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
2014-10-31 12:17:32 -04:00
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
Main()
|