summaryrefslogtreecommitdiff
path: root/backend/python/src/clipperz.py
Unidiff
Diffstat (limited to 'backend/python/src/clipperz.py') (more/less context) (ignore whitespace changes)
-rw-r--r--backend/python/src/clipperz.py708
1 files changed, 708 insertions, 0 deletions
diff --git a/backend/python/src/clipperz.py b/backend/python/src/clipperz.py
new file mode 100644
index 0000000..c8d91de
--- a/dev/null
+++ b/backend/python/src/clipperz.py
@@ -0,0 +1,708 @@
1 #
2 #Copyright 2008-2011 Clipperz Srl
3 #
4 #This file is part of Clipperz's Javascript Crypto Library.
5 #Javascript Crypto Library provides web developers with an extensive
6 #and efficient set of cryptographic functions. The library aims to
7 #obtain maximum execution speed while preserving modularity and
8 #reusability.
9 #For further information about its features and functionalities please
10 #refer to http://www.clipperz.com
11 #
12 #* Javascript Crypto Library is free software: you can redistribute
13 # it and/or modify it under the terms of the GNU Affero General Public
14 # License as published by the Free Software Foundation, either version
15 # 3 of the License, or (at your option) any later version.
16 #
17 #* Javascript Crypto Library is distributed in the hope that it will
18 # be useful, but WITHOUT ANY WARRANTY; without even the implied
19 # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
20 # See the GNU Affero General Public License for more details.
21 #
22 #* You should have received a copy of the GNU Affero General Public
23 # License along with Javascript Crypto Library. If not, see
24 # <http://www.gnu.org/licenses/>.
25 #
26
27import os
28import cgi
29import wsgiref.handlers
30
31import datetime
32import uuid
33import random
34import hashlib
35
36import logging
37
38from google.appengine.api import users
39from google.appengine.ext import webapp
40from google.appengine.ext import db
41from google.appengine.ext.webapp import template
42
43from django.utils import simplejson
44
45#==============================================================================
46
47sessionTimeout = datetime.timedelta(minutes=-2)
48
49def randomSeed():
50 return hex(random.getrandbits(32*8))[2:-1]
51
52def clipperzHash(aString):
53 #logging.info(">>> string: " + aString)
54 firstRound = hashlib.sha256()
55 firstRound.update(aString)
56 #logging.info("firstRound: " + firstRound.hexdigest() + " - " + firstRound.digest())
57 result = hashlib.sha256()
58 result.update(firstRound.digest())
59 #logging.info("<<< finalResul: " + result.hexdigest())
60
61 return result.hexdigest()
62
63#==============================================================================
64
65class User(db.Model):
66 username= db.StringProperty()
67 srp_s = db.StringProperty()
68 srp_v = db.StringProperty()
69 header = db.TextProperty()
70 statistics= db.TextProperty()
71 auth_version= db.StringProperty()
72 version = db.StringProperty()
73 lock = db.StringProperty()
74
75 def updateCredentials(self, someCredentials):
76 self.username = someCredentials['C']
77 self.srp_s = someCredentials['s']
78 self.srp_v = someCredentials['v']
79 self.auth_version= someCredentials['version']
80
81 def update(self, someData):
82 self.header = someData['header']
83 self.statistics= someData['statistics']
84 self.version= someData['version']
85 self.lock = someData['lock']
86
87#------------------------------------------------------------------------------
88
89class Record(db.Model):
90 user = db.ReferenceProperty(User)
91 reference = db.StringProperty()
92 data = db.TextProperty()
93 version = db.StringProperty()
94 creation_date= db.DateTimeProperty(auto_now_add=True)
95 update_date = db.DateTimeProperty(auto_now_add=True)
96 access_date = db.DateTimeProperty(auto_now_add=True)
97
98#------------------------------------------------------------------------------
99
100class RecordVersion(db.Model):
101 record = db.ReferenceProperty(Record)
102 reference = db.StringProperty()
103 header = db.TextProperty()
104 data = db.TextProperty()
105 version = db.StringProperty()
106 previousVersionKey= db.StringProperty()
107 previousVersion = db.SelfReferenceProperty()
108 creation_date = db.DateTimeProperty(auto_now_add=True)
109 update_date = db.DateTimeProperty(auto_now_add=True)
110 access_date = db.DateTimeProperty(auto_now_add=True)
111
112 def update(self, someData):
113 recordData = someData['record'];
114 self.parent().reference =recordData['reference']
115 self.parent().data = recordData['data']
116 self.parent().version = recordData['version']
117 self.parent().update_date =datetime.datetime.now()
118
119 recordVersionData = someData['currentRecordVersion'];
120 self.reference = recordVersionData ['reference']
121 self.data = recordVersionData ['data']
122 self.version = recordVersionData ['version']
123 #self.previous_version =#recordVersionData ['previousVersion']
124 self.previous_version_key =recordVersionData ['previousVersionKey']
125 self.update_date = datetime.datetime.now()
126
127#------------------------------------------------------------------------------
128
129class OneTimePassword(db.Model):
130 user = db.ReferenceProperty(User)
131 status = db.StringProperty()
132 reference = db.StringProperty()
133 keyValue = db.StringProperty()
134 keyChecksum = db.StringProperty()
135 data = db.TextProperty()
136 version = db.StringProperty()
137 creation_date= db.DateTimeProperty(auto_now_add=True)
138 request_date= db.DateTimeProperty()
139 usage_date = db.DateTimeProperty()
140
141 def update(self, someParameters, aStatus):
142 self.reference = someParameters['reference']
143 self.keyValue = someParameters['key']
144 self.keyChecksum = someParameters['keyChecksum']
145 self.data = someParameters['data']
146 self.version = someParameters['version']
147 self.status = aStatus
148
149 def reset(self, aStatus):
150 self.data = ""
151 self.status =aStatus
152
153 return self
154
155#------------------------------------------------------------------------------
156
157class Session(db.Expando):
158 sessionId= db.StringProperty()
159 access_date= db.DateTimeProperty()
160
161#==============================================================================
162
163class MainPage(webapp.RequestHandler):
164 def get(self):
165 path = os.path.join(os.path.dirname(__file__), 'static%s' % self.request.path)
166 self.response.out.write(template.render(path, {}))
167
168#==============================================================================
169
170class XHR(webapp.RequestHandler):
171
172 #==========================================================================
173
174 def get(self):
175 logging.info("self.request.path: " + self.request.path)
176 if self.request.path == "/dump":
177 session = self.getSession()
178 userData = {}
179 offline_data_placeholder = ""
180
181 user = db.Query(User).filter('username =', session.C).get()
182
183 userData['users'] = {
184 'catchAllUser': {
185 '__masterkey_test_value__': 'masterkey',
186 's': '112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00',
187 'v': '112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00'
188 }
189 }
190
191 records = {}
192 for currentRecord in db.Query(Record).ancestor(user):
193 versions = {}
194 for currentVersion in db.Query(RecordVersion).ancestor(currentRecord):
195 versions[currentVersion.reference] ={
196 'header': currentVersion.header,
197 'data': currentVersion.data,
198 'version': currentVersion.version,
199 'creationDate':str(currentVersion.creation_date),
200 'updateDate':str(currentVersion.update_date),
201 'accessDate':str(currentVersion.access_date)
202 }
203
204 records[currentRecord.reference] = {
205 'data': currentRecord.data,
206 'version': currentRecord.version,
207 'creationDate': str(currentRecord.creation_date),
208 'updateDate': str(currentRecord.update_date),
209 'accessDate': str(currentRecord.access_date),
210 'currentVersion':currentVersion.reference,
211 'versions': versions
212 }
213
214 userData['users'][user.username] = {
215 's': user.srp_s,
216 'v': user.srp_v,
217 'version': user.auth_version,
218 'maxNumberOfRecords':'100',
219 'userDetails': user.header,
220 'statistics': user.statistics,
221 'userDetailsVersion':user.version,
222 'records': records
223 }
224
225 offline_data_placeholder = offline_data_placeholder + "_clipperz_dump_data_ = " + simplejson.dumps(userData, indent=4) + "\n"
226 offline_data_placeholder = offline_data_placeholder + "Clipperz.PM.Proxy.defaultProxy = new Clipperz.PM.Proxy.Offline();" + "\n"
227 offline_data_placeholder = offline_data_placeholder + "Clipperz.Crypto.PRNG.defaultRandomGenerator().fastEntropyAccumulationForTestingPurpose();" + "\n"
228
229 path = os.path.join(os.path.dirname(__file__), 'static/dump.html')
230
231 self.response.headers.add_header('Content-Type', 'text/html')
232 self.response.headers.add_header('Content-Disposition', 'attachment', filename='Clipperz.html')
233 self.response.out.write(template.render(path, {'offline_data_placeholder': offline_data_placeholder}))
234
235 #==========================================================================
236
237 def post(self):
238 method = self.request.get('method')
239 parameters = simplejson.loads(self.request.get('parameters'))
240 session = self.getSession()
241 result = {};
242
243 #----------------------------------------------------------------------
244
245 if method == 'registration':
246 message = parameters['message'];
247
248 if message == 'completeRegistration':
249 user = User()
250
251 user.updateCredentials(parameters['credentials'])
252 user.update(parameters['user'])
253 user.put()
254
255 result['lock'] = user.lock
256 result['result'] = "done"
257
258 #----------------------------------------------------------------------
259
260 elif method == 'handshake':
261 srp_g = 2L
262 srp_n = long("0x%s" % "115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3", 16)
263
264 message = parameters['message'];
265
266 #------------------------------------------------------------------
267
268 if message == 'connect':
269 session.C = parameters['parameters']['C']
270 session.A = parameters['parameters']['A']
271
272 user = db.Query(User).filter('username =', session.C).get()
273
274 if user != None:
275 try:
276 optId = session.otpId
277
278 oneTimePassword = db.Query(OneTimePassword).filter('keyValue =', optId).get()
279
280 if oneTimePassword.parent().username != user.username:
281 oneTimePassword.reset('DISABLED').put()
282 raise Exception, "User missmatch between the current session and 'One Time Password' user"
283 elif oneTimePassword.status != 'REQUESTED':
284 oneTimePassword.reset('DISABLED').put()
285 raise Exception, "Tring to use an 'One Time Password' in the wrong state"
286
287 oneTimePassword.reset("USED").put()
288
289 result['oneTimePassword'] = oneTimePassword.reference
290
291 except Exception, detail:
292 logging.error("connect.optId: " + str(detail))
293
294 session.s = user.srp_s
295 session.v = user.srp_v
296 else:
297 session.s = "112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00"
298 session.v = "112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00"
299
300 session.b = randomSeed()
301 session.B = hex(long("0x%s" % session.v, 16) + pow(srp_g, long("0x%s" %session.b, 16), srp_n))[2:-1]
302
303 result['s'] = session.s
304 result['B'] = session.B
305
306 #------------------------------------------------------------------
307
308 elif message == 'credentialCheck':
309 B = long("0x%s" % session.B, 16)
310 b = long("0x%s" % session.b, 16)
311 A = long("0x%s" % session.A, 16)
312 v = long("0x%s" % session.v, 16)
313 u = long("0x%s" % clipperzHash(str(B)), 16)
314 n = srp_n
315
316 S = pow((A * pow(v, u, n)), b, n)
317 K = clipperzHash(str(S))
318 M1 = clipperzHash(str(A) + str(B) + K)
319
320 if M1 == parameters['parameters']['M1']:
321 session.K = K
322 M2 = clipperzHash(str(A) + M1 + K)
323
324 result['M2'] = M2
325 result["connectionId"] = ""
326 result["loginInfo"] = {}
327 result["loginInfo"]["latest"] = {}
328 result["loginInfo"]["current"] = {}
329 result["offlineCopyNeeded"] = "false";
330 result["lock"] = "----";
331 else:
332 result['error'] = "?"
333
334 #------------------------------------------------------------------
335
336 elif message == 'oneTimePassword':
337 oneTimePassword = db.Query(OneTimePassword).filter("keyValue =", parameters["parameters"]["oneTimePasswordKey"]).get()
338
339 if oneTimePassword != None:
340 if oneTimePassword.status == 'ACTIVE':
341 if oneTimePassword.keyChecksum == parameters['parameters']['oneTimePasswordKeyChecksum']:
342 #session.userId =str(oneTimePassword.parent().username)
343 session.otpId = str(oneTimePassword.keyValue)
344
345 result['data'] = oneTimePassword.data
346 result['version'] = oneTimePassword.version
347
348 oneTimePassword.reset('REQUESTED').put()
349
350 else:
351 oneTimePassword.reset('DISABLED').put()
352 raise Exception, "The requested One Time Password has been disabled, due to a wrong keyChecksum"
353 else:
354 raise Exception, "The requested One Time Password was not active"
355 else:
356 raise Exception, "The requested One Time Password has not been found"
357
358 #----------------------------------------------------------------------
359
360 elif method == 'message':
361 if parameters['srpSharedSecret'] == session.K:
362 message = parameters['message']
363
364 if message == 'getUserDetails':
365 #{"message":"getUserDetails", "srpSharedSecret":"f18e5cf7c3a83b67d4db9444af813ee48c13daf4f8f6635397d593e52ba89a08", "parameters":{}}
366 user = db.Query(User).filter('username =', session.C).get()
367
368 result['header'] = user.header;
369 result['statistics'] =user.statistics;
370 result['version'] = user.version;
371
372 elif message == "addNewRecords":
373 user = db.Query(User).filter('username =', session.C).get()
374 result = db.run_in_transaction(self.addNewRecords, session, user, parameters)
375
376 """
377 user = db.Query(User).filter('username =', session.C).get()
378 user.update(parameters['parameters']['user'])
379
380 for recordParameter in parameters['parameters']['records']:
381 record = Record(parent=user)
382 record.put()
383 recordVersion = RecordVersion(parent=record)
384 recordVersion.put()
385
386 recordVersion.update(recordParameter)
387
388 record.put()
389 recordVersion.put()
390
391 user.put();
392
393 result['lock'] = user.lock
394 result['result'] = 'done'
395 """
396
397 elif message == 'getRecordDetail':
398 record = db.Query(Record).ancestor(db.Query(User).filter('username =', session.C).get()).filter('reference =', parameters["parameters"]["reference"]).get()
399 recordVersion = db.Query(RecordVersion).ancestor(record).get()
400
401 result['currentVersion'] = {}
402 result['currentVersion']['reference'] = recordVersion.reference
403 result['currentVersion']['data'] = recordVersion.data
404 result['currentVersion']['header'] = recordVersion.header
405 result['currentVersion']['version'] = recordVersion.version
406 result['currentVersion']['creationDate'] =str(recordVersion.creation_date)
407 result['currentVersion']['updateDate'] =str(recordVersion.update_date)
408 result['currentVersion']['accessDate'] =str(recordVersion.access_date)
409
410 result['reference'] = record.reference
411 result['data'] = record.data
412 result['version'] = record.version
413 result['creationDate'] = str(record.creation_date)
414 result['updateDate'] = str(record.update_date)
415 result['accessDate'] = str(record.access_date)
416 result['oldestUsedEncryptedVersion'] = "---"
417
418 elif message == 'updateData':
419 user = db.Query(User).filter('username =', session.C).get()
420 user.update(parameters['parameters']['user'])
421
422 for recordParameter in parameters['parameters']['records']:
423 logging.info('reference =' + recordParameter['record']['reference'])
424 record = db.Query(Record).ancestor(user).filter('reference =', recordParameter['record']['reference']).get()
425 recordVersion = db.Query(RecordVersion).ancestor(record).get()
426
427 recordVersion.update(recordParameter)
428
429 recordVersion.put()
430 recordVersion.parent().put()
431
432 user.put();
433
434 result['lock'] = user.lock
435 result['result'] = 'done'
436
437 elif message == 'deleteRecords':
438 user = db.Query(User).filter('username =', session.C).get()
439 user.update(parameters['parameters']['user'])
440
441 for recordReference in parameters['parameters']['recordReferences']:
442 record = db.Query(Record).ancestor(user).filter('reference =', recordReference).get()
443 #recordVersion = db.Query(RecordVersion).ancestor(record).get()
444
445 db.delete(db.Query(RecordVersion).ancestor(record))
446 record.delete()
447
448 user.put()
449
450 result['lock'] = user.lock
451 result['result'] = 'done'
452
453 elif message == 'deleteUser':
454 user = db.Query(User).filter('username =', session.C).get()
455 db.delete(db.Query(RecordVersion).ancestor(user))
456 db.delete(db.Query(Record).ancestor(user))
457 user.delete()
458
459 elif message == 'addNewOneTimePassword':
460 user = db.Query(User).filter('username =', session.C).get()
461 user.update(parameters['parameters']['user'])
462
463 oneTimePassword = OneTimePassword(parent=user)
464 oneTimePassword.update(parameters['parameters']['oneTimePassword'], "ACTIVE")
465 oneTimePassword.put()
466
467 user.put()
468
469 result['lock'] = user.lock
470 result['result'] = 'done'
471
472 elif message == 'updateOneTimePasswords':
473 user = db.Query(User).filter('username =', session.C).get()
474 user.update(parameters['parameters']['user'])
475
476 validOtpReferences = parameters['parameters']['oneTimePasswords']
477 for currentOtp in db.Query(OneTimePassword).ancestor(user):
478 if currentOtp.reference in validOtpReferences:
479 pass
480 else:
481 currentOtp.delete()
482
483 user.put()
484
485 result['result'] = user.lock
486
487 elif message == 'getOneTimePasswordsDetails':
488 pass
489
490 elif message == 'getLoginHistory':
491 result["result"] = []
492
493 elif message == 'upgradeUserCredentials':
494 user = db.Query(User).filter('username =', session.C).get()
495
496 user.updateCredentials(parameters['parameters']['credentials'])
497 user.update(parameters['parameters']['user'])
498
499 for oneTimePasswordReference in parameters['parameters']['oneTimePasswords']:
500 oneTimePassword = db.Query(OneTimePassword).ancestor(user).filter("reference =", oneTimePasswordReference).get()
501
502 if oneTimePassword != None:
503 oneTimePassword.data = parameters['parameters']['oneTimePasswords'][oneTimePasswordReference]
504 oneTimePassword.put()
505
506 user.put()
507
508 result['lock'] = user.lock
509 result['result'] = 'done'
510
511 """
512 $user = new user();
513 $user->Get($_SESSION["userId"]);
514
515 $otp = new onetimepassword();
516
517 updateUserCredentials($parameters["parameters"]["credentials"], $user);
518 updateUserData($parameters["parameters"]["user"], $user);
519
520 $otpList = $parameters["parameters"]["oneTimePasswords"];
521 foreach($otpList as $otpReference=>$otpData) {
522 $otpList = $otp->GetList(array(array("reference", "=", $otpReference)));
523 $currentOtp = $otpList[0];
524 $currentOtp->data = $otpData;
525 $currentOtp->Save();
526 }
527
528 $user->Save();
529
530 $result["lock"] = $user->lock;
531 $result["result"] = "done";
532 """
533
534 #=============================================================
535
536 """
537 java.util.Mapresult;
538
539 try {
540 java.util.Mapcredentials;
541
542 if (someParameters.get("credentials") != null) {
543 credentials = (java.util.Map)someParameters.get("credentials");
544 } else {
545 credentials = someParameters;
546 }
547
548 aUser.setUsername((java.lang.String)credentials.get("C"));
549 aUser.setSrpS((java.lang.String)credentials.get("s"));
550 aUser.setSrpV((java.lang.String)credentials.get("v"));
551 aUser.setVersion((java.lang.String)credentials.get("version"));
552
553 if (someParameters.get("user") != null) {
554 com.clipperz.dataModel.EncoderHelper.updateWithMap(aUser, (java.util.Map)someParameters.get("user"));
555 }
556
557 if (someParameters.get("oneTimePasswords") != null) {
558 java.util.MapupdatedOneTimePasswords;
559 java.util.ListusersOneTimePasswords;
560 int i,c;
561
562 updatedOneTimePasswords = (java.util.Map)someParameters.get("oneTimePasswords");
563 usersOneTimePasswords = com.clipperz.dataModel.OneTimePassword.oneTimePasswordsForUser(this.user());
564 c = usersOneTimePasswords.size();
565 for (i=0; i<c; i++) {
566 com.clipperz.dataModel.OneTimePasswordcurrentOneTimePassword;
567
568 currentOneTimePassword = (com.clipperz.dataModel.OneTimePassword)usersOneTimePasswords.get(i);
569
570 if (updatedOneTimePasswords.get(currentOneTimePassword.getReference()) != null) {
571 currentOneTimePassword.setData((java.lang.String)updatedOneTimePasswords.get(currentOneTimePassword.getReference()));
572 }
573 }
574 }
575
576 result = new java.util.Hashtable();
577 this.dataContext().commitChanges();
578 result.put("lock", this.user().getNewLock());
579 result.put("result", "done");
580 } catch(java.lang.Exception exception) {
581 this.dataContext().rollbackChanges();
582 logger.error(exception);
583 throw exception;
584 }
585
586 return result;
587 """
588
589 elif message == 'echo':
590 result['result'] = parameters;
591
592 else:
593 result['error'] = "Wrong shared secret!"
594
595 #----------------------------------------------------------------------
596
597 elif method == 'logout':
598 result['method'] = 'logout'
599
600 #----------------------------------------------------------------------
601
602 else:
603 result['method'] = 'PRRRRRR'
604
605 #----------------------------------------------------------------------
606
607 self.saveSession(session)
608 self.response.out.write(simplejson.dumps(result))
609
610 #==========================================================================
611
612 def addNewRecords (self, aSession, aUser, someParameters):
613 result = {}
614
615 #user = db.Query(User).filter('username =', aSession.C).get()
616 aUser.update(someParameters['parameters']['user'])
617
618 for recordParameter in someParameters['parameters']['records']:
619 record = Record(parent=aUser)
620 record.put()
621 recordVersion = RecordVersion(parent=record)
622 recordVersion.put()
623
624 recordVersion.update(recordParameter)
625
626 record.put()
627 recordVersion.put()
628
629 aUser.put();
630
631 result['lock'] = aUser.lock
632 result['result'] = 'done'
633
634 return result
635
636 #==========================================================================
637
638 def getSession(self):
639 #logging.info(">>> getSession (%d) => %s" % (db.Query(Session).count(), str(map(lambda v: v.sessionId, db.Query(Session).fetch(100)))) )
640 result = None
641 try:
642 sessionId = self.request.cookies['sessionId']
643 except:
644 sessionId = None
645
646 #logging.info("wannabe sessionId: " + str(sessionId))
647
648 if sessionId != None:
649 #query = db.Query(Session)
650 #query.filter('sessionId =', sessionId)
651
652 #result = query.get()
653
654 #result = db.Query(Session).filter('sessionId =', str(sessionId)).filter('access_date >', (datetime.datetime.utcnow() - sessionTimeout)).get()
655 result = db.Query(Session).filter('sessionId =', str(sessionId)).get()
656 #logging.info("searching session on datastore. Found: " + str(result))
657
658 if result == None:
659 sessionId = str(uuid.uuid4())
660 #logging.info("creating a new session with sessionId=" + str(sessionId))
661 result = Session(sessionId=sessionId)
662
663 result.access_date = datetime.datetime.utcnow()
664 result.put()
665
666 #logging.info("<<< getSession (%d)" % db.Query(Session).count())
667
668 return result
669
670 #==========================================================================
671
672 def saveSession(self, aSession):
673 #logging.info(">>> saveSession (%d)" % db.Query(Session).count())
674 #self.response.set_cookie('sessionId', aSession.sessionId, max_age=360, path='/', domain='example.org', secure=True)
675 aSession.put()
676 self.response.headers.add_header('Set-Cookie', 'sessionId=' + str(aSession.sessionId), path='/')
677 self.cleanOldSessions()
678 #logging.info("<<< saveSession (%d)" % db.Query(Session).count())
679
680 #==========================================================================
681
682 def cleanOldSessions(self):
683 query = db.Query(Session).filter('accessDate <', (datetime.datetime.utcnow() - sessionTimeout))
684
685 expiredSessions = query.count();
686 if expiredSessions != 0:
687 #logging.info("deleting %d sessions" % expiredSessions)
688 pass
689
690 """
691 try:
692 db.delete(query)
693 except Exception, exception:
694 logging.error("some issues raised while deleting the expired sessions")
695 logging.error("exception type: " + str(type(exception)))
696 logging.error("exception: " + str(exception))
697 """
698 pass
699
700#==============================================================================
701
702def main():
703 application = webapp.WSGIApplication([('/xhr', XHR), ('/dump', XHR), ('/.*', MainPage)], debug=True)
704 wsgiref.handlers.CGIHandler().run(application)
705
706if __name__ == "__main__":
707 main()
708