patch_servlet.py revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
1a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)# Copyright 2013 The Chromium Authors. All rights reserved.
2a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
3a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)# found in the LICENSE file.
4a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
5a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from fnmatch import fnmatch
6a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)import logging
7a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from urlparse import urlparse
8a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
9a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from appengine_url_fetcher import AppEngineUrlFetcher
10a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from appengine_wrappers import IsDevServer
11a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from caching_file_system import CachingFileSystem
12a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from caching_rietveld_patcher import CachingRietveldPatcher
13a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from chained_compiled_file_system import ChainedCompiledFileSystem
14a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from compiled_file_system import  CompiledFileSystem
154e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)from host_file_system_provider import HostFileSystemProvider
16a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from instance_servlet import InstanceServlet
17a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from render_servlet import RenderServlet
18a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from rietveld_patcher import RietveldPatcher, RietveldPatcherError
19a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from object_store_creator import ObjectStoreCreator
20a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from patched_file_system import PatchedFileSystem
21a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from server_instance import ServerInstance
22a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from servlet import Request, Response, Servlet
23a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)import svn_constants
24a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)import url_constants
25a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
26a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)class _PatchServletDelegate(RenderServlet.Delegate):
27a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  def __init__(self, issue, delegate):
28a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    self._issue = issue
29a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    self._delegate = delegate
30a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
31ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  def CreateServerInstance(self):
324e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # start_empty=False because a patch can rely on files that are already in
334e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # SVN repository but not yet pulled into data store by cron jobs (a typical
34a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    # example is to add documentation for an existing API).
354e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    object_store_creator = ObjectStoreCreator(start_empty=False)
364e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
374e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    unpatched_file_system = self._delegate.CreateHostFileSystemProvider(
384e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        object_store_creator).GetTrunk()
39a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
40a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    rietveld_patcher = CachingRietveldPatcher(
41a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        RietveldPatcher(svn_constants.EXTENSIONS_PATH,
42a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)                        self._issue,
43a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)                        AppEngineUrlFetcher(url_constants.CODEREVIEW_SERVER)),
44a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        object_store_creator)
454e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
464e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    patched_file_system = PatchedFileSystem(unpatched_file_system,
47a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)                                            rietveld_patcher)
48a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
494e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    patched_host_file_system_provider = (
504e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        self._delegate.CreateHostFileSystemProvider(
514e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)            object_store_creator,
524e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)            # The patched file system needs to be online otherwise it'd be
534e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)            # impossible to add files in the patches.
544e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)            offline=False,
554e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)            # The trunk file system for this creator should be the patched one.
564e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)            default_trunk_instance=patched_file_system))
574e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
584e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    combined_compiled_fs_factory = ChainedCompiledFileSystem.Factory(
594e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        [unpatched_file_system], object_store_creator)
604e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
614e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    branch_utility = self._delegate.CreateBranchUtility(object_store_creator)
627dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
63ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    return ServerInstance(object_store_creator,
64a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)                          self._delegate.CreateAppSamplesFileSystem(
65ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch                              object_store_creator),
664e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                          combined_compiled_fs_factory,
677dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch                          branch_utility,
684e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                          patched_host_file_system_provider,
694e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                          base_path='/_patch/%s/' % self._issue)
70a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
71a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)class PatchServlet(Servlet):
72a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  '''Servlet which renders patched docs.
73a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  '''
74a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  def __init__(self, request, delegate=None):
75a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    self._request = request
76a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    self._delegate = delegate or InstanceServlet.Delegate()
77a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
78a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  def Get(self):
79a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    if (not IsDevServer() and
80a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        not fnmatch(urlparse(self._request.host).netloc, '*.appspot.com')):
81a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      # Only allow patches on appspot URLs; it doesn't matter if appspot.com is
82a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      # XSS'ed, but it matters for chrome.com.
83a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      redirect_host = 'https://chrome-apps-doc.appspot.com'
84a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      logging.info('Redirecting from XSS-able host %s to %s' % (
85a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)          self._request.host, redirect_host))
86a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      return Response.Redirect(
87a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)          '%s/_patch/%s' % (redirect_host, self._request.path))
88a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
89a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    path_with_issue = self._request.path.lstrip('/')
90a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    if '/' in path_with_issue:
91ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      issue, path_without_issue = path_with_issue.split('/', 1)
92a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    else:
93a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      return Response.NotFound('Malformed URL. It should look like ' +
94a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)          'https://developer.chrome.com/_patch/12345/extensions/...')
95a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
96a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    try:
97a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      response = RenderServlet(
98ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch          Request(path_without_issue,
99ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch                  self._request.host,
100ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch                  self._request.headers),
101a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)          _PatchServletDelegate(issue, self._delegate)).Get()
102a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      # Disable cache for patched content.
103a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      response.headers.pop('cache-control', None)
104a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    except RietveldPatcherError as e:
105a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      response = Response.NotFound(e.message, {'Content-Type': 'text/plain'})
106a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
107a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    redirect_url, permanent = response.GetRedirect()
108a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    if redirect_url is not None:
109a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      response = Response.Redirect('/_patch/%s%s' % (issue, redirect_url),
110a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)                                   permanent)
111a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    return response
112