ImageReceiveServer.py

#! /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):
        self.send_error(404)
 
    def do_PUT(self):
        global gImageDirPath
        
        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
        
        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 ""
 
if __name__ == "__main__":
    main()