| OLD | NEW |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 from fnmatch import fnmatch | 5 from fnmatch import fnmatch |
| 6 import logging | 6 import logging |
| 7 import mimetypes | 7 import mimetypes |
| 8 import posixpath |
| 9 import traceback |
| 8 from urlparse import urlsplit | 10 from urlparse import urlsplit |
| 9 | 11 |
| 10 from data_source_registry import CreateDataSources | 12 from data_source_registry import CreateDataSources |
| 11 from file_system import FileNotFoundError | 13 from file_system import FileNotFoundError |
| 14 from redirector import Redirector |
| 12 from servlet import Servlet, Response | 15 from servlet import Servlet, Response |
| 13 from svn_constants import DOCS_PATH, PUBLIC_TEMPLATE_PATH | 16 from svn_constants import DOCS_PATH, PUBLIC_TEMPLATE_PATH |
| 17 from third_party.handlebar import Handlebar |
| 14 | 18 |
| 15 def _IsBinaryMimetype(mimetype): | 19 |
| 16 return any( | 20 def _MakeHeaders(content_type): |
| 17 mimetype.startswith(prefix) for prefix in ['audio', 'image', 'video']) | 21 return { |
| 22 'x-frame-options': 'sameorigin', |
| 23 'content-type': content_type, |
| 24 'cache-control': 'max-age=300', |
| 25 } |
| 26 |
| 18 | 27 |
| 19 class RenderServlet(Servlet): | 28 class RenderServlet(Servlet): |
| 20 '''Servlet which renders templates. | 29 '''Servlet which renders templates. |
| 21 ''' | 30 ''' |
| 31 |
| 22 class Delegate(object): | 32 class Delegate(object): |
| 23 def CreateServerInstance(self): | 33 def CreateServerInstance(self): |
| 24 raise NotImplementedError(self.__class__) | 34 raise NotImplementedError(self.__class__) |
| 25 | 35 |
| 26 def __init__(self, request, delegate): | 36 def __init__(self, request, delegate): |
| 27 Servlet.__init__(self, request) | 37 Servlet.__init__(self, request) |
| 28 self._delegate = delegate | 38 self._delegate = delegate |
| 29 | 39 |
| 30 def Get(self): | 40 def Get(self): |
| 31 ''' Render the page for a request. | 41 ''' Render the page for a request. |
| 32 ''' | 42 ''' |
| 33 # TODO(kalman): a consistent path syntax (even a Path class?) so that we | 43 # TODO(kalman): a consistent path syntax (even a Path class?) so that we |
| 34 # can stop being so conservative with stripping and adding back the '/'s. | 44 # can stop being so conservative with stripping and adding back the '/'s. |
| 35 path = self._request.path.lstrip('/') | 45 path = self._request.path.lstrip('/') |
| 36 | |
| 37 if path.split('/')[-1] == 'redirects.json': | |
| 38 return Response.Ok('') | |
| 39 | |
| 40 server_instance = self._delegate.CreateServerInstance() | 46 server_instance = self._delegate.CreateServerInstance() |
| 41 | 47 |
| 42 redirect = server_instance.redirector.Redirect(self._request.host, path) | 48 try: |
| 49 return self._GetSuccessResponse(path, server_instance) |
| 50 except FileNotFoundError: |
| 51 # Maybe it didn't find the file because its canonical location is |
| 52 # somewhere else; this is distinct from "redirects", which are typically |
| 53 # explicit. This is implicit. |
| 54 canonical_result = server_instance.path_canonicalizer.Canonicalize(path) |
| 55 redirect = canonical_result.path.lstrip('/') |
| 56 if path != redirect: |
| 57 return Response.Redirect('/' + redirect, |
| 58 permanent=canonical_result.permanent) |
| 59 |
| 60 # Not found for reals. Find the closest 404.html file and serve that; |
| 61 # e.g. if the path is extensions/manifest/typo.html then first look for |
| 62 # extensions/manifest/404.html, then extensions/404.html, then 404.html. |
| 63 # |
| 64 # Failing that just print 'Not Found' but that should preferrably never |
| 65 # happen, because it would look really bad. |
| 66 path_components = path.split('/') |
| 67 for i in xrange(len(path_components) - 1, -1, -1): |
| 68 try: |
| 69 path_404 = posixpath.join(*(path_components[0:i] + ['404.html'])) |
| 70 response = self._GetSuccessResponse(path_404, server_instance) |
| 71 return Response.NotFound(response.content.ToString(), |
| 72 headers=response.headers) |
| 73 except FileNotFoundError: continue |
| 74 logging.error('No 404.html found in %s' % path) |
| 75 return Response.NotFound('Not Found', headers=_MakeHeaders('text/plain')) |
| 76 |
| 77 def _GetSuccessResponse(self, path, server_instance): |
| 78 '''Returns the Response from trying to render |path| with |
| 79 |server_instance|. If |path| isn't found then a FileNotFoundError will be |
| 80 raised, such that the only responses that will be returned from this method |
| 81 are Ok and Redirect. |
| 82 ''' |
| 83 content_provider, path = ( |
| 84 server_instance.content_providers.GetByServlet(path)) |
| 85 assert content_provider, 'No ContentProvider found for %s' % path |
| 86 |
| 87 redirect = Redirector( |
| 88 server_instance.compiled_fs_factory, |
| 89 content_provider.file_system).Redirect(self._request.host, path) |
| 43 if redirect is not None: | 90 if redirect is not None: |
| 44 return Response.Redirect(redirect) | 91 return Response.Redirect(redirect, permanent=False) |
| 45 | 92 |
| 46 canonical_result = server_instance.path_canonicalizer.Canonicalize(path) | 93 content_info = content_provider.GetContentInfo( |
| 47 redirect = canonical_result.path.lstrip('/') | 94 self._request.host, path).Get() |
| 48 if path != redirect: | 95 if not content_info.content: |
| 49 return Response.Redirect('/' + redirect, | |
| 50 permanent=canonical_result.permanent) | |
| 51 | |
| 52 trunk_fs = server_instance.host_file_system_provider.GetTrunk() | |
| 53 template_renderer = server_instance.template_renderer | |
| 54 template_cache = server_instance.compiled_fs_factory.ForTemplates(trunk_fs) | |
| 55 | |
| 56 content = None | |
| 57 content_type = None | |
| 58 | |
| 59 try: | |
| 60 # At this point, any valid paths ending with '/' have been redirected. | |
| 61 # Therefore, the response should be a 404 Not Found. | |
| 62 if path.endswith('/'): | |
| 63 pass | |
| 64 elif fnmatch(path, 'extensions/examples/*.zip'): | |
| 65 zip_path = DOCS_PATH + path[len('extensions'):-len('.zip')] | |
| 66 content = server_instance.directory_zipper.Zip(zip_path).Get() | |
| 67 content_type = 'application/zip' | |
| 68 elif path.startswith('extensions/examples/'): | |
| 69 mimetype = mimetypes.guess_type(path)[0] or 'text/plain' | |
| 70 content = trunk_fs.ReadSingle( | |
| 71 '%s/%s' % (DOCS_PATH, path[len('extensions/'):]), | |
| 72 binary=_IsBinaryMimetype(mimetype)).Get() | |
| 73 content_type = mimetype | |
| 74 elif path.startswith('static/'): | |
| 75 mimetype = mimetypes.guess_type(path)[0] or 'text/plain' | |
| 76 content = trunk_fs.ReadSingle( | |
| 77 '%s/%s' % (DOCS_PATH, path), | |
| 78 binary=_IsBinaryMimetype(mimetype)).Get() | |
| 79 content_type = mimetype | |
| 80 elif path.endswith('.html'): | |
| 81 content = template_renderer.Render( | |
| 82 template_cache.GetFromFile( | |
| 83 '%s/%s' % (PUBLIC_TEMPLATE_PATH, path)).Get(), | |
| 84 self._request) | |
| 85 content_type = 'text/html' | |
| 86 else: | |
| 87 content = None | |
| 88 except FileNotFoundError: | |
| 89 content = None | |
| 90 | |
| 91 headers = {'x-frame-options': 'sameorigin'} | |
| 92 if content is None: | |
| 93 def render_template_or_none(path): | |
| 94 try: | |
| 95 return template_renderer.Render( | |
| 96 template_cache.GetFromFile( | |
| 97 '%s/%s' % (PUBLIC_TEMPLATE_PATH, path)).Get(), | |
| 98 self._request) | |
| 99 except FileNotFoundError: | |
| 100 return None | |
| 101 content = (render_template_or_none('%s/404.html' % | |
| 102 path.split('/', 1)[0]) or | |
| 103 render_template_or_none('extensions/404.html') or | |
| 104 'Not found') | |
| 105 return Response.NotFound(content, headers=headers) | |
| 106 | |
| 107 if not content: | |
| 108 logging.error('%s had empty content' % path) | 96 logging.error('%s had empty content' % path) |
| 109 | 97 |
| 110 headers.update({ | 98 if isinstance(content_info.content, Handlebar): |
| 111 'content-type': content_type, | 99 content_info.content = server_instance.template_renderer.Render( |
| 112 'cache-control': 'max-age=300', | 100 content_info.content, self._request) |
| 113 }) | 101 |
| 114 return Response.Ok(content, headers=headers) | 102 return Response.Ok(content_info.content, |
| 103 headers=_MakeHeaders(content_info.content_type)) |
| OLD | NEW |