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.
5 from fnmatch
import fnmatch
7 from urlparse
import urlparse
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
22 from gcs_file_system_provider
import CloudStorageFileSystemProvider
25 class _PatchServletDelegate(RenderServlet
.Delegate
):
26 def __init__(self
, issue
, delegate
):
28 self
._delegate
= delegate
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)
36 unpatched_file_system
= self
._delegate
.CreateHostFileSystemProvider(
37 object_store_creator
).GetTrunk()
39 rietveld_patcher
= CachingRietveldPatcher(
40 RietveldPatcher(self
._issue
,
41 AppEngineUrlFetcher(url_constants
.CODEREVIEW_SERVER
)),
44 patched_file_system
= PatchedFileSystem(unpatched_file_system
,
47 patched_host_file_system_provider
= (
48 self
._delegate
.CreateHostFileSystemProvider(
50 # The patched file system needs to be online otherwise it'd be
51 # impossible to add files in the patches.
53 # The trunk file system for this creator should be the patched one.
54 default_trunk_instance
=patched_file_system
))
56 combined_compiled_fs_factory
= ChainedCompiledFileSystem
.Factory(
57 [unpatched_file_system
], object_store_creator
)
59 branch_utility
= self
._delegate
.CreateBranchUtility(object_store_creator
)
61 server_instance
= ServerInstance(
63 combined_compiled_fs_factory
,
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
)
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()
76 return server_instance
78 class PatchServlet(Servlet
):
79 '''Servlet which renders patched docs.
81 def __init__(self
, request
, delegate
=None):
82 self
._request
= request
83 self
._delegate
= delegate
or InstanceServlet
.Delegate()
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
))
96 path_with_issue
= self
._request
.path
.lstrip('/')
97 if '/' in path_with_issue
:
98 issue
, path_without_issue
= path_with_issue
.split('/', 1)
100 return Response
.NotFound('Malformed URL. It should look like ' +
101 'https://developer.chrome.com/_patch/12345/extensions/...')
104 response
= RenderServlet(
105 Request(path_without_issue
,
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'})
114 redirect_url
, permanent
= response
.GetRedirect()
115 if redirect_url
is not None:
116 response
= Response
.Redirect('/_patch/%s%s' % (issue
, redirect_url
),