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