Mercurial > traipse_dev
comparison plugins/cherrypy/_cphttptools.py @ 0:4385a7d0efd1 grumpy-goblin
Deleted and repushed it with the 'grumpy-goblin' branch. I forgot a y
author | sirebral |
---|---|
date | Tue, 14 Jul 2009 16:41:58 -0500 |
parents | |
children | 78407d627cba |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4385a7d0efd1 |
---|---|
1 """ | |
2 Copyright (c) 2004, CherryPy Team (team@cherrypy.org) | |
3 All rights reserved. | |
4 | |
5 Redistribution and use in source and binary forms, with or without modification, | |
6 are permitted provided that the following conditions are met: | |
7 | |
8 * Redistributions of source code must retain the above copyright notice, | |
9 this list of conditions and the following disclaimer. | |
10 * Redistributions in binary form must reproduce the above copyright notice, | |
11 this list of conditions and the following disclaimer in the documentation | |
12 and/or other materials provided with the distribution. | |
13 * Neither the name of the CherryPy Team nor the names of its contributors | |
14 may be used to endorse or promote products derived from this software | |
15 without specific prior written permission. | |
16 | |
17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
18 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
19 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
20 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE | |
21 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
22 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
23 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
24 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
25 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
26 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
27 """ | |
28 | |
29 import cpg, urllib, sys, time, traceback, types, StringIO, cgi, os | |
30 import mimetypes, hashlib, random, string, _cputil, cperror, Cookie, urlparse | |
31 from lib.filter import basefilter | |
32 | |
33 """ | |
34 Common Service Code for CherryPy | |
35 """ | |
36 | |
37 mimetypes.types_map['.dwg']='image/x-dwg' | |
38 mimetypes.types_map['.ico']='image/x-icon' | |
39 | |
40 weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] | |
41 monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] | |
42 | |
43 class IndexRedirect(Exception): pass | |
44 | |
45 def parseFirstLine(data): | |
46 cpg.request.path = data.split()[1] | |
47 cpg.request.queryString = "" | |
48 cpg.request.browserUrl = cpg.request.path | |
49 cpg.request.paramMap = {} | |
50 cpg.request.paramList = [] # Only used for Xml-Rpc | |
51 cpg.request.filenameMap = {} | |
52 cpg.request.fileTypeMap = {} | |
53 i = cpg.request.path.find('?') | |
54 if i != -1: | |
55 # Parse parameters from URL | |
56 if cpg.request.path[i+1:]: | |
57 k = cpg.request.path[i+1:].find('?') | |
58 if k != -1: | |
59 j = cpg.request.path[:k].rfind('=') | |
60 if j != -1: | |
61 cpg.request.path = cpg.request.path[:j+1] + \ | |
62 urllib.quote_plus(cpg.request.path[j+1:]) | |
63 for paramStr in cpg.request.path[i+1:].split('&'): | |
64 sp = paramStr.split('=') | |
65 if len(sp) > 2: | |
66 j = paramStr.find('=') | |
67 sp = (paramStr[:j], paramStr[j+1:]) | |
68 if len(sp) == 2: | |
69 key, value = sp | |
70 value = urllib.unquote_plus(value) | |
71 if cpg.request.paramMap.has_key(key): | |
72 # Already has a value: make a list out of it | |
73 if type(cpg.request.paramMap[key]) == type([]): | |
74 # Already is a list: append the new value to it | |
75 cpg.request.paramMap[key].append(value) | |
76 else: | |
77 # Only had one value so far: start a list | |
78 cpg.request.paramMap[key] = [cpg.request.paramMap[key], value] | |
79 else: | |
80 cpg.request.paramMap[key] = value | |
81 cpg.request.queryString = cpg.request.path[i+1:] | |
82 cpg.request.path = cpg.request.path[:i] | |
83 | |
84 def cookHeaders(clientAddress, remoteHost, headers, requestLine): | |
85 """Process the headers into the request.headerMap""" | |
86 cpg.request.headerMap = {} | |
87 cpg.request.requestLine = requestLine | |
88 cpg.request.simpleCookie = Cookie.SimpleCookie() | |
89 | |
90 # Build headerMap | |
91 for item in headers.items(): | |
92 # Warning: if there is more than one header entry for cookies (AFAIK, only Konqueror does that) | |
93 # only the last one will remain in headerMap (but they will be correctly stored in request.simpleCookie) | |
94 insertIntoHeaderMap(item[0],item[1]) | |
95 | |
96 # Handle cookies differently because on Konqueror, multiple cookies come on different lines with the same key | |
97 cookieList = headers.getallmatchingheaders('cookie') | |
98 for cookie in cookieList: | |
99 cpg.request.simpleCookie.load(cookie) | |
100 | |
101 cpg.request.remoteAddr = clientAddress | |
102 cpg.request.remoteHost = remoteHost | |
103 | |
104 # Set peer_certificate (in SSL mode) so the web app can examinate the client certificate | |
105 try: cpg.request.peerCertificate = self.request.get_peer_certificate() | |
106 except: pass | |
107 | |
108 _cputil.getSpecialFunction('_cpLogMessage')("%s - %s" % (cpg.request.remoteAddr, requestLine[:-2]), "HTTP") | |
109 | |
110 | |
111 def parsePostData(rfile): | |
112 # Read request body and put it in data | |
113 len = int(cpg.request.headerMap.get("Content-Length","0")) | |
114 if len: data = rfile.read(len) | |
115 else: data="" | |
116 | |
117 # Put data in a StringIO so FieldStorage can read it | |
118 newRfile = StringIO.StringIO(data) | |
119 # Create a copy of headerMap with lowercase keys because | |
120 # FieldStorage doesn't work otherwise | |
121 lowerHeaderMap = {} | |
122 for key, value in cpg.request.headerMap.items(): | |
123 lowerHeaderMap[key.lower()] = value | |
124 forms = cgi.FieldStorage(fp = newRfile, headers = lowerHeaderMap, environ = {'REQUEST_METHOD':'POST'}, keep_blank_values = 1) | |
125 for key in forms.keys(): | |
126 # Check if it's a list or not | |
127 valueList = forms[key] | |
128 if type(valueList) == type([]): | |
129 # It's a list of values | |
130 cpg.request.paramMap[key] = [] | |
131 cpg.request.filenameMap[key] = [] | |
132 cpg.request.fileTypeMap[key] = [] | |
133 for item in valueList: | |
134 cpg.request.paramMap[key].append(item.value) | |
135 cpg.request.filenameMap[key].append(item.filename) | |
136 cpg.request.fileTypeMap[key].append(item.type) | |
137 else: | |
138 # It's a single value | |
139 # In case it's a file being uploaded, we save the filename in a map (user might need it) | |
140 cpg.request.paramMap[key] = valueList.value | |
141 cpg.request.filenameMap[key] = valueList.filename | |
142 cpg.request.fileTypeMap[key] = valueList.type | |
143 | |
144 def applyFilterList(methodName): | |
145 try: | |
146 filterList = _cputil.getSpecialFunction('_cpFilterList') | |
147 for filter in filterList: | |
148 method = getattr(filter, methodName, None) | |
149 if method: | |
150 method() | |
151 except basefilter.InternalRedirect: | |
152 # If we get an InternalRedirect, we start the filter list | |
153 # from scratch. Is cpg.request.path or cpg.request.objectPath | |
154 # has been modified by the hook, then a new filter list | |
155 # will be applied. | |
156 # We use recursion so if there is an infinite loop, we'll | |
157 # get the regular python "recursion limit exceeded" exception. | |
158 applyFilterList(methodName) | |
159 | |
160 | |
161 def insertIntoHeaderMap(key,value): | |
162 normalizedKey = '-'.join([s.capitalize() for s in key.split('-')]) | |
163 cpg.request.headerMap[normalizedKey] = value | |
164 | |
165 def initRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile): | |
166 parseFirstLine(requestLine) | |
167 cookHeaders(clientAddress, remoteHost, headers, requestLine) | |
168 | |
169 cpg.request.base = "http://" + cpg.request.headerMap['Host'] | |
170 cpg.request.browserUrl = cpg.request.base + cpg.request.browserUrl | |
171 cpg.request.isStatic = False | |
172 cpg.request.parsePostData = True | |
173 cpg.request.rfile = rfile | |
174 | |
175 # Change objectPath in filters to change the object that will get rendered | |
176 cpg.request.objectPath = None | |
177 | |
178 applyFilterList('afterRequestHeader') | |
179 | |
180 if cpg.request.method == 'POST' and cpg.request.parsePostData: | |
181 parsePostData(rfile) | |
182 | |
183 applyFilterList('afterRequestBody') | |
184 | |
185 def doRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile): | |
186 # creates some attributes on cpg.response so filters can use them | |
187 cpg.response.wfile = wfile | |
188 cpg.response.sendResponse = 1 | |
189 try: | |
190 initRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile) | |
191 except basefilter.RequestHandled: | |
192 # request was already fully handled; it may be a cache hit | |
193 return | |
194 | |
195 # Prepare response variables | |
196 now = time.time() | |
197 year, month, day, hh, mm, ss, wd, y, z = time.gmtime(now) | |
198 date = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (weekdayname[wd], day, monthname[month], year, hh, mm, ss) | |
199 cpg.response.headerMap = { | |
200 "protocolVersion": cpg.configOption.protocolVersion, | |
201 "Status": "200 OK", | |
202 "Content-Type": "text/html", | |
203 "Server": "CherryPy/" + cpg.__version__, | |
204 "Date": date, | |
205 "Set-Cookie": [], | |
206 "Content-Length": 0 | |
207 } | |
208 cpg.response.simpleCookie = Cookie.SimpleCookie() | |
209 | |
210 try: | |
211 handleRequest(cpg.response.wfile) | |
212 except: | |
213 # TODO: in some cases exceptions and filters are conflicting; | |
214 # error reporting seems to be broken in some cases. This code is | |
215 # a helper to check it | |
216 err = "" | |
217 exc_info_1 = sys.exc_info()[1] | |
218 if hasattr(exc_info_1, 'args') and len(exc_info_1.args) >= 1: | |
219 err = exc_info_1.args[0] | |
220 | |
221 try: | |
222 _cputil.getSpecialFunction('_cpOnError')() | |
223 | |
224 # Still save session data | |
225 if cpg.configOption.sessionStorageType and not cpg.request.isStatic: | |
226 sessionId = cpg.response.simpleCookie[cpg.configOption.sessionCookieName].value | |
227 expirationTime = time.time() + cpg.configOption.sessionTimeout * 60 | |
228 _cputil.getSpecialFunction('_cpSaveSessionData')(sessionId, cpg.request.sessionMap, expirationTime) | |
229 | |
230 wfile.write('%s %s\r\n' % (cpg.response.headerMap['protocolVersion'], cpg.response.headerMap['Status'])) | |
231 | |
232 if (cpg.response.headerMap.has_key('Content-Length') and | |
233 cpg.response.headerMap['Content-Length']==0): | |
234 buf = StringIO.StringIO() | |
235 [buf.write(x) for x in cpg.response.body] | |
236 buf.seek(0) | |
237 cpg.response.body = [buf.read()] | |
238 cpg.response.headerMap['Content-Length'] = len(cpg.response.body[0]) | |
239 | |
240 for key, valueList in cpg.response.headerMap.items(): | |
241 if key not in ('Status', 'protocolVersion'): | |
242 if type(valueList) != type([]): valueList = [valueList] | |
243 for value in valueList: | |
244 wfile.write('%s: %s\r\n'%(key, value)) | |
245 wfile.write('\r\n') | |
246 for line in cpg.response.body: | |
247 wfile.write(line) | |
248 except: | |
249 bodyFile = StringIO.StringIO() | |
250 traceback.print_exc(file = bodyFile) | |
251 body = bodyFile.getvalue() | |
252 wfile.write('%s 200 OK\r\n' % cpg.configOption.protocolVersion) | |
253 wfile.write('Content-Type: text/plain\r\n') | |
254 wfile.write('Content-Length: %s\r\n' % len(body)) | |
255 wfile.write('\r\n') | |
256 wfile.write(body) | |
257 | |
258 def sendResponse(wfile): | |
259 applyFilterList('beforeResponse') | |
260 | |
261 # Set the content-length | |
262 if (cpg.response.headerMap.has_key('Content-Length') and | |
263 cpg.response.headerMap['Content-Length']==0): | |
264 buf = StringIO.StringIO() | |
265 [buf.write(x) for x in cpg.response.body] | |
266 buf.seek(0) | |
267 cpg.response.body = [buf.read()] | |
268 cpg.response.headerMap['Content-Length'] = len(cpg.response.body[0]) | |
269 | |
270 # Save session data | |
271 if cpg.configOption.sessionStorageType and not cpg.request.isStatic: | |
272 sessionId = cpg.response.simpleCookie[cpg.configOption.sessionCookieName].value | |
273 expirationTime = time.time() + cpg.configOption.sessionTimeout * 60 | |
274 _cputil.getSpecialFunction('_cpSaveSessionData')(sessionId, cpg.request.sessionMap, expirationTime) | |
275 | |
276 wfile.write('%s %s\r\n' % (cpg.response.headerMap['protocolVersion'], cpg.response.headerMap['Status'])) | |
277 for key, valueList in cpg.response.headerMap.items(): | |
278 if key not in ('Status', 'protocolVersion'): | |
279 if type(valueList) != type([]): valueList = [valueList] | |
280 for value in valueList: | |
281 wfile.write('%s: %s\r\n' % (key, value)) | |
282 | |
283 # Send response cookies | |
284 cookie = cpg.response.simpleCookie.output() | |
285 if cookie: | |
286 wfile.write(cookie+'\r\n') | |
287 wfile.write('\r\n') | |
288 | |
289 for line in cpg.response.body: | |
290 wfile.write(line) | |
291 | |
292 # finalization hook for filter cleanup & logging purposes | |
293 applyFilterList('afterResponse') | |
294 | |
295 def handleRequest(wfile): | |
296 # Clean up expired sessions if needed: | |
297 now = time.time() | |
298 if cpg.configOption.sessionStorageType and cpg.configOption.sessionCleanUpDelay and cpg._lastSessionCleanUpTime + cpg.configOption.sessionCleanUpDelay * 60 <= now: | |
299 cpg._lastSessionCleanUpTime = now | |
300 _cputil.getSpecialFunction('_cpCleanUpOldSessions')() | |
301 | |
302 # Save original values (in case they get modified by filters) | |
303 cpg.request.originalPath = cpg.request.path | |
304 cpg.request.originalParamMap = cpg.request.paramMap | |
305 cpg.request.originalParamList = cpg.request.paramList | |
306 | |
307 path = cpg.request.path | |
308 if path.startswith('/'): | |
309 # Remove leading slash | |
310 path = path[1:] | |
311 if path.endswith('/'): | |
312 # Remove trailing slash | |
313 path = path[:-1] | |
314 path = urllib.unquote(path) # Replace quoted chars (eg %20) from url | |
315 | |
316 # Handle static directories | |
317 for urlDir, fsDir in cpg.configOption.staticContentList: | |
318 if path == urlDir or path[:len(urlDir)+1]==urlDir+'/': | |
319 | |
320 cpg.request.isStatic = 1 | |
321 | |
322 fname = fsDir + path[len(urlDir):] | |
323 start_url_var = cpg.request.browserUrl.find('?') | |
324 if start_url_var != -1: fname = fname + cpg.request.browserUrl[start_url_var:] | |
325 try: | |
326 stat = os.stat(fname) | |
327 except OSError: | |
328 raise cperror.NotFound | |
329 modifTime = stat.st_mtime | |
330 | |
331 strModifTime = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(modifTime)) | |
332 | |
333 # Check if browser sent "if-modified-since" in request header | |
334 if cpg.request.headerMap.has_key('If-Modified-Since'): | |
335 # Check if if-modified-since date is the same as strModifTime | |
336 if cpg.request.headerMap['If-Modified-Since'] == strModifTime: | |
337 cpg.response.headerMap = { | |
338 'Status': 304, | |
339 'protocolVersion': cpg.configOption.protocolVersion, | |
340 'Date': cpg.response.headerMap['Date']} | |
341 cpg.response.body = [] | |
342 sendResponse(wfile) | |
343 return | |
344 | |
345 cpg.response.headerMap['Last-Modified'] = strModifTime | |
346 # Set Content-Length and use an iterable (file object) | |
347 # this way CP won't load the whole file in memory | |
348 cpg.response.headerMap['Content-Length'] = stat[6] | |
349 cpg.response.body = open(fname, 'rb') | |
350 # Set content-type based on filename extension | |
351 i = path.rfind('.') | |
352 if i != -1: ext = path[i:] | |
353 else: ext = "" | |
354 contentType = mimetypes.types_map.get(ext, "text/plain") | |
355 cpg.response.headerMap['Content-Type'] = contentType | |
356 sendResponse(wfile) | |
357 return | |
358 | |
359 # Get session data | |
360 if cpg.configOption.sessionStorageType and not cpg.request.isStatic: | |
361 now = time.time() | |
362 # First, get sessionId from cookie | |
363 try: sessionId = cpg.request.simpleCookie[cpg.configOption.sessionCookieName].value | |
364 except: sessionId=None | |
365 if sessionId: | |
366 # Load session data from wherever it was stored | |
367 sessionData = _cputil.getSpecialFunction('_cpLoadSessionData')(sessionId) | |
368 if sessionData == None: | |
369 sessionId = None | |
370 else: | |
371 cpg.request.sessionMap, expirationTime = sessionData | |
372 # Check that is hasn't expired | |
373 if now > expirationTime: | |
374 # Session expired | |
375 sessionId = None | |
376 | |
377 # Create a new sessionId if needed | |
378 if not sessionId: | |
379 cpg.request.sessionMap = {} | |
380 sessionId = generateSessionId() | |
381 cpg.request.sessionMap['_sessionId'] = sessionId | |
382 | |
383 cpg.response.simpleCookie[cpg.configOption.sessionCookieName] = sessionId | |
384 cpg.response.simpleCookie[cpg.configOption.sessionCookieName]['path'] = '/' | |
385 cpg.response.simpleCookie[cpg.configOption.sessionCookieName]['version'] = 1 | |
386 | |
387 try: | |
388 func, objectPathList, virtualPathList = mapPathToObject() | |
389 except IndexRedirect, inst: | |
390 # For an IndexRedirect, we don't go through the regular | |
391 # mechanism: we return the redirect immediately | |
392 newUrl = urlparse.urljoin(cpg.request.base, inst.args[0]) | |
393 wfile.write('%s 302\r\n' % (cpg.response.headerMap['protocolVersion'])) | |
394 cpg.response.headerMap['Location'] = newUrl | |
395 for key, valueList in cpg.response.headerMap.items(): | |
396 if key not in ('Status', 'protocolVersion'): | |
397 if type(valueList) != type([]): valueList = [valueList] | |
398 for value in valueList: | |
399 wfile.write('%s: %s\r\n'%(key, value)) | |
400 wfile.write('\r\n') | |
401 return | |
402 | |
403 # Remove "root" from objectPathList and join it to get objectPath | |
404 cpg.request.objectPath = '/' + '/'.join(objectPathList[1:]) | |
405 body = func(*(virtualPathList + cpg.request.paramList), **(cpg.request.paramMap)) | |
406 | |
407 # builds a uniform return type | |
408 if not isinstance(body, types.GeneratorType): | |
409 cpg.response.body = [body] | |
410 else: | |
411 cpg.response.body = body | |
412 | |
413 if cpg.response.sendResponse: | |
414 sendResponse(wfile) | |
415 | |
416 def generateSessionId(): | |
417 s = '' | |
418 for i in range(50): | |
419 s += random.choice(string.letters+string.digits) | |
420 s += '%s'%time.time() | |
421 return hashlib.hashlib(s).hexdigest() | |
422 | |
423 def getObjFromPath(objPathList, objCache): | |
424 """ For a given objectPathList (like ['root', 'a', 'b', 'index']), | |
425 return the object (or None if it doesn't exist). | |
426 Also keep a cache for maximum efficiency | |
427 """ | |
428 # Let cpg be the first valid object. | |
429 validObjects = ["cpg"] | |
430 | |
431 # Scan the objPathList in order from left to right | |
432 for index, obj in enumerate(objPathList): | |
433 # maps virtual filenames to Python identifiers (substitutes '.' for '_') | |
434 obj = obj.replace('.', '_') | |
435 | |
436 # currentObjStr holds something like 'cpg.root.something.else' | |
437 currentObjStr = ".".join(validObjects) | |
438 | |
439 #--------------- | |
440 # Cache check | |
441 #--------------- | |
442 # Generate a cacheKey from the first 'index' elements of objPathList | |
443 cacheKey = tuple(objPathList[:index+1]) | |
444 # Is this cacheKey in the objCache? | |
445 if cacheKey in objCache: | |
446 # And is its value not None? | |
447 if objCache[cacheKey]: | |
448 # Yes, then add it to the list of validObjects | |
449 validObjects.append(obj) | |
450 # OK, go to the next iteration | |
451 continue | |
452 # Its value is None, so we stop | |
453 # (This means it is not a valid object) | |
454 break | |
455 | |
456 #----------------- | |
457 # Attribute check | |
458 #----------------- | |
459 if getattr(eval(currentObjStr), obj, None): | |
460 # obj is a valid attribute of the current object | |
461 validObjects.append(obj) | |
462 # Store it in the cache | |
463 objCache[cacheKey] = eval(".".join(validObjects)) | |
464 else: | |
465 # obj is not a valid attribute | |
466 # Store None in the cache | |
467 objCache[cacheKey] = None | |
468 # Stop, we won't process the remaining objPathList | |
469 break | |
470 | |
471 # Return the last cached object (even if its None) | |
472 return objCache[cacheKey] | |
473 | |
474 def mapPathToObject(path = None): | |
475 # Traverse path: | |
476 # for /a/b?arg=val, we'll try: | |
477 # root.a.b.index -> redirect to /a/b/?arg=val | |
478 # root.a.b.default(arg='val') -> redirect to /a/b/?arg=val | |
479 # root.a.b(arg='val') | |
480 # root.a.default('b', arg='val') | |
481 # root.default('a', 'b', arg='val') | |
482 | |
483 # Also, we ignore trailing slashes | |
484 # Also, a method has to have ".exposed = True" in order to be exposed | |
485 | |
486 if path is None: | |
487 path = cpg.request.objectPath or cpg.request.path | |
488 if path.startswith('/'): | |
489 path = path[1:] # Remove leading slash | |
490 if path.endswith('/'): | |
491 path = path[:-1] # Remove trailing slash | |
492 | |
493 if not path: | |
494 objectPathList = [] | |
495 else: | |
496 objectPathList = path.split('/') | |
497 objectPathList = ['root'] + objectPathList + ['index'] | |
498 | |
499 # Try successive objects... (and also keep the remaining object list) | |
500 objCache = {} | |
501 isFirst = True | |
502 isSecond = False | |
503 isDefault = False | |
504 foundIt = False | |
505 virtualPathList = [] | |
506 while objectPathList: | |
507 if isFirst or isSecond: | |
508 # Only try this for a.b.index() or a.b() | |
509 candidate = getObjFromPath(objectPathList, objCache) | |
510 if callable(candidate) and getattr(candidate, 'exposed', False): | |
511 foundIt = True | |
512 break | |
513 # Couldn't find the object: pop one from the list and try "default" | |
514 lastObj = objectPathList.pop() | |
515 if (not isFirst) or (not path): | |
516 virtualPathList.insert(0, lastObj) | |
517 objectPathList.append('default') | |
518 candidate = getObjFromPath(objectPathList, objCache) | |
519 if callable(candidate) and getattr(candidate, 'exposed', False): | |
520 foundIt = True | |
521 isDefault = True | |
522 break | |
523 objectPathList.pop() # Remove "default" | |
524 if isSecond: | |
525 isSecond = False | |
526 if isFirst: | |
527 isFirst = False | |
528 isSecond = True | |
529 | |
530 # Check results of traversal | |
531 if not foundIt: | |
532 raise cperror.NotFound # We didn't find anything | |
533 | |
534 if isFirst: | |
535 # We found the extra ".index" | |
536 # Check if the original path had a trailing slash (otherwise, do | |
537 # a redirect) | |
538 if cpg.request.path[-1] != '/': | |
539 newUrl = cpg.request.path + '/' | |
540 if cpg.request.queryString: newUrl += cpg.request.queryString | |
541 raise IndexRedirect(newUrl) | |
542 | |
543 return candidate, objectPathList, virtualPathList |