simple HTTP server with upload

  1 #!/usr/bin/env python
  2 
  3 """Simple HTTP Server With Upload.
  4 
  5 https://github.com/tualatrix/tools/blob/master/SimpleHTTPServerWithUpload.py
  6 
  7 This module builds on BaseHTTPServer by implementing the standard GET
  8 and HEAD requests in a fairly straightforward manner.
  9 
 10 """
 11 
 12 
 13 import os
 14 import posixpath
 15 import BaseHTTPServer
 16 import urllib
 17 import cgi
 18 import shutil
 19 import mimetypes
 20 import re
 21 
 22 __version__ = "0.1"
 23 __all__ = ["SimpleHTTPRequestHandler"]
 24 __author__ = "bones7456"
 25 __home_page__ = "http://li2z.cn/"
 26 
 27 try:
 28     from cStringIO import StringIO
 29 except ImportError:
 30     from StringIO import StringIO
 31 
 32 
 33 class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
 34 
 35     """Simple HTTP request handler with GET/HEAD/POST commands.
 36 
 37     This serves files from the current directory and any of its
 38     subdirectories.  The MIME type for files is determined by
 39     calling the .guess_type() method. And can reveive file uploaded
 40     by client.
 41 
 42     The GET/HEAD/POST requests are identical except that the HEAD
 43     request omits the actual contents of the file.
 44 
 45     """
 46 
 47     server_version = "SimpleHTTPWithUpload/" + __version__
 48 
 49     def do_GET(self):
 50         """Serve a GET request."""
 51         f = self.send_head()
 52         if f:
 53             self.copyfile(f, self.wfile)
 54             f.close()
 55 
 56     def do_HEAD(self):
 57         """Serve a HEAD request."""
 58         f = self.send_head()
 59         if f:
 60             f.close()
 61 
 62     def do_POST(self):
 63         """Serve a POST request."""
 64         r, info = self.deal_post_data()
 65         print r, info, "by: ", self.client_address
 66         f = StringIO()
 67         f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
 68         f.write("<html>\n<meta charset='UTF-8'>\n<title>Upload Result Page</title>\n")
 69         f.write("<html>\n<title>Upload Result Page</title>\n")
 70         f.write("<body>\n<h2>Upload Result Page</h2>\n")
 71         f.write("<hr>\n")
 72         if r:
 73             f.write("<strong>Success:</strong>")
 74         else:
 75             f.write("<strong>Failed:</strong>")
 76         f.write(info)
 77         f.write("<br><a href=\"%s\">back</a>" % self.headers['referer'])
 78         f.write("<hr><small>Powered By: bones7456, check new version at ")
 79         f.write("<a href=\"http://li2z.cn/?s=SimpleHTTPServerWithUpload\">")
 80         f.write("here</a>.</small></body>\n</html>\n")
 81         length = f.tell()
 82         f.seek(0)
 83         self.send_response(200)
 84         self.send_header("Content-type", "text/html")
 85         self.send_header("Content-Length", str(length))
 86         self.end_headers()
 87         if f:
 88             self.copyfile(f, self.wfile)
 89             f.close()
 90 
 91     def deal_post_data(self):
 92         boundary = self.headers.plisttext.split("=")[1]
 93         remainbytes = int(self.headers['content-length'])
 94         line = self.rfile.readline()
 95         remainbytes -= len(line)
 96         if boundary not in line:
 97             return (False, "Content NOT begin with boundary")
 98         line = self.rfile.readline()
 99         remainbytes -= len(line)
