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