-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathblobserver.py
More file actions
214 lines (187 loc) · 7 KB
/
blobserver.py
File metadata and controls
214 lines (187 loc) · 7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#!/usr/bin/python
import sys
import MySQLdb
from random import seed, randint
seed()
from urllib import unquote_plus as unescapeURL
import cgi
logfile = '/var/log/blobserver.log'
def parseQueryString(s):
q = s.split('&')
d = {}
if len(q) > 0:
for e in q:
s = e.split('=')
if len(s) > 1:
d[s[0]] = unescapeURL('='.join(s[1:]))
return d
#
# MIME Encapsulation
#
# HTTP POST encapsulates uploaded files
#
class Encapsulated:
def __init__(self, request_body):
open(logfile,'a').write(request_body+'\n')
s = request_body.split('\n')
boundary = s[0]
content_disposition = s[1]
content_type = s[2]
header = '\n'.join(s[:4])
self.data = request_body[len(header)+1:len(request_body)-len(boundary)-3]
#
# methods for ID and key generation
#
def randomChar(onlyLowercase=True):
if onlyLowercase:
return chr(97+randint(0,25))
else:
return chr(65+(32*randint(0,1))+randint(0,25))
# True/False corresponds to onlyLowercase = True/False
vocals = {
True: ['a','e','i','o','u'],
False: ['A','E','I','O','U','a','e','i','o','u']
}
def randomVocal(onlyLowercase=True):
return vocals[onlyLowercase][randint(0,len(vocals[onlyLowercase])-1)]
def randomConsonant(onlyLowercase=True):
c = 'a'
while c in vocals[onlyLowercase]:
c = randomChar(onlyLowercase)
return c
def randomID():
return randomConsonant()+randomVocal()+randomConsonant()+randomVocal()+randomConsonant()
def randomKey():
return ''.join([randomChar(onlyLowercase=False) for i in range(5)])
#
# HTML form for manual BLOB (file) upload
#
def uploadForm(environ, start_response, debug=False):
page = """<html>
<body style="padding-top: 15%; padding-left: 35%;">
<form name="upload" method="post" enctype="multipart/form-data">
<h1>Upload a file: </h1>
<input name="blob" type="file" onchange="document.forms['upload'].submit();"/>
</form>
</body>
</html>"""
# show WSGI environment variables
# for debugging
if debug:
page += "\n<!--\n"+("\n".join([str(key)+": "+str(environ[key]) for key in environ.keys()]))+"\n--!>"
start_response('200 OK', [('Content-Type', 'text/html')])
return [page]
#
# http://www.python.org/dev/peps/pep-0333/
# http://www.online-tutorials.net/mysql/mysql-zugriff-mit-python/sourcecodes-t-130-316.html
#
def upload(environ, start_response, mysql_opts):
# reject anything that is not GET or POST
if environ['REQUEST_METHOD'] not in ['GET','POST']:
start_response('400 Bad Request', [('Content-Type', 'text/plain')])
return ['400 Bad Request']
# upload via form or URL
if environ['REQUEST_METHOD'] == 'GET':
query = parseQueryString(environ['QUERY_STRING'])
if query.has_key('blob'):
BLOB = query['blob']
else:
return uploadForm(environ, start_response)
# upload via HTTP POST
elif environ['REQUEST_METHOD'] == 'POST':
try:
request_body_size = int(environ.get('CONTENT_LENGTH', 0))
except (ValueError):
# the environment variable CONTENT_LENGTH may be empty or missing
request_body_size = 0
request_body = environ['wsgi.input'].read(request_body_size)
BLOB = Encapsulated(request_body).data
# BLOB is empty
if len(BLOB) == 0:
start_response('200 OK', [('Content-Type', 'text/plain')])
return ['Submitted BLOB is empty']
# BLOB is too large
if len(BLOB) > 4*(1024**2):
# https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
start_response('200 OK', [('Content-Type', 'text/plain')])
return ['Rejecting too large BLOB']
# save BLOB to database
ID = randomID()
IP = environ['HTTP_X_FORWARDED_FOR']
agent = environ['HTTP_USER_AGENT']
key = randomKey()
mysql = MySQLdb.connect(mysql_opts['host'], mysql_opts['user'], mysql_opts['pass'], mysql_opts['db'])
cursor = mysql.cursor()
cmd = "INSERT INTO `%s` (`ID`,`from`,`agent`,`blob`,`owner_key`,`created`) VALUES ('%s','%s','%s','%s','%s',NOW());" % (mysql_opts['table:blobs'],ID,IP,mysql.escape_string(agent),mysql.escape_string(BLOB),key)
open(logfile,'a').write(cmd+'\n')
cursor.execute(cmd)
start_response('200 OK', [('Content-Type', 'text/html')])
#
# return URL and QR code
# http://davidshimjs.github.io/qrcodejs/
#
return ["""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta name="id" content=\""""+ID+"""\"/>
<meta name="owner_key" content=\""""+key+"""\"/>
<script>
"""+open(environ['SCRIPT_FILENAME'][:-len('/blobserver.py')]+'/qrcode.js').read()+"""
</script>
</head>
<body id='body'>
<h1>Upload complete</h1>
Your BLOB is available here:
<a href="http://blob.interoberlin.de/download?id="""+ID+"""\">http://blob.interoberlin.de/download?id="""+ID+"""</a><br/>
<br/>
To edit or remove this BLOB later, remember the following, case-sensitive key: <b style="font-family:'Courier New';">"""+key+"""</b><br/>
<br/>
<a href="upload">Upload more files</a>
<br/><br/>
<h1>A QR code for your link:</h1>
<script>
new QRCode(document.getElementById('body'),'http://blob.interoberlin.de/download?id="""+ID+"""');
</script>
</body>
</html>"""]
#
# download?id=vebob
#
# return BLOB
#
def download(environ, start_response, mysql_opts):
# id specified ?
query = parseQueryString(environ['QUERY_STRING'])
if not query.has_key('id'):
start_response('200 OK', [('Content-Type', 'text/html')])
return ['Usage: <a href="download?id=vebob">download?id=abcde</a>']
# query mysql database
mysql = MySQLdb.connect(mysql_opts['host'], mysql_opts['user'], mysql_opts['pass'], mysql_opts['db'])
cursor = mysql.cursor()
ID = query['id']
try:
# fetch BLOB
cursor.execute("SELECT `BLOB` FROM `%s` WHERE `ID`='%s'" % (mysql_opts['table:blobs'],ID))
BLOB = cursor.fetchone()[0]
# update access counter
cursor.execute("UPDATE `%s` SET access_counter = access_counter + 1 WHERE `ID`='%s'" % (mysql_opts['table:blobs'],ID))
cursor.execute("UPDATE `%s` SET accessed = NOW() WHERE `ID`='%s'" % (mysql_opts['table:blobs'],ID))
# return BLOB
start_response('200 OK', [('Content-Type', 'text/plain')])
return [BLOB]
except:
start_response("404 Not Found", [('Content-Type', 'text/plain')])
return ['404 Not Found: Unable to fetch BLOB from database']
#
# WSGI entry point
#
def application(environ, start_response):
sys.path.append(environ['SCRIPT_FILENAME'][:-len('/blobserver.py')])
from settings import mysql_opts
if environ['PATH_INFO'] == '/upload':
return upload(environ, start_response, mysql_opts)
elif environ['PATH_INFO'] == '/download':
return download(environ, start_response, mysql_opts)
else:
start_response("302 Moved Permanently", [('Location','http://blob.interoberlin.de/upload')])
return []