100         fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line)
101         if not fn:
102             return (False, "Can't find out file name...")
103         path = self.translate_path(self.path)
104         fn = os.path.join(path, fn[0])
105         while os.path.exists(fn):
106             fn += "_"
107         line = self.rfile.readline()
108         remainbytes -= len(line)
109         line = self.rfile.readline()
110         remainbytes -= len(line)
111         try:
112             out = open(fn, 'wb')
113         except IOError:
114             return (False, "Can't create file to write, do you have permission to write?")
115 
116         preline = self.rfile.readline()
117         remainbytes -= len(preline)
118         while remainbytes > 0:
119             line = self.rfile.readline()
120             remainbytes -= len(line)
121             if boundary in line:
122                 preline = preline[0:-1]
123                 if preline.endswith('\r'):
124                     preline = preline[0:-1]
125                 out.write(preline)
126                 out.close()
127                 return (True, "File '%s' upload success!" % fn)
128             else:
129                 out.write(preline)
130                 preline = line
131         return (False, "Unexpect Ends of data.")
132 
133     def send_head(self):
134         """Common code for GET and HEAD commands.
135 
136         This sends the response code and MIME headers.
137 
138         Return value is either a file object (which has to be copied
139         to the outputfile by the caller unless the command was HEAD,
140         and must be closed by the caller under all circumstances), or
141         None, in which case the caller has nothing further to do.
142 
143         """
144         path = self.translate_path(self.path)
145         f = None
146         if os.path.isdir(path):
147             if not self.path.endswith('/'):
148                 # redirect browser - doing basically what apache does
149                 self.send_response(301)
150                 self.send_header("Location", self.path + "/")
151                 self.end_headers()
152                 return None
153             for index in "index.html", "index.htm":
154                 index = os.path.join(path, index)
155                 if os.path.exists(index):
156                     path = index
157                     break
158             else:
159                 return self.list_directory(path)
160         ctype = self.guess_type(path)
161         try:
162             # Always read in binary mode. Opening files in text mode may cause
163             # newline translations, making the actual size of the content
164             # transmitted *less* than the content-length!
165             f = open(path, 'rb')
166         except IOError:
167             self.send_error(404, "File not found")
168             return None
169         self.send_response(200)
170         self.send_header("Content-type", ctype)
171         fs = os.fstat(f.fileno())
172         self.send_header("Content-Length", str(fs[6]))
173         self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
174         self.end_headers()
175         return f
176 
177     def list_directory(self, path):
178         """Helper to produce a directory listing (absent index.html).
179 
180         Return value is either a file object, or None (indicating an
181         error).  In either case, the headers are sent, making the
182         interface the same as for send_head().
183 
184         """
185         try:
186             list = os.listdir(path)
187         except os.error:
188             self.send_error(404, "No permission to list directory")
189             return None
190         list.sort(key=lambda a: a.lower())
191         f = StringIO()
192         displaypath = cgi.escape(urllib.unquote(self.path))
193         f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
194         f.write("<html><meta charset='UTF-8'>\n<title>Directory listing for %s</title>\n" % displaypath)
195         f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
196         f.write("<hr>\n")
197         f.write("<form ENCTYPE=\"multipart/form-data\" method=\"post\">")
198         f.write("<input name=\"file\" type=\"file\"/>")
199         f.write("<input type=\"submit\" value=\"upload\"/></form>\n")
200         f.write("<hr>\n<ul>\n")
201         for name in list:
202             if '.py' not in name and '.html' not in name:
203                 fullname = os.path.join(path, name)
204                 displayname = linkname = name
205             # Append / for directories or @ for symbolic links
206                 if os.path.isdir(fullname):
207                     displayname = name + "/"
208                     linkname = name + "/"
209                 if os.path.islink(fullname):
210                     displayname = name + "@"
211                 # Note: a link to a directory displays with @ and links with /
212                 f.write('<li><a href="%s">%s</a>\n'
213                         % (urllib.quote(linkname), cgi.escape(displayname)))
214         f.write("</ul>\n<hr>\n</body>\n</html>\n")
215         length = f.tell()
216         f.seek(0)
217         self.send_response(200)
218         self.send_header("Content-type", "text/html")
219         self.send_header("Content-Length", str(length))
220         self.end_headers()
221         return f
222 
223     def translate_path(self, path):
224         """Translate a /-separated PATH to the local filename syntax.
225 
226         Components that mean special things to the local file system
227         (e.g. drive or directory names) are ignored.  (XXX They should
228         probably be diagnosed.)
229 
230         """
231         # abandon query parameters
232         path = path.split('?', 1)[0]
233         path = path.split('#', 1)[0]
234         path = posixpath.normpath(urllib.unquote(path))
235         words = path.split('/')
236         words = filter(None, words)
237         path = os.getcwd()
238         for word in words:
239             drive, word = os.path.splitdrive(word)
240             head, word = os.path.split(word)
241             if word in (os.curdir, os.pardir):
242                 continue
243             path = os.path.join(path, word)
244         return path
245 
246     def copyfile(self, source, outputfile):
247         """Copy all data between two file objects.
248 
249         The SOURCE argument is a file object open for reading
250         (or anything with a read() method) and the DESTINATION
251         argument is a file object open for writing (or
252         anything with a write() method).
253 
254         The only reason for overriding this would be to change
255         the block size or perhaps to replace newlines by CRLF
256         -- note however that this the default server uses this
257         to copy binary data as well.
258 
259         """
260         shutil.copyfileobj(source, outputfile)
261 
262     def guess_type(self, path):
263         """Guess the type of a file.
264 
265         Argument is a PATH (a filename).
266 
267         Return value is a string of the form type/subtype,
268         usable for a MIME Content-type header.
269 
270         The default implementation looks the file's extension
271         up in the table self.extensions_map, using application/octet-stream
272         as a default; however it would be permissible (if
273         slow) to look inside the data to make a better guess.
274 
275         """
276 
277         base, ext = posixpath.splitext(path)
278         if ext in self.extensions_map:
279             return self.extensions_map[ext]
280         ext = ext.lower()
281         if ext in self.extensions_map:
282             return self.extensions_map[ext]
283         else:
284             return self.extensions_map['']
285 
286     if not mimetypes.inited:
287         mimetypes.init()  # try to read system mime.types
288     extensions_map = mimetypes.types_map.copy()
289     extensions_map.update({
290         '': 'application/octet-stream',  # Default
291         '.py': 'text/plain',
292         '.c': 'text/plain',
293         '.h': 'text/plain',
294         })
295 
296 
297 def test(HandlerClass=SimpleHTTPRequestHandler,
298          ServerClass=BaseHTTPServer.HTTPServer):
299     BaseHTTPServer.test(HandlerClass, ServerClass)
300 
301 if __name__ == '__main__':
302     test()

 

posted @ 2017-04-17 13:11  Fatt  阅读(869)  评论(0编辑  收藏  举报