42
42
from globaleaks .utils .sock import isIPAddress
43
43
44
44
tid_regexp = r'([0-9]+)'
45
- uuid_regexp = r'([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})'
45
+ uuid_regexp = r'([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}|closed)'
46
+ uuid_regexp_or_closed = r'([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})'
46
47
key_regexp = r'([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}|[a-z_]{0,100})'
47
48
48
49
api_spec = [
49
- (r '/api/health' , health .HealthStatusHandler ),
50
- (r '/api/report ' , report . ReportHandler ),
51
-
52
- # Public API
53
- (r '/api/public ' , public . PublicResource ),
50
+ ('/api/health' , health .HealthStatusHandler ),
51
+ ('/api/public ' , public . PublicResource ),
52
+ ( '/api/report' , report . ReportHandler ),
53
+ ( '/api/support' , support . SupportHandler ),
54
+ ('/api/wizard ' , wizard . Wizard ),
54
55
55
56
# Authentication Handlers
56
- (r '/api/auth/token' , auth .token .TokenHandler ),
57
- (r '/api/auth/authentication' , auth .AuthenticationHandler ),
58
- (r '/api/auth/type' , auth .AuthTypeHandler ),
59
- (r '/api/auth/tokenauth' , auth .TokenAuthHandler ),
60
- (r '/api/auth/receiptauth' , auth .ReceiptAuthHandler ),
61
- (r '/api/auth/session' , auth .SessionHandler ),
62
- (r '/api/auth/tenantauthswitch/' + tid_regexp , auth .TenantAuthSwitchHandler ),
63
- (r '/api/auth/operatorauthswitch' , auth .OperatorAuthSwitchHandler ),
57
+ ('/api/auth/token' , auth .token .TokenHandler ),
58
+ ('/api/auth/authentication' , auth .AuthenticationHandler ),
59
+ ('/api/auth/type' , auth .AuthTypeHandler ),
60
+ ('/api/auth/tokenauth' , auth .TokenAuthHandler ),
61
+ ('/api/auth/receiptauth' , auth .ReceiptAuthHandler ),
62
+ ('/api/auth/session' , auth .SessionHandler ),
63
+ ('/api/auth/tenantauthswitch/' , auth .TenantAuthSwitchHandler , r'/api/auth/tenantauthswitch/' + tid_regexp ),
64
+ ('/api/auth/operatorauthswitch' , auth .OperatorAuthSwitchHandler ),
64
65
65
66
# User Preferences Handler
66
- (r '/api/user/preferences' , user .UserInstance ),
67
- (r '/api/user/operations' , user .operation .UserOperationHandler ),
68
- (r '/api/user/reset/password' , user .reset_password .PasswordResetHandler ),
69
- (r '/api/user/reset/password/(.+) ' , user .reset_password .PasswordResetHandler ),
70
- (r '/api/user/validate/email/(.+) ' , user .validate_email .EmailValidation ),
67
+ ('/api/user/preferences' , user .UserInstance ),
68
+ ('/api/user/operations' , user .operation .UserOperationHandler ),
69
+ ('/api/user/reset/password' , user .reset_password .PasswordResetHandler ),
70
+ ('/api/user/reset/password' , user .reset_password .PasswordResetHandler , r'/api/user/reset/password/(.+)' ),
71
+ ('/api/user/validate/email' , user .validate_email .EmailValidation , r'/api/user/validate/email/(.+)' ),
71
72
72
73
# Receiver Handlers
73
- (r '/api/recipient/operations' , recipient .Operations ),
74
- (r '/api/recipient/rtips' , recipient .TipsCollection ),
75
- (r '/api/recipient/rtips/' + uuid_regexp , recipient .rtip .RTipInstance ),
76
- (r'/api/recipient/rtips/' + uuid_regexp + r'/comments' , recipient . rtip . RTipCommentCollection ),
77
- (r'/api/recipient/rtips/' + uuid_regexp + r'/iars' , recipient . rtip . IdentityAccessRequestsCollection ),
78
- (r'/api/recipient/rtips/' + uuid_regexp + r'/export' , recipient . export . ExportHandler ),
79
- (r'/api/recipient/rtips/' + uuid_regexp + r'/rfiles' , recipient . rtip . ReceiverFileUpload ),
80
- (r '/api/recipient/redactions' , recipient .rtip .RTipRedactionCollection ),
81
- (r '/api/recipient/redactions/' + uuid_regexp , recipient .rtip .RTipRedactionCollection ),
82
- (r '/api/recipient/rfiles/' + uuid_regexp , recipient .rtip .ReceiverFileDownload ),
83
- (r '/api/recipient/wbfiles/' + uuid_regexp , recipient .rtip .WhistleblowerFileDownload ),
74
+ ('/api/recipient/operations' , recipient .Operations ),
75
+ ('/api/recipient/rtips' , recipient .TipsCollection ),
76
+ ('/api/recipient/rtips' , recipient .rtip .RTipInstance , r'/api/recipient/rtips/' + uuid_regexp ),
77
+ ('/api/recipient/rtips' , recipient . rtip . RTipCommentCollection , r'/api/recipient/rtips/' + uuid_regexp + r'/comments' ),
78
+ ('/api/recipient/rtips' , recipient . rtip . IdentityAccessRequestsCollection , r'/api/recipient/rtips/' + uuid_regexp + r'/iars' ),
79
+ ('/api/recipient/rtips' , recipient . export . ExportHandler , r'/api/recipient/rtips/' + uuid_regexp + r'/export' ),
80
+ ('/api/recipient/rtips' , recipient . rtip . ReceiverFileUpload , r'/api/recipient/rtips/' + uuid_regexp + r'/rfiles' ),
81
+ ('/api/recipient/redactions' , recipient .rtip .RTipRedactionCollection ),
82
+ ('/api/recipient/redactions' , recipient .rtip .RTipRedactionCollection , r'/api/recipient/redactions/' + uuid_regexp ),
83
+ ('/api/recipient/rfiles' , recipient .rtip .ReceiverFileDownload , r'/api/recipient/rfiles/' + uuid_regexp ),
84
+ ('/api/recipient/wbfiles' , recipient .rtip .WhistleblowerFileDownload , r'/api/recipient/wbfiles/' + uuid_regexp ),
84
85
85
86
# Whistleblower Handlers
86
- (r '/api/whistleblower/operations' , whistleblower .wbtip .Operations ),
87
- (r '/api/whistleblower/submission' , whistleblower .submission .SubmissionInstance ),
88
- (r '/api/whistleblower/submission/attachment' , whistleblower .attachment .SubmissionAttachment ),
89
- (r '/api/whistleblower/wbtip' , whistleblower .wbtip .WBTipInstance ),
90
- (r '/api/whistleblower/wbtip/comments' , whistleblower .wbtip .WBTipCommentCollection ),
91
- (r '/api/whistleblower/wbtip/rfiles/' + uuid_regexp , whistleblower .wbtip .ReceiverFileDownload ),
92
- (r '/api/whistleblower/wbtip/wbfiles' , whistleblower .attachment .PostSubmissionAttachment ),
93
- (r '/api/whistleblower/wbtip/wbfiles/' + uuid_regexp , whistleblower .wbtip .WhistleblowerFileDownload ),
94
- (r '/api/whistleblower/wbtip/identity' , whistleblower .wbtip .WBTipIdentityHandler ),
95
- (r '/api/whistleblower/wbtip/fillform' , whistleblower .wbtip .WBTipAdditionalQuestionnaire ),
87
+ ('/api/whistleblower/operations' , whistleblower .wbtip .Operations ),
88
+ ('/api/whistleblower/submission' , whistleblower .submission .SubmissionInstance ),
89
+ ('/api/whistleblower/submission/attachment' , whistleblower .attachment .SubmissionAttachment ),
90
+ ('/api/whistleblower/wbtip' , whistleblower .wbtip .WBTipInstance ),
91
+ ('/api/whistleblower/wbtip/comments' , whistleblower .wbtip .WBTipCommentCollection ),
92
+ ('/api/whistleblower/wbtip/rfiles' , whistleblower .wbtip .ReceiverFileDownload , r'/api/whistleblower/wbtip/rfiles/' + uuid_regexp ),
93
+ ('/api/whistleblower/wbtip/wbfiles' , whistleblower .attachment .PostSubmissionAttachment ),
94
+ ('/api/whistleblower/wbtip/wbfiles' , whistleblower .wbtip .WhistleblowerFileDownload , r'/api/whistleblower/wbtip/wbfiles/' + uuid_regexp ),
95
+ ('/api/whistleblower/wbtip/identity' , whistleblower .wbtip .WBTipIdentityHandler ),
96
+ ('/api/whistleblower/wbtip/fillform' , whistleblower .wbtip .WBTipAdditionalQuestionnaire ),
96
97
97
98
# Custodian Handlers
98
- (r '/api/custodian/iars' , custodian .IdentityAccessRequestsCollection ),
99
- (r'/api/custodian/iars/' + uuid_regexp , custodian . IdentityAccessRequestInstance ),
99
+ ('/api/custodian/iars' , custodian .IdentityAccessRequestsCollection ),
100
+ ('/api/custodian/iars' , custodian . IdentityAccessRequestInstance , r'/api/custodian/iars/' + uuid_regexp ),
100
101
101
102
# Analyst Handlers
102
- (r '/api/analyst/stats' , analyst .Statistics ),
103
+ ('/api/analyst/stats' , analyst .Statistics ),
103
104
104
105
# Admin Handlers
105
- (r'/api/admin/node' , admin .node .NodeInstance ),
106
- (r'/api/admin/network' , admin .network .NetworkInstance ),
107
- (r'/api/admin/users' , admin .user .UsersCollection ),
108
- (r'/api/admin/users/' + uuid_regexp , admin .user .UserInstance ),
109
- (r'/api/admin/contexts' , admin .context .ContextsCollection ),
110
- (r'/api/admin/contexts/' + uuid_regexp , admin .context .ContextInstance ),
111
- (r'/api/admin/questionnaires' , admin .questionnaire .QuestionnairesCollection ),
112
- (r'/api/admin/questionnaires/duplicate' , admin .questionnaire .QuestionnareDuplication ),
113
- (r'/api/admin/questionnaires/' + key_regexp , admin .questionnaire .QuestionnaireInstance ),
114
- (r'/api/admin/notification' , admin .notification .NotificationInstance ),
115
- (r'/api/admin/fields' , admin .field .FieldsCollection ),
116
- (r'/api/admin/fields/' + key_regexp , admin .field .FieldInstance ),
117
- (r'/api/admin/steps' , admin .step .StepCollection ),
118
- (r'/api/admin/steps/' + uuid_regexp , admin .step .StepInstance ),
119
- (r'/api/admin/fieldtemplates' , admin .field .FieldTemplatesCollection ),
120
- (r'/api/admin/fieldtemplates/' + key_regexp , admin .field .FieldTemplateInstance ),
121
- (r'/api/admin/redirects' , admin .redirect .RedirectCollection ),
122
- (r'/api/admin/redirects/' + uuid_regexp , admin .redirect .RedirectInstance ),
123
- (r'/api/admin/auditlog' , admin .auditlog .AuditLog ),
124
- (r'/api/admin/auditlog/access' , admin .auditlog .AccessLog ),
125
- (r'/api/admin/auditlog/debug' , admin .auditlog .DebugLog ),
126
- (r'/api/admin/auditlog/jobs' , admin .auditlog .JobsTiming ),
127
- (r'/api/admin/auditlog/tips' , admin .auditlog .TipsCollection ),
128
- (r'/api/admin/l10n/(' + '|' .join (LANGUAGES_SUPPORTED_CODES ) + ')' , admin .l10n .AdminL10NHandler ),
129
- (r'/api/admin/config' , admin .operation .AdminOperationHandler ),
130
- (r'/api/admin/config/csr/gen' , admin .https .CSRHandler ),
131
- (r'/api/admin/config/acme/run' , admin .https .AcmeHandler ),
132
- (r'/api/admin/config/tls' , admin .https .ConfigHandler ),
133
- (r'/api/admin/config/tls/files/(cert|chain|key)' , admin .https .FileHandler ),
134
- (r'/api/admin/files' , admin .file .FileCollection ),
135
- (r'/api/admin/files/(.+)' , admin .file .FileInstance ),
136
- (r'/api/admin/tenants' , admin .tenant .TenantCollection ),
137
- (r'/api/admin/tenants/' + '([0-9]{1,20})' , admin .tenant .TenantInstance ),
138
- (r'/api/admin/statuses' , admin .submission_statuses .SubmissionStatusCollection ),
139
- (r'/api/admin/statuses/' + r'(closed)' + r'/substatuses' , admin .submission_statuses .SubmissionSubStatusCollection ),
140
- (r'/api/admin/statuses/' + uuid_regexp , admin .submission_statuses .SubmissionStatusInstance ),
141
- (r'/api/admin/statuses/' + r'(closed)' , admin .submission_statuses .SubmissionStatusInstance ),
142
- (r'/api/admin/statuses/' + uuid_regexp + r'/substatuses' , admin .submission_statuses .SubmissionSubStatusCollection ),
143
- (r'/api/admin/statuses/' + r'(closed)' + r'/substatuses/' + uuid_regexp , admin .submission_statuses .SubmissionSubStatusInstance ),
144
- (r'/api/admin/statuses/' + uuid_regexp + r'/substatuses/' + uuid_regexp , admin .submission_statuses .SubmissionSubStatusInstance ),
145
-
146
- # Services
147
- (r'/api/support' , support .SupportHandler ),
148
- (r'/api/signup' , signup .Signup ),
149
- (r'/api/signup/([a-zA-Z0-9_\-]{64})' , signup .SignupActivation ),
150
- (r'/api/wizard' , wizard .Wizard ),
106
+ ('/api/admin/node' , admin .node .NodeInstance ),
107
+ ('/api/admin/network' , admin .network .NetworkInstance ),
108
+ ('/api/admin/users' , admin .user .UsersCollection ),
109
+ ('/api/admin/users' , admin .user .UserInstance , r'/api/admin/users/' + uuid_regexp ),
110
+ ('/api/admin/contexts' , admin .context .ContextsCollection ),
111
+ ('/api/admin/contexts' , admin .context .ContextInstance , r'/api/admin/contexts/' + uuid_regexp ),
112
+ ('/api/admin/questionnaires' , admin .questionnaire .QuestionnairesCollection ),
113
+ ('/api/admin/questionnaires' , admin .questionnaire .QuestionnaireInstance , r'/api/admin/questionnaires/' + key_regexp ),
114
+ ('/api/admin/questionnaires/duplicate' , admin .questionnaire .QuestionnareDuplication ),
115
+ ('/api/admin/notification' , admin .notification .NotificationInstance ),
116
+ ('/api/admin/fields' , admin .field .FieldsCollection ),
117
+ ('/api/admin/fields' , admin .field .FieldInstance , r'/api/admin/fields/' + key_regexp ),
118
+ ('/api/admin/steps' , admin .step .StepCollection ),
119
+ ('/api/admin/steps' , admin .step .StepInstance , r'/api/admin/steps/' + uuid_regexp ),
120
+ ('/api/admin/fieldtemplates' , admin .field .FieldTemplatesCollection ),
121
+ ('/api/admin/fieldtemplates' , admin .field .FieldTemplateInstance , r'/api/admin/fieldtemplates/' + key_regexp ),
122
+ ('/api/admin/redirects' , admin .redirect .RedirectCollection , r'/api/admin/redirects' ),
123
+ ('/api/admin/redirects' , admin .redirect .RedirectInstance , r'/api/admin/redirects/' + uuid_regexp ),
124
+ ('/api/admin/auditlog' , admin .auditlog .AuditLog ),
125
+ ('/api/admin/auditlog/access' , admin .auditlog .AccessLog ),
126
+ ('/api/admin/auditlog/debug' , admin .auditlog .DebugLog ),
127
+ ('/api/admin/auditlog/jobs' , admin .auditlog .JobsTiming ),
128
+ ('/api/admin/auditlog/tips' , admin .auditlog .TipsCollection ),
129
+ ('/api/admin/l10n/' , admin .l10n .AdminL10NHandler , r'/api/admin/l10n/(' + '|' .join (LANGUAGES_SUPPORTED_CODES ) + ')' ),
130
+ ('/api/admin/config' , admin .operation .AdminOperationHandler ),
131
+ ('/api/admin/config/csr/gen' , admin .https .CSRHandler ),
132
+ ('/api/admin/config/acme/run' , admin .https .AcmeHandler ),
133
+ ('/api/admin/config/tls' , admin .https .ConfigHandler ),
134
+ ('/api/admin/config/tls/files/' , admin .https .FileHandler , r'/api/admin/config/tls/files/(cert|chain|key)' ),
135
+ ('/api/admin/files' , admin .file .FileCollection ),
136
+ ('/api/admin/files' , admin .file .FileInstance , r'/api/admin/files/(.+)' ),
137
+ ('/api/admin/tenants' , admin .tenant .TenantCollection ),
138
+ ('/api/admin/tenants' , admin .tenant .TenantInstance , r'/api/admin/tenants/' + '([0-9]{1,20})' ),
139
+ ('/api/admin/statuses' , admin .submission_statuses .SubmissionStatusCollection ),
140
+ ('/api/admin/statuses' , admin .submission_statuses .SubmissionStatusInstance , r'/api/admin/statuses/' + uuid_regexp_or_closed ),
141
+ ('/api/admin/statuses' , admin .submission_statuses .SubmissionSubStatusCollection , r'/api/admin/statuses/' + uuid_regexp_or_closed + r'/substatuses' ),
142
+ ('/api/admin/statuses' , admin .submission_statuses .SubmissionSubStatusInstance , r'/api/admin/statuses/' + uuid_regexp_or_closed + r'/substatuses/' + uuid_regexp ),
143
+
144
+ # Signup
145
+ ('/api/signup' , signup .Signup ),
146
+ ('/api/signup' , signup .SignupActivation , r'/api/signup/([a-zA-Z0-9_\-]{64})' ),
151
147
152
148
# Well known path
153
- (r'/.well-known/acme-challenge/([a-zA-Z0-9_\-]{42,44})' , admin . https . AcmeChallengeHandler ),
154
- (r '/.well-known/security.txt' , security .SecuritytxtHandler ),
149
+ ('/.well-known/acme-challenge' , admin . https . AcmeChallengeHandler , r'/\ .well-known/acme-challenge/([a-zA-Z0-9_\-]{42,44})' ),
150
+ ('/.well-known/security.txt' , security .SecuritytxtHandler ),
155
151
156
152
# Special Files Handlers
157
- (r '/robots.txt' , robots .RobotstxtHandler ),
158
- (r '/sitemap.xml' , sitemap .SitemapHandler ),
159
- (r '/s/(.+) ' , file .FileHandler ),
160
- (r'/l10n/(' + '|' .join (LANGUAGES_SUPPORTED_CODES ) + ')' , l10n . L10NHandler ),
153
+ ('/robots.txt' , robots .RobotstxtHandler ),
154
+ ('/sitemap.xml' , sitemap .SitemapHandler ),
155
+ ('/s/' , file .FileHandler , r'/s/(.+)' ),
156
+ ('/l10n/' , l10n . L10NHandler , r'/l10n/(' + '|' .join (LANGUAGES_SUPPORTED_CODES ) + ')' ),
161
157
162
158
# Path alias
163
- (r'^(/admin|/login|/submission)$' , redirect .SpecialRedirectHandler ),
164
-
165
- # This handler attempts to route all non routed get requests
166
- (r'/([a-zA-Z0-9_\-\/\.\@]*)' , staticfile .StaticFileHandler )
159
+ ('/admin' , redirect .SpecialRedirectHandler ),
160
+ ('/login' , redirect .SpecialRedirectHandler ),
161
+ ('/submission' , redirect .SpecialRedirectHandler ),
167
162
]
168
163
164
+ # Extend the tuples in the API spec that have 2 elements with None
165
+ api_spec = [t if len (t ) == 3 else (* t , re .escape (t [0 ])) for t in api_spec ]
166
+
167
+
168
+ default_api = staticfile .StaticFileHandler
169
+ default_regexp = re .compile (r'/([a-zA-Z0-9_\-\/\.\@]*)' )
170
+
171
+
172
+ class TrieNode :
173
+ def __init__ (self ):
174
+ self .children = {}
175
+ self .descriptors = [] # List of tuples (regexp, handler)
176
+
177
+
178
+ class Trie :
179
+ def __init__ (self ):
180
+ self .root = TrieNode ()
181
+
182
+ def insert (self , prefix , regexp , handler ):
183
+ """
184
+ Insert a route prefix into the Trie.
185
+ """
186
+ node = self .root
187
+ parts = prefix .strip ('/' ).split ('/' )
188
+
189
+ for part in parts :
190
+ if part not in node .children :
191
+ node .children [part ] = TrieNode ()
192
+ node = node .children [part ]
193
+
194
+ # Attach the full regexp and handler at the leaf node
195
+ node .descriptors .append ((re .compile (regexp ), handler ))
196
+
197
+ def search (self , path ):
198
+ """
199
+ Search for a matching handler based on the path.
200
+ """
201
+ match = None
202
+ node = self .root
203
+ parts = path .strip ('/' ).split ('/' )
204
+
205
+ for part in parts :
206
+ if part in node .children :
207
+ node = node .children [part ]
208
+ else :
209
+ break
210
+
211
+ for regexp , handler in node .descriptors :
212
+ match = regexp .match (path )
213
+ if match :
214
+ return match , handler
215
+
216
+ return default_regexp .match (path ), default_api
217
+
169
218
170
219
class APIResourceWrapper (Resource ):
171
220
isLeaf = True
@@ -180,17 +229,15 @@ class APIResourceWrapper(Resource):
180
229
181
230
def __init__ (self ):
182
231
Resource .__init__ (self )
183
- self .registry = []
232
+ self .registry = Trie ()
184
233
self .handler = None
185
234
186
- for tup in api_spec :
187
- pattern , handler = tup
235
+ for prefix , handler , regexp in api_spec :
236
+ if not regexp .startswith ("^" ):
237
+ regexp = "^" + regexp
188
238
189
- if not pattern .startswith ("^" ):
190
- pattern = "^" + pattern
191
-
192
- if not pattern .endswith ("$" ):
193
- pattern += "$"
239
+ if not regexp .endswith ("$" ):
240
+ regexp += "$"
194
241
195
242
if not hasattr (handler , '_decorated' ):
196
243
handler ._decorated = True
@@ -199,21 +246,10 @@ def __init__(self):
199
246
if hasattr (handler , m ):
200
247
decorators .decorate_method (handler , m )
201
248
202
- self .registry .append (( re .compile (pattern ), handler ) )
249
+ self .registry .insert ( prefix , re .compile (regexp ), handler )
203
250
204
251
def resolve_handler (self , path ):
205
- match = None
206
-
207
- for regexp , handler in self .registry :
208
- try :
209
- match = regexp .match (path )
210
- except UnicodeDecodeError :
211
- match = None
212
- if match :
213
- break
214
-
215
- if match :
216
- return match , handler
252
+ return self .registry .search (path )
217
253
218
254
def should_redirect_https (self , request ):
219
255
if request .isSecure () or \
@@ -326,7 +362,7 @@ def render(self, request):
326
362
(State .tenants [1 ].cache .hostname == '' and isIPAddress (request .hostname )):
327
363
request .tid = 1
328
364
else :
329
- request .tid = State .tenant_hostname_id_map .get (request .hostname , None )
365
+ request .tid = State .tenant_hostname_id_map .get (request .hostname )
330
366
331
367
if request .tid == 1 :
332
368
try :
@@ -392,7 +428,6 @@ def render(self, request):
392
428
return b''
393
429
394
430
match , handler = self .resolve_handler (request_path )
395
-
396
431
if match is None :
397
432
self .handle_exception (errors .ResourceNotFound , request )
398
433
return b''
0 commit comments