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 3 # found in the LICENSE file. 4 5 from fnmatch import fnmatch 6 import logging 7 from urlparse import urlparse 8 9 from appengine_url_fetcher import AppEngineUrlFetcher 10 from caching_rietveld_patcher import CachingRietveldPatcher 11 from chained_compiled_file_system import ChainedCompiledFileSystem 12 from environment import IsDevServer 13 from extensions_paths import CONTENT_PROVIDERS 14 from instance_servlet import InstanceServlet 15 from render_servlet import RenderServlet 16 from rietveld_patcher import RietveldPatcher, RietveldPatcherError 17 from object_store_creator import ObjectStoreCreator 18 from patched_file_system import PatchedFileSystem 19 from server_instance import ServerInstance 20 from servlet import Request, Response, Servlet 21 import url_constants 22 from gcs_file_system_provider import CloudStorageFileSystemProvider 23 24 25 class _PatchServletDelegate(RenderServlet.Delegate): 26 def __init__(self, issue, delegate): 27 self._issue = issue 28 self._delegate = delegate 29 30 def CreateServerInstance(self): 31 # start_empty=False because a patch can rely on files that are already in 32 # SVN repository but not yet pulled into data store by cron jobs (a typical 33 # example is to add documentation for an existing API). 34 object_store_creator = ObjectStoreCreator(start_empty=False) 35 36 unpatched_file_system = self._delegate.CreateHostFileSystemProvider( 37 object_store_creator).GetTrunk() 38 39 rietveld_patcher = CachingRietveldPatcher( 40 RietveldPatcher(self._issue, 41 AppEngineUrlFetcher(url_constants.CODEREVIEW_SERVER)), 42 object_store_creator) 43 44 patched_file_system = PatchedFileSystem(unpatched_file_system, 45 rietveld_patcher) 46 47 patched_host_file_system_provider = ( 48 self._delegate.CreateHostFileSystemProvider( 49 object_store_creator, 50 # The patched file system needs to be online otherwise it'd be 51 # impossible to add files in the patches. 52 offline=False, 53 # The trunk file system for this creator should be the patched one. 54 default_trunk_instance=patched_file_system)) 55 56 combined_compiled_fs_factory = ChainedCompiledFileSystem.Factory( 57 [unpatched_file_system], object_store_creator) 58 59 branch_utility = self._delegate.CreateBranchUtility(object_store_creator) 60 61 server_instance = ServerInstance( 62 object_store_creator, 63 combined_compiled_fs_factory, 64 branch_utility, 65 patched_host_file_system_provider, 66 self._delegate.CreateGithubFileSystemProvider(object_store_creator), 67 CloudStorageFileSystemProvider(object_store_creator), 68 base_path='/_patch/%s/' % self._issue) 69 70 # HACK: if content_providers.json changes in this patch then the cron needs 71 # to be re-run to pull in the new configuration. 72 _, _, modified = rietveld_patcher.GetPatchedFiles() 73 if CONTENT_PROVIDERS in modified: 74 server_instance.content_providers.Cron().Get() 75 76 return server_instance 77 78 class PatchServlet(Servlet): 79 '''Servlet which renders patched docs. 80 ''' 81 def __init__(self, request, delegate=None): 82 self._request = request 83 self._delegate = delegate or InstanceServlet.Delegate() 84 85 def Get(self): 86 if (not IsDevServer() and 87 not fnmatch(urlparse(self._request.host).netloc, '*.appspot.com')): 88 # Only allow patches on appspot URLs; it doesn't matter if appspot.com is 89 # XSS'ed, but it matters for chrome.com. 90 redirect_host = 'https://chrome-apps-doc.appspot.com' 91 logging.info('Redirecting from XSS-able host %s to %s' % ( 92 self._request.host, redirect_host)) 93 return Response.Redirect( 94 '%s/_patch/%s' % (redirect_host, self._request.path)) 95 96 path_with_issue = self._request.path.lstrip('/') 97 if '/' in path_with_issue: 98 issue, path_without_issue = path_with_issue.split('/', 1) 99 else: 100 return Response.NotFound('Malformed URL. It should look like ' + 101 'https://developer.chrome.com/_patch/12345/extensions/...') 102 103 try: 104 response = RenderServlet( 105 Request(path_without_issue, 106 self._request.host, 107 self._request.headers), 108 _PatchServletDelegate(issue, self._delegate)).Get() 109 # Disable cache for patched content. 110 response.headers.pop('cache-control', None) 111 except RietveldPatcherError as e: 112 response = Response.NotFound(e.message, {'Content-Type': 'text/plain'}) 113 114 redirect_url, permanent = response.GetRedirect() 115 if redirect_url is not None: 116 response = Response.Redirect('/_patch/%s%s' % (issue, redirect_url), 117 permanent) 118 return response 119