diff options
Diffstat (limited to 'Foundation/TestData/GTMHTTPFetcherTestServer')
-rwxr-xr-x | Foundation/TestData/GTMHTTPFetcherTestServer | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/Foundation/TestData/GTMHTTPFetcherTestServer b/Foundation/TestData/GTMHTTPFetcherTestServer new file mode 100755 index 0000000..838c110 --- /dev/null +++ b/Foundation/TestData/GTMHTTPFetcherTestServer @@ -0,0 +1,274 @@ +#!/usr/bin/python +# +# Copyright 2006-2008 Google Inc. +# +# 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 simple server for testing the http calls + +"""A simple BaseHTTPRequestHandler for unit testing GTM Network code + +This http server is for use by GTMHTTPFetcherTest.m in testing +both authentication and object retrieval. + +Requests to the path /accounts/ClientLogin are assumed to be +for login; other requests are for object retrieval +""" + +__author__ = 'Google Inc.' + +import string +import cgi +import time +import os +import sys +import re +import mimetypes +import socket +from BaseHTTPServer import BaseHTTPRequestHandler +from BaseHTTPServer import HTTPServer +from optparse import OptionParser + +class ServerTimeoutException(Exception): + pass + + +class TimeoutServer(HTTPServer): + + """HTTP server for testing GTM network requests. + + This server will throw an exception if it receives no connections for + several minutes. We use this to ensure that the server will be cleaned + up if something goes wrong during the unit testing. + """ + + def get_request(self): + self.socket.settimeout(120.0) + result = None + while result is None: + try: + result = self.socket.accept() + except socket.timeout: + raise ServerTimeoutException + result[0].settimeout(None) + return result + + +class SimpleServer(BaseHTTPRequestHandler): + + """HTTP request handler for testing GTM network requests. + + This is an implementation of a request handler for BaseHTTPServer, + specifically designed for GTM network code usage. + + Normal requests for GET/POST/PUT simply retrieve the file from the + supplied path, starting in the current directory. A cookie called + TestCookie is set by the response header, with the value of the filename + requested. + + DELETE requests always succeed. + + Appending ?status=n results in a failure with status value n. + + Paths ending in .auth have the .auth extension stripped, and must have + an authorization header of "GoogleLogin auth=GoodAuthToken" to succeed. + + Successful results have a Last-Modified header set; if that header's value + ("thursday") is supplied in a request's "If-Modified-Since" header, the + result is 304 (Not Modified). + + Requests to /accounts/ClientLogin will fail if supplied with a body + containing Passwd=bad. If they contain logintoken and logincaptcha values, + those must be logintoken=CapToken&logincaptch=good to succeed. + """ + + def do_GET(self): + self.doAllRequests() + + def do_POST(self): + self.doAllRequests() + + def do_PUT(self): + self.doAllRequests() + + def do_DELETE(self): + self.doAllRequests() + + def doAllRequests(self): + # This method handles all expected incoming requests + # + # Requests to path /accounts/ClientLogin are assumed to be for signing in + # + # Other paths are for retrieving a local file. An .auth appended + # to a file path will require authentication (meaning the Authorization + # header must be present with the value "GoogleLogin auth=GoodAuthToken".) + # If the token is present, the file (without uthe .auth at the end) will + # be returned. + # + # HTTP Delete commands succeed but return no data. + # + # GData override headers (putting the verb in X-HTTP-Method-Override) + # are supported. + # + # Any auth password is valid except "bad", which will fail, and "captcha", + # which will fail unless the authentication request's post string includes + # "logintoken=CapToken&logincaptcha=good" + + # We will use a readable default result string since it should never show up + # in output + resultString = "default GTMHTTPFetcherTestServer result\n"; + resultStatus = 0 + headerType = "text/plain" + postString = "" + modifiedDate = "thursday" # clients should treat dates as opaque, generally + + # auth queries and some others may include post data + postLength = int(self.headers.getheader("Content-Length", "0")); + if postLength > 0: + postString = self.rfile.read(postLength) + + # auth queries and some GData queries include post data + ifModifiedSince = self.headers.getheader("If-Modified-Since", ""); + + # retrieve the auth header; require it if the file path ends + # with the string ".auth" + authorization = self.headers.getheader("Authorization", "") + if self.path.endswith(".auth"): + if authorization != "GoogleLogin auth=GoodAuthToken": + self.send_error(401,"Unauthorized: %s" % self.path) + return + self.path = self.path[:-5] # remove the .auth at the end + + overrideHeader = self.headers.getheader("X-HTTP-Method-Override", "") + httpCommand = self.command + if httpCommand == "POST" and len(overrideHeader) > 0: + httpCommand = overrideHeader + + try: + if self.path.endswith("/accounts/ClientLogin"): + # + # it's a sign-in attempt; it's good unless the password is "bad" or + # "captcha" + # + + # use regular expression to find the password + password = "" + searchResult = re.search("(Passwd=)([^&\n]*)", postString) + if searchResult: + password = searchResult.group(2) + + if password == "bad": + resultString = "Error=BadAuthentication\n" + resultStatus = 403 + + elif password == "captcha": + logintoken = "" + logincaptcha = "" + + # use regular expressions to find the captcha token and answer + searchResult = re.search("(logintoken=)([^&\n]*)", postString); + if searchResult: + logintoken = searchResult.group(2) + + searchResult = re.search("(logincaptcha=)([^&\n]*)", postString); + if searchResult: + logincaptcha = searchResult.group(2) + + # if the captcha token is "CapToken" and the answer is "good" + # then it's a valid sign in + if (logintoken == "CapToken") and (logincaptcha == "good"): + resultString = "SID=GoodSID\nLSID=GoodLSID\nAuth=GoodAuthToken\n" + resultStatus = 200 + else: + # incorrect captcha token or answer provided + resultString = ("Error=CaptchaRequired\nCaptchaToken=CapToken" + "\nCaptchaUrl=CapUrl\n") + resultStatus = 403 + + else: + # valid username/password + resultString = "SID=GoodSID\nLSID=GoodLSID\nAuth=GoodAuthToken\n" + resultStatus = 200 + + elif httpCommand == "DELETE": + # + # it's an object delete; read and return empty data + # + resultString = "" + resultStatus = 200 + headerType = "text/plain" + + else: + # queries that have something like "?status=456" should fail with the + # status code + searchResult = re.search("(status=)([0-9]+)", self.path) + if searchResult: + status = searchResult.group(2) + self.send_error(int(status), + "Test HTTP server status parameter: %s" % self.path) + return + + # if the client gave us back our not modified date, then say there's no + # change in the response + if ifModifiedSince == modifiedDate: + self.send_response(304) # Not Modified + return + + else: + # + # it's a file fetch; read and return the data file + # + f = open("." + self.path) + resultString = f.read() + f.close() + resultStatus = 200 + fileTypeInfo = mimetypes.guess_type("." + self.path) + headerType = fileTypeInfo[0] # first part of the tuple is mime type + + self.send_response(resultStatus) + self.send_header("Content-type", headerType) + self.send_header("Last-Modified", modifiedDate) + + cookieValue = os.path.basename("." + self.path) + self.send_header('Set-Cookie', 'TestCookie=%s' % cookieValue) + self.end_headers() + self.wfile.write(resultString) + + except IOError: + self.send_error(404,"File Not Found: %s" % self.path) + + +def main(): + try: + parser = OptionParser() + parser.add_option("-p", "--port", dest="port", help="Port to run server on", + type="int", default="80") + parser.add_option("-r", "--root", dest="root", help="Where to root server", + default=".") + (options, args) = parser.parse_args() + os.chdir(options.root) + server = TimeoutServer(("127.0.0.1", options.port), SimpleServer) + sys.stdout.write("started GTMHTTPFetcherTestServer," + " serving files from root directory %s..." % os.getcwd()); + sys.stdout.flush(); + server.serve_forever() + except KeyboardInterrupt: + print "^C received, shutting down server" + server.socket.close() + except ServerTimeoutException: + print "Too long since the last request, shutting down server" + server.socket.close() + + +if __name__ == "__main__": + main() |