1c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)# Copyright 2013 The Chromium Authors. All rights reserved.
2c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
3c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)# found in the LICENSE file.
4c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
5ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochimport logging
6ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochimport posixpath
7ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochimport traceback
8ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
9ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochfrom branch_utility import BranchUtility
10d57369da7c6519fef57db42085f7b42d4c8845c1Torne (Richard Coles)from compiled_file_system import SingleFile
11f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)from extensions_paths import PUBLIC_TEMPLATES
12ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochfrom file_system import FileNotFoundError
13f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
14c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
15ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochdef _SimplifyFileName(file_name):
16ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  return (posixpath.splitext(file_name)[0]
17ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      .lower()
18ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      .replace('.', '')
19ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      .replace('-', '')
20ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      .replace('_', ''))
21ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
22f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
23c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)class PathCanonicalizer(object):
24c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  '''Transforms paths into their canonical forms. Since the dev server has had
25c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  many incarnations - e.g. there didn't use to be apps/ - there may be old
26c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  paths lying around the webs. We try to redirect those to where they are now.
27c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  '''
284e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  def __init__(self, compiled_fs_factory, file_system):
29ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    # Map of simplified API names (for typo detection) to their real paths.
300f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    @SingleFile
31ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    def make_public_apis(_, file_names):
32ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      return dict((_SimplifyFileName(name), name) for name in file_names)
334e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    self._public_apis = compiled_fs_factory.Create(file_system,
344e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                                   make_public_apis,
35ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch                                                   PathCanonicalizer)
36c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
37c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  def Canonicalize(self, path):
38ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    '''Returns the canonical path for |path|, and whether that path is a
39ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    permanent canonicalisation (e.g. when we redirect from a channel to a
40ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    channel-less URL) or temporary (e.g. when we redirect from an apps-only API
41ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    to an extensions one - we may at some point enable it for extensions).
42ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    '''
43ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    class ReturnType(object):
44ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      def __init__(self, path, permanent):
45ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        self.path = path
46ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        self.permanent = permanent
47ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
48ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      # Catch incorrect comparisons by disabling ==/!=.
49ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      def __eq__(self, _): raise NotImplementedError()
50ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      def __ne__(self, _): raise NotImplementedError()
51ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
52ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    # Strip any channel info off it. There are no channels anymore.
53ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    for channel_name in BranchUtility.GetAllChannelNames():
54ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      channel_prefix = channel_name + '/'
55ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      if path.startswith(channel_prefix):
56ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        # Redirect now so that we can set the permanent-redirect bit.  Channel
57ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        # redirects are the only things that should be permanent redirects;
58ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        # anything else *could* change, so is temporary.
59ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        return ReturnType(path[len(channel_prefix):], True)
60ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
61ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    # No further work needed for static.
62ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    if path.startswith('static/'):
63ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      return ReturnType(path, False)
64ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
65ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    # People go to just "extensions" or "apps". Redirect to the directory.
66ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    if path in ('extensions', 'apps'):
67ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      return ReturnType(path + '/', False)
68ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
69ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    # The rest of this function deals with trying to figure out what API page
70ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    # for extensions/apps to redirect to, if any. We see a few different cases
71ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    # here:
72ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    #  - Unqualified names ("browserAction.html"). These are easy to resolve;
73ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    #    figure out whether it's an extension or app API and redirect.
74ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    #     - but what if it's both? Well, assume extensions. Maybe later we can
75ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    #       check analytics and see which is more popular.
76ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    #  - Wrong names ("apps/browserAction.html"). This really does happen,
77ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    #    damn it, so do the above logic but record which is the default.
78ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    if path.startswith(('extensions/', 'apps/')):
79ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      default_platform, reference_path = path.split('/', 1)
80ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    else:
81ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      default_platform, reference_path = ('extensions', path)
82ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
83ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    try:
84ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      apps_public = self._public_apis.GetFromFileListing(
85f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          '%s/apps' % PUBLIC_TEMPLATES).Get()
86ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      extensions_public = self._public_apis.GetFromFileListing(
87f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          '%s/extensions' % PUBLIC_TEMPLATES).Get()
88ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    except FileNotFoundError:
89ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      # Probably offline.
90ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      logging.warning(traceback.format_exc())
91ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      return ReturnType(path, False)
92ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
93ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    simple_reference_path = _SimplifyFileName(reference_path)
94ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    apps_path = apps_public.get(simple_reference_path)
95ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    extensions_path = extensions_public.get(simple_reference_path)
96ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
97ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    if apps_path is None:
98ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      if extensions_path is None:
99ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        # No idea. Just return the original path. It'll probably 404.
100ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        pass
101ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      else:
102ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        path = 'extensions/%s' % extensions_path
103c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    else:
104ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      if extensions_path is None:
105ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        path = 'apps/%s' % apps_path
106ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      else:
107ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        assert apps_path == extensions_path
108ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        path = '%s/%s' % (default_platform, apps_path)
109ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
110ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    return ReturnType(path, False)
111