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