#! /usr/bin/python # File: ImageReceiveServer.py # # Contains: A basic HTTP server to accept HTTP PUTs and POSTs. # # Written by: DTS # # Copyright: Copyright (c) 2009 Apple Inc. All Rights Reserved. # # Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. # ("Apple") in consideration of your agreement to the following # terms, and your use, installation, modification or # redistribution of this Apple software constitutes acceptance of # these terms. If you do not agree with these terms, please do # not use, install, modify or redistribute this Apple software. # # In consideration of your agreement to abide by the following # terms, and subject to these terms, Apple grants you a personal, # non-exclusive license, under Apple's copyrights in this # original Apple software (the "Apple Software"), to use, # reproduce, modify and redistribute the Apple Software, with or # without modifications, in source and/or binary forms; provided # that if you redistribute the Apple Software in its entirety and # without modifications, you must retain this notice and the # following text and disclaimers in all such redistributions of # the Apple Software. Neither the name, trademarks, service marks # or logos of Apple Inc. may be used to endorse or promote # products derived from the Apple Software without specific prior # written permission from Apple. Except as expressly stated in # this notice, no other rights or licenses, express or implied, # are granted by Apple herein, including but not limited to any # patent rights that may be infringed by your derivative works or # by other works in which the Apple Software may be incorporated. # # The Apple Software is provided by Apple on an "AS IS" basis. # APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING # WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING # THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN # COMBINATION WITH YOUR PRODUCTS. # # IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, # INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY # OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION # OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY # OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR # OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. import os import sys import BaseHTTPServer import string import email.feedparser global gImageDirPath class HTTPError (Exception): def __init__(self, statusCode, statusMessage=None): self.statusCode = statusCode self.statusMessage = statusMessage class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(self): print "do_GET:" self.send_error(404) def do_PUT(self): global gImageDirPath print "do_PUT:" try: assert self.path[0] == "/" fileName = self.path[1:] if fileName.translate(string.maketrans("", ""), string.ascii_letters + string.digits + "_") != ".": # Non-alphanumeric characters must be limited to exactly # one dot, that is, the extension delimiter. raise HTTPError(403) # These Content-Type comparisons are too naive (they should be # case insensitive, and they should ignore training parameters), # but this is only a test server, not a real server. contentType = self.headers.get('Content-Type') if contentType == None or (contentType not in ["image/png", "image/jpeg", "image/gif"]): raise HTTPError(406) if contentType == "image/png" and not fileName.endswith(".png"): raise HTTPError(406) if contentType == "image/jpeg" and not fileName.endswith(".jpg"): raise HTTPError(406) contentLength = self.headers.get('Content-Length') if contentLength == None: raise HTTPError(411) try: fileLength = int(contentLength) assert fileLength > 0 except: raise HTTPError(411) filePath = os.path.join(gImageDirPath, fileName) fileExists = os.path.exists(filePath) imageFile = open(filePath, "w") # Transfer the data in MB chunks to prevent us being run out of # memory with large files. bytesReadSoFar = 0 while bytesReadSoFar != fileLength: bytesToRead = (fileLength - bytesReadSoFar) if bytesToRead > (1024 * 1024): bytesToRead = (1024 * 1024) data = self.rfile.read(bytesToRead) bytesReadSoFar += len(data) imageFile.write(data) data = None imageFile.close() if fileExists: self.send_response(200) else: self.send_response(201) self.end_headers() except HTTPError, e: self.send_error(e.statusCode, e.statusMessage) except: self.send_error(500, "Internal Server Error") def do_POST(self): global gImageDirPath print "do_POST:" try: if self.path != "/cgi-bin/PostIt.py": raise HTTPError(403) # These Content-Type comparisons are too naive (they should be # case insensitive, and they should ignore training parameters), # but this is only a test server, not a real server. contentType = self.headers.get('Content-Type') if contentType == None or not contentType.startswith("multipart/form-data"): raise HTTPError(406) contentLength = self.headers.get('Content-Length') if contentLength == None: raise HTTPError(411) try: fileLength = int(contentLength) assert fileLength > 0 except: raise HTTPError(411) # Set up a MIME parser and feed it the Content-Type header. p = email.feedparser.FeedParser() p.feed("Content-Type: %s\r\n" % self.headers.get('Content-Type')) # Transfer the data in MB chunks to prevent us being run out of # memory with large files. Of course, a large file is probably # going to hoark and die because the MIME parser keeps everything # in memory. Good thing this is only a trivial test server. bytesReadSoFar = 0 while bytesReadSoFar != fileLength: bytesToRead = (fileLength - bytesReadSoFar) if bytesToRead > (1024 * 1024): bytesToRead = (1024 * 1024) data = self.rfile.read(bytesToRead) bytesReadSoFar += len(data) p.feed(data) data = None rootMessage = p.close() # Check some basic facts about the message. if not rootMessage.is_multipart() or len(rootMessage.defects) != 0: raise HTTPError(400) # Look for the "fileContents" part. fileContentsMessage = None for message in rootMessage.get_payload(): if message.get_param("name", header="Content-Disposition") == "fileContents": fileContentsMessage = message break if fileContentsMessage == None or fileContentsMessage.is_multipart(): raise HTTPError(400) # Extract the file name and check that it's reasonable. fileName = fileContentsMessage.get_param("filename", header="Content-Disposition") if fileName == None: raise HTTPError(400) if fileName.translate(string.maketrans("", ""), string.ascii_letters + string.digits + "_") != ".": # Non-alphanumeric characters must be limited to exactly # one dot, that is, the extension delimiter. raise HTTPError(403) # Verify the content type. contentType = fileContentsMessage.get('Content-Type') if contentType == None or (contentType not in ["image/png", "image/jpeg", "image/gif"]): raise HTTPError(406) if contentType == "image/png" and not fileName.endswith(".png"): raise HTTPError(406) if contentType == "image/jpeg" and not fileName.endswith(".jpg"): raise HTTPError(406) # Create the file with the supplied name and write the content to it. filePath = os.path.join(gImageDirPath, fileName) fileExists = os.path.exists(filePath) imageFile = open(filePath, "w") imageFile.write(fileContentsMessage.get_payload()) imageFile.close() if fileExists: self.send_response(200) else: self.send_response(201) self.end_headers() except HTTPError, e: self.send_error(e.statusCode, e.statusMessage) except: self.send_error(500, "Internal Server Error") def main(): global gImageDirPath gImageDirPath = os.path.join(os.curdir, "images") if not os.path.exists(gImageDirPath): os.mkdir(gImageDirPath) server = BaseHTTPServer.HTTPServer(('', 9000), MyHandler) print "Hello Cruel World!" try: server.serve_forever() except KeyboardInterrupt: print "Ouch!" if __name__ == "__main__": main()