forked from ab-smith/gruyere
-
Notifications
You must be signed in to change notification settings - Fork 0
/
gruyere.py
executable file
·871 lines (718 loc) · 27.6 KB
/
gruyere.py
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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
#!/usr/bin/env python3
"""Gruyere - a web application with holes.
Copyright 2017 Google Inc. All rights reserved.
This code is licensed under the
https://creativecommons.org/licenses/by-nd/3.0/us/
Creative Commons Attribution-No Derivative Works 3.0 United States license.
DO NOT COPY THIS CODE!
This application is a small self-contained web application with numerous
security holes. It is provided for use with the Web Application Exploits and
Defenses codelab. You may modify the code for your own use while doing the
codelab but you may not distribute the modified code. Brief excerpts of this
code may be used for educational or instructional purposes provided this
notice is kept intact. By using Gruyere you agree to the Terms of Service
https://www.google.com/intl/en/policies/terms/
"""
from __future__ import print_function
from future import standard_library
standard_library.install_aliases()
from builtins import str
__author__ = 'Bruce Leban'
# system modules
from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer
import cgi
import pickle
import os
import random
import sys
import threading
import re
import urllib.request, urllib.parse, urllib.error
from urllib.parse import urlparse
try:
sys.dont_write_bytecode = True
except AttributeError:
pass
# our modules
import data
import gtl
DB_FILE = '/stored-data.txt'
SECRET_FILE = '/secret.txt'
INSTALL_PATH = '.'
RESOURCE_PATH = 'resources'
SPECIAL_COOKIE = '_cookie'
SPECIAL_PROFILE = '_profile'
SPECIAL_DB = '_db'
SPECIAL_PARAMS = '_params'
SPECIAL_UNIQUE_ID = '_unique_id'
COOKIE_UID = 'uid'
COOKIE_ADMIN = 'is_admin'
COOKIE_AUTHOR = 'is_author'
# Set to True to cause the server to exit after processing the current url.
quit_server = False
# A global copy of the database so that _GetDatabase can access it.
stored_data = None
# The HTTPServer object.
http_server = None
# A secret value used to generate hashes to protect cookies from tampering.
cookie_secret = ''
# File extensions of resource files that we recognize.
RESOURCE_CONTENT_TYPES = {
'.css': 'text/css',
'.gif': 'image/gif',
'.htm': 'text/html',
'.html': 'text/html',
'.js': 'application/javascript',
'.jpeg': 'image/jpeg',
'.jpg': 'image/jpeg',
'.png': 'image/png',
'.ico': 'image/x-icon',
'.text': 'text/plain',
'.txt': 'text/plain',
}
def main():
_SetWorkingDirectory()
global quit_server
quit_server = False
quit_timer = threading.Timer(7200, lambda: _Exit('Timeout'))
quit_timer.start()
server_name = 'localhost'
server_port = 8008
# The unique id is created from a CSPRNG.
try:
r = random.SystemRandom()
except NotImplementedError:
_Exit('Could not obtain a CSPRNG source')
global server_unique_id
server_unique_id = str(r.randint(2**128, 2**(128+1)))
global http_server
http_server = HTTPServer((server_name, server_port),
GruyereRequestHandler)
print('''
Gruyere started...
http://%s:%d/
http://%s:%d/%s/''' % (
server_name, server_port, server_name, server_port,
server_unique_id), file=sys.stderr)
global stored_data
stored_data = _LoadDatabase()
while not quit_server:
try:
http_server.handle_request()
_SaveDatabase(stored_data)
except KeyboardInterrupt:
print('\nReceived KeyboardInterrupt', file=sys.stderr)
quit_server = True
print('\nClosing', file=sys.stderr)
http_server.socket.close()
_Exit('quit_server')
def _Exit(reason):
# use os._exit instead of sys.exit because this can't be trapped
print('\nExit: ' + reason, file=sys.stderr)
os._exit(0)
def _SetWorkingDirectory():
"""Set the working directory to the directory containing this file."""
if sys.path[0]:
os.chdir(sys.path[0])
def _LoadDatabase():
"""Load the database from stored-data.txt.
Returns:
The loaded database.
"""
try:
f = _Open(INSTALL_PATH, DB_FILE,'rb')
stored_data = pickle.load(f)
f.close()
except (IOError, ValueError):
_Log('Couldn\'t load data; expected the first time Gruyere is run')
stored_data = None
f = _Open(INSTALL_PATH, SECRET_FILE)
global cookie_secret
cookie_secret = f.readline()
f.close()
return stored_data
def _SaveDatabase(save_database):
"""Save the database to stored-data.txt.
Args:
save_database: the database to save.
"""
try:
f = _Open(INSTALL_PATH, DB_FILE, 'wb')
pickle.dump(save_database, f)
f.close()
except IOError:
_Log('Couldn\'t save data')
def sanitize_filename(filename):
"""Sanitize the filename to prevent path traversal."""
# Remove any leading/trailing whitespace and ensure no path traversal sequences
filename = os.path.basename(filename.strip())
return filename
def _Open(location, filename, mode='r'):
"""Open a file from a specific location.
Args:
location: The directory containing the file.
filename: The name of the file.
mode: File mode for open().
Returns:
A file object.
"""
# Sanitize the filename to prevent path traversal
filename = sanitize_filename(filename)
# Ensure the location ends with a directory separator
if not location.endswith(os.path.sep):
location += os.path.sep
return open(location + filename, mode)
class GruyereRequestHandler(BaseHTTPRequestHandler):
"""Handle a http request."""
# An empty cookie
NULL_COOKIE = {COOKIE_UID: None, COOKIE_ADMIN: False, COOKIE_AUTHOR: False}
ALLOWED_FILE_TYPES = {'image/jpeg', 'image/png'}
MAX_FILE_SIZE = 2 * 1024 * 1024 # 2 MB
# Urls that can only be accessed by administrators.
_PROTECTED_URLS = [
'/quit',
'/reset'
]
def _GetDatabase(self):
"""Gets the database."""
global stored_data
if not stored_data:
stored_data = data.DefaultData()
return stored_data
def _ResetDatabase(self):
"""Reset the database."""
# global stored_data
stored_data = data.DefaultData()
def _DoLogin(self, cookie, specials, params):
"""Handles the /login url: validates the user and creates a cookie.
Args:
cookie: The cookie for this request.
specials: Other special values for this request.
params: Cgi parameters.
"""
database = self._GetDatabase()
message = ''
if 'uid' in params and 'pw' in params:
uid = self._GetParameter(params, 'uid')
if uid in database:
if database[uid]['pw'] == self._GetParameter(params, 'pw'):
(cookie, new_cookie_text) = (
self._CreateCookie('GRUYERE', uid))
self._DoHome(cookie, specials, params, new_cookie_text)
return
message = 'Invalid user name or password.'
# not logged in
specials['_message'] = message
self._SendTemplateResponse('/login.gtl', specials, params)
def _DoLogout(self, cookie, specials, params):
"""Handles the /logout url: clears the cookie.
Args:
cookie: The cookie for this request.
specials: Other special values for this request.
params: Cgi parameters.
"""
(cookie, new_cookie_text) = (
self._CreateCookie('GRUYERE', None))
self._DoHome(cookie, specials, params, new_cookie_text)
def _Do(self, cookie, specials, params):
"""Handles the home page (http://localhost/).
Args:
cookie: The cookie for this request.
specials: Other special values for this request.
params: Cgi parameters.
"""
self._DoHome(cookie, specials, params)
def _DoHome(self, cookie, specials, params, new_cookie_text=None):
"""Renders the home page.
Args:
cookie: The cookie for this request.
specials: Other special values for this request.
params: Cgi parameters.
new_cookie_text: New cookie.
"""
database = self._GetDatabase()
specials[SPECIAL_COOKIE] = cookie
if cookie and cookie.get(COOKIE_UID):
specials[SPECIAL_PROFILE] = database.get(cookie[COOKIE_UID])
else:
specials.pop(SPECIAL_PROFILE, None)
self._SendTemplateResponse(
'/home.gtl', specials, params, new_cookie_text)
def _DoBadUrl(self, path, cookie, specials, params):
"""Handles invalid urls: displays an appropriate error message.
Args:
path: The invalid url.
cookie: The cookie for this request.
specials: Other special values for this request.
params: Cgi parameters.
"""
self._SendError('Invalid request: %s' % (path,), cookie, specials, params)
def _DoQuitserver(self, cookie, specials, params):
"""Handles the /quitserver url for administrators to quit the server.
Args:
cookie: The cookie for this request. (unused)
specials: Other special values for this request. (unused)
params: Cgi parameters. (unused)
"""
global quit_server
quit_server = True
self._SendTextResponse('Server quit.', None)
def _AddParameter(self, name, params, data_dict, default=None):
"""Transfers a value (with a default) from the parameters to the data."""
if params.get(name):
data_dict[name] = params[name][0]
elif default is not None:
data_dict[name] = default
def _GetParameter(self, params, name, default=None):
"""Gets a parameter value with a default."""
if params.get(name):
return params[name][0]
return default
def _GetSnippets(self, cookie, specials, create=False):
"""Returns all of the user's snippets."""
database = self._GetDatabase()
try:
profile = database[cookie[COOKIE_UID]]
if create and 'snippets' not in profile:
profile['snippets'] = []
snippets = profile['snippets']
except (KeyError, TypeError):
_Log('Error getting snippets')
return None
return snippets
def _DoNewsnippet2(self, cookie, specials, params):
"""Handles the /newsnippet2 url: actually add the snippet.
Args:
cookie: The cookie for this request.
specials: Other special values for this request.
params: Cgi parameters.
"""
snippet = self._GetParameter(params, 'snippet')
if not snippet:
self._SendError('No snippet!', cookie, specials, params)
else:
snippets = self._GetSnippets(cookie, specials, True)
if snippets is not None:
snippets.insert(0, snippet)
self._SendRedirect('/snippets.gtl', specials[SPECIAL_UNIQUE_ID])
def _DoDeletesnippet(self, cookie, specials, params):
"""Handles the /deletesnippet url: delete the indexed snippet.
Args:
cookie: The cookie for this request.
specials: Other special values for this request.
params: Cgi parameters.
"""
index = self._GetParameter(params, 'index')
snippets = self._GetSnippets(cookie, specials)
try:
del snippets[int(index)]
except (IndexError, TypeError, ValueError):
self._SendError(
'Invalid index (%s)' % (index,),
cookie, specials, params)
return
self._SendRedirect('/snippets.gtl', specials[SPECIAL_UNIQUE_ID])
def _DoSaveprofile(self, cookie, specials, params):
"""Saves the user's profile.
Args:
cookie: The cookie for this request.
specials: Other special values for this request.
params: Cgi parameters.
If the 'action' cgi parameter is 'new', then this is creating a new user
and it's an error if the user already exists. If action is 'update', then
this is editing an existing user's profile and it's an error if the user
does not exist.
"""
# build new profile
profile_data = {}
uid = self._GetParameter(params, 'uid', cookie[COOKIE_UID])
newpw = self._GetParameter(params, 'pw')
self._AddParameter('name', params, profile_data, uid)
self._AddParameter('pw', params, profile_data)
self._AddParameter('is_author', params, profile_data)
self._AddParameter('is_admin', params, profile_data)
self._AddParameter('private_snippet', params, profile_data)
self._AddParameter('icon', params, profile_data)
self._AddParameter('web_site', params, profile_data)
self._AddParameter('color', params, profile_data)
# Each case below has to set either error or redirect
database = self._GetDatabase()
message = None
new_cookie_text = None
action = self._GetParameter(params, 'action')
if action == 'new':
if uid in database:
message = 'User already exists.'
else:
message = None
if len(newpw) < 12:
message = 'Password must be at least 12 characters long and include uppercase letters, lowercase letters, digits, and special characters'
if not re.search(r'[A-Z]', newpw):
message = 'Password must be at least 12 characters long and include uppercase letters, lowercase letters, digits, and special characters'
if not re.search(r'[a-z]', newpw):
message = 'Password must be at least 12 characters long and include uppercase letters, lowercase letters, digits, and special characters'
if not re.search(r'[0-9]', newpw):
message = 'Password must be at least 12 characters long and include uppercase letters, lowercase letters, digits, and special characters'
if not re.search(r'[@$!%*?&]', newpw):
message = 'Password must be at least 12 characters long and include uppercase letters, lowercase letters, digits, and special characters'
if message is None:
profile_data['pw'] = newpw
database[uid] = profile_data
(cookie, new_cookie_text) = self._CreateCookie('GRUYERE', uid)
message = 'Account created.' # error message can also indicates success
elif action == 'update':
if uid not in database:
message = 'User does not exist.'
elif (newpw and database[uid]['pw'] != self._GetParameter(params, 'oldpw')
and not cookie.get(COOKIE_ADMIN)):
# must be admin or supply old pw to change password
message = 'Incorrect password.'
else:
if newpw:
profile_data['pw'] = newpw
database[uid].update(profile_data)
redirect = '/'
else:
message = 'Invalid request'
_Log('SetProfile(%s, %s): %s' %(str(uid), str(action), str(message)))
if message:
self._SendError(message, cookie, specials, params, new_cookie_text)
else:
self._SendRedirect(redirect, specials[SPECIAL_UNIQUE_ID])
def _SendHtmlResponse(self, html, new_cookie_text=None):
"""Sends the provided html response with appropriate headers.
Args:
html: The response.
new_cookie_text: New cookie to set.
"""
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.send_header('Pragma', 'no-cache')
if new_cookie_text:
self.send_header('Set-Cookie', new_cookie_text)
self.send_header('X-XSS-Protection', '0')
self.end_headers()
self.wfile.write(html.encode())
def _SendTextResponse(self, text, new_cookie_text=None):
"""Sends a verbatim text response."""
self._SendHtmlResponse('<pre>' + cgi.escape(text) + '</pre>',
new_cookie_text)
def _SendTemplateResponse(self, filename, specials, params,
new_cookie_text=None):
"""Sends a response using a gtl template.
Args:
filename: The template file.
specials: Other special values for this request.
params: Cgi parameters.
new_cookie_text: New cookie to set.
"""
f = None
try:
f = _Open(RESOURCE_PATH, filename)
template = f.read()
finally:
if f: f.close()
self._SendHtmlResponse(
gtl.ExpandTemplate(template, specials, params),
new_cookie_text)
def _SendFileResponse(self, filename, cookie, specials, params):
"""Sends the contents of a file.
Args:
filename: The file to send.
cookie: The cookie for this request.
specials: Other special values for this request.
params: Cgi parameters.
"""
content_type = None
if filename.endswith('.gtl'):
self._SendTemplateResponse(filename, specials, params)
return
name_only = filename[filename.rfind('/'):]
extension = name_only[name_only.rfind('.'):]
if '.' not in extension:
content_type = 'text/plain'
elif extension in RESOURCE_CONTENT_TYPES:
content_type = RESOURCE_CONTENT_TYPES[extension]
else:
self._SendError(
'Unrecognized file type (%s).' % (filename,),
cookie, specials, params)
return
f = None
try:
f = _Open(RESOURCE_PATH, filename, 'rb')
self.send_response(200)
self.send_header('Content-type', content_type)
# Always cache static resources
self.send_header('Cache-control', 'public, max-age=7200')
self.send_header('X-XSS-Protection', '0')
self.end_headers()
self.wfile.write(f.read())
finally:
if f: f.close()
def _SendError(self, message, cookie, specials, params, new_cookie_text=None):
"""Sends an error message (using the error.gtl template).
Args:
message: The error to display.
cookie: The cookie for this request. (unused)
specials: Other special values for this request.
params: Cgi parameters.
new_cookie_text: New cookie to set.
"""
specials['_message'] = message
self._SendTemplateResponse(
'/error.gtl', specials, params, new_cookie_text)
def _CreateCookie(self, cookie_name, uid):
"""Creates a cookie for this user.
Args:
cookie_name: Cookie to create.
uid: The user.
Returns:
(cookie, new_cookie_text).
The cookie contains all the information we need to know about
the user for normal operations, including whether or not the user
should have access to the authoring pages or the admin pages.
The cookie is signed with a hash function.
"""
if uid is None:
return (self.NULL_COOKIE, cookie_name + '=; path=/; HttpOnly')
database = self._GetDatabase()
profile = database[uid]
if profile.get('is_author', False):
is_author = 'author'
else:
is_author = ''
if profile.get('is_admin', False):
is_admin = 'admin'
else:
is_admin = ''
c = {COOKIE_UID: uid, COOKIE_ADMIN: is_admin, COOKIE_AUTHOR: is_author}
c_data = '%s|%s|%s' % (uid, is_admin, is_author)
# global cookie_secret; only use positive hash values
h_data = str(hash(cookie_secret + c_data) & 0x7FFFFFF)
c_text = '%s=%s|%s; path=/; HttpOnly' % (cookie_name, h_data, c_data)
return (c, c_text)
def _GetCookie(self, cookie_name):
"""Reads, verifies and parses the cookie.
Args:
cookie_name: The cookie to get.
Returns:
a dict containing user, is_admin, and is_author if the cookie
is present and valid. Otherwise, None.
"""
cookies = self.headers.get('Cookie')
if isinstance(cookies, str):
for c in cookies.split(';'):
matched_cookie = self._MatchCookie(cookie_name, c)
if matched_cookie:
return self._ParseCookie(matched_cookie)
return self.NULL_COOKIE
def _MatchCookie(self, cookie_name, cookie):
"""Matches the cookie.
Args:
cookie_name: The name of the cookie.
cookie: The full cookie (name=value).
Returns:
The cookie if it matches or None if it doesn't match.
"""
try:
(cn, cd) = cookie.strip().split('=', 1)
if cn != cookie_name:
return None
except (IndexError, ValueError):
return None
return cd
def _ParseCookie(self, cookie):
"""Parses the cookie and returns NULL_COOKIE if it's invalid.
Args:
cookie: The text of the cookie.
Returns:
A map containing the values in the cookie.
"""
try:
(hashed, cookie_data) = cookie.split('|', 1)
# global cookie_secret
if hashed != str(hash(cookie_secret + cookie_data) & 0x7FFFFFF):
return self.NULL_COOKIE
values = cookie_data.split('|')
return {
COOKIE_UID: values[0],
COOKIE_ADMIN: values[1] == 'admin',
COOKIE_AUTHOR: values[2] == 'author',
}
except (IndexError, ValueError):
return self.NULL_COOKIE
def _DoReset(self, cookie, specials, params): # debug only; resets this db
"""Handles the /reset url for administrators to reset the database.
Args:
cookie: The cookie for this request. (unused)
specials: Other special values for this request. (unused)
params: Cgi parameters. (unused)
"""
self._ResetDatabase()
self._SendTextResponse('Server reset to default values...', None)
def _DoUpload2(self, cookie, specials, params):
"""Handles the /upload2 url: finish the upload and save the file.
Args:
cookie: The cookie for this request.
specials: Other special values for this request.
params: Cgi parameters. (unused)
"""
(filename, file_data) = self._ExtractFileFromRequest()
filename = sanitize_filename(filename) # Sanitize filename
directory = self._MakeUserDirectory(cookie[COOKIE_UID])
message = None
url = None
if not self._is_allowed_file_type(file_data):
message = 'File type not allowed. Only JPG and PNG files are allowed.'
_Log(message)
elif len(file_data) > self.MAX_FILE_SIZE:
message = 'File size exceeds the 2MB limit.'
_Log(message)
else:
try:
f = _Open(directory, filename, 'wb')
f.write(file_data)
f.close()
(host, port) = http_server.server_address
url = 'http://%s:%d/%s/%s/%s' % (
host, port, specials[SPECIAL_UNIQUE_ID], cookie[COOKIE_UID], filename)
except IOError as ex:
message = 'Couldn\'t write file %s: %s' % (filename, ex.message)
_Log(message)
specials['_message'] = message
self._SendTemplateResponse(
'/upload2.gtl', specials,
{'url': url})
def _ExtractFileFromRequest(self):
"""Extracts the file from an upload request.
Returns:
(filename, file_data)
"""
form = cgi.FieldStorage(
fp=self.rfile,
headers=self.headers,
environ={'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': self.headers.get('content-type')})
upload_file = form['upload_file']
file_data = upload_file.file.read()
return (upload_file.filename, file_data)
def _is_allowed_file_type(self, file_data):
"""Check if the file type is allowed.
Args:
file_data: The file data to check.
Returns:
True if the file type is allowed, False otherwise."""
import magic
mime = magic.Magic(mime=True)
file_type = mime.from_buffer(file_data)
return file_type in self.ALLOWED_FILE_TYPES
def _MakeUserDirectory(self, uid):
"""Creates a separate directory for each user to avoid upload conflicts.
Args:
uid: The user to create a directory for.
Returns:
The new directory path (/uid/).
"""
directory = RESOURCE_PATH + os.sep + str(uid) + os.sep
try:
print('mkdir: ', directory)
os.mkdir(directory)
# throws an exception if directory already exists,
# however exception type varies by platform
except Exception:
pass # just ignore it if it already exists
return directory
def _SendRedirect(self, url, unique_id):
"""Sends a 302 redirect.
Automatically adds the unique_id.
Args:
url: The location to redirect to which must start with '/'.
unique_id: The unique id to include in the url.
"""
if not url:
url = '/'
url = '/' + unique_id + url
self.send_response(302)
self.send_header('Location', url)
self.send_header('Pragma', 'no-cache')
self.send_header('Content-type', 'text/html')
self.send_header('X-XSS-Protection', '0')
self.end_headers()
res = u'''<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML//EN'>
<html><body>
<title>302 Redirect</title>
Redirected <a href="%s">here</a>
</body></html>''' % (url)
self.wfile.write(res.encode())
def _GetHandlerFunction(self, path):
try:
return getattr(GruyereRequestHandler, '_Do' + path[1:].capitalize())
except AttributeError:
return None
def do_POST(self): # part of BaseHTTPRequestHandler interface
self.DoGetOrPost()
def do_GET(self): # part of BaseHTTPRequestHandler interface
self.DoGetOrPost()
def DoGetOrPost(self):
"""Validate an http get or post request and call HandleRequest."""
url = urlparse(self.path)
path = url[2]
query = url[4]
#Network Security settings
allowed_ips = ['127.0.0.1']
request_ip = self.client_address[0]
if request_ip not in allowed_ips:
print((
'DANGER! Request from bad ip: ' + request_ip), file=sys.stderr)
_Exit('bad_ip')
if (server_unique_id not in path
and path != '/favicon.ico'):
if path == '' or path == '/':
self._SendRedirect('/', server_unique_id)
return
else:
print((
'DANGER! Request without unique id: ' + path), file=sys.stderr)
#_Exit('bad_id')
path = path.replace('/' + server_unique_id, '', 1)
self.HandleRequest(path, query, server_unique_id)
def HandleRequest(self, path, query, unique_id):
"""Handles an http request.
Args:
path: The path part of the url, with leading slash.
query: The query part of the url, without leading question mark.
unique_id: The unique id from the url.
"""
path = urllib.parse.unquote(path)
if not path:
self._SendRedirect('/', server_unique_id)
return
params = urllib.parse.parse_qs(query) # url.query
specials = {}
cookie = self._GetCookie('GRUYERE')
database = self._GetDatabase()
specials[SPECIAL_COOKIE] = cookie
specials[SPECIAL_DB] = database
specials[SPECIAL_PROFILE] = database.get(cookie.get(COOKIE_UID))
specials[SPECIAL_PARAMS] = params
specials[SPECIAL_UNIQUE_ID] = unique_id
if path in self._PROTECTED_URLS and not cookie[COOKIE_ADMIN]:
self._SendError('Invalid request', cookie, specials, params)
return
try:
handler = self._GetHandlerFunction(path)
if callable(handler):
(handler)(self, cookie, specials, params)
else:
try:
self._SendFileResponse(path, cookie, specials, params)
except IOError:
self._DoBadUrl(path, cookie, specials, params)
except KeyboardInterrupt:
_Exit('KeyboardInterrupt')
def _Log(message):
print(message, file=sys.stderr)
if __name__ == '__main__':
main()