Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / common / extensions / docs / server2 / content_provider.py
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 mimetypes
6 import posixpath
7
8 from compiled_file_system import SingleFile
9 from directory_zipper import DirectoryZipper
10 from docs_server_utils import ToUnicode
11 from file_system import FileNotFoundError
12 from future import Gettable, Future
13 from path_canonicalizer import PathCanonicalizer
14 from path_util import AssertIsValid, Join, ToDirectory
15 from special_paths import SITE_VERIFICATION_FILE
16 from third_party.handlebar import Handlebar
17 from third_party.markdown import markdown
18
19
20 _MIMETYPE_OVERRIDES = {
21   # SVG is not supported by mimetypes.guess_type on AppEngine.
22   '.svg': 'image/svg+xml',
23 }
24
25
26 class ContentAndType(object):
27   '''Return value from ContentProvider.GetContentAndType.
28   '''
29
30   def __init__(self, content, content_type):
31     self.content = content
32     self.content_type = content_type
33
34
35 class ContentProvider(object):
36   '''Returns file contents correctly typed for their content-types (in the HTTP
37   sense). Content-type is determined from Python's mimetype library which
38   guesses based on the file extension.
39
40   Typically the file contents will be either str (for binary content) or
41   unicode (for text content). However, HTML files *may* be returned as
42   Handlebar templates (if |supports_templates| is True on construction), in
43   which case the caller will presumably want to Render them.
44
45   Zip file are automatically created and returned for .zip file extensions if
46   |supports_zip| is True.
47
48   |default_extensions| is a list of file extensions which are queried when no
49   file extension is given to GetCanonicalPath/GetContentAndType.  Typically
50   this will include .html.
51   '''
52
53   def __init__(self,
54                name,
55                compiled_fs_factory,
56                file_system,
57                object_store_creator,
58                default_extensions=(),
59                supports_templates=False,
60                supports_zip=False):
61     # Public.
62     self.name = name
63     self.file_system = file_system
64     # Private.
65     self._content_cache = compiled_fs_factory.Create(file_system,
66                                                      self._CompileContent,
67                                                      ContentProvider)
68     self._path_canonicalizer = PathCanonicalizer(file_system,
69                                                  object_store_creator,
70                                                  default_extensions)
71     self._default_extensions = default_extensions
72     self._supports_templates = supports_templates
73     if supports_zip:
74       self._directory_zipper = DirectoryZipper(compiled_fs_factory, file_system)
75     else:
76       self._directory_zipper = None
77
78   @SingleFile
79   def _CompileContent(self, path, text):
80     assert text is not None, path
81     _, ext = posixpath.splitext(path)
82     mimetype = _MIMETYPE_OVERRIDES.get(ext, mimetypes.guess_type(path)[0])
83     if ext == '.md':
84       # See http://pythonhosted.org/Markdown/extensions
85       # for details on "extensions=".
86       content = markdown(ToUnicode(text),
87                          extensions=('extra', 'headerid', 'sane_lists'))
88       if self._supports_templates:
89         content = Handlebar(content, name=path)
90       mimetype = 'text/html'
91     elif mimetype is None:
92       content = text
93       mimetype = 'text/plain'
94     elif mimetype == 'text/html':
95       content = ToUnicode(text)
96       if self._supports_templates:
97         content = Handlebar(content, name=path)
98     elif (mimetype.startswith('text/') or
99           mimetype in ('application/javascript', 'application/json')):
100       content = ToUnicode(text)
101     else:
102       content = text
103     return ContentAndType(content, mimetype)
104
105   def GetCanonicalPath(self, path):
106     '''Gets the canonical location of |path|. This class is tolerant of
107     spelling errors and missing files that are in other directories, and this
108     returns the correct/canonical path for those.
109
110     For example, the canonical path of "browseraction" is probably
111     "extensions/browserAction.html".
112
113     Note that the canonical path is relative to this content provider i.e.
114     given relative to |path|. It does not add the "serveFrom" prefix which
115     would have been pulled out in ContentProviders, callers must do that
116     themselves.
117     '''
118     AssertIsValid(path)
119     base, ext = posixpath.splitext(path)
120     if self._directory_zipper and ext == '.zip':
121       # The canonical location of zip files is the canonical location of the
122       # directory to zip + '.zip'.
123       return self._path_canonicalizer.Canonicalize(base + '/').rstrip('/') + ext
124     return self._path_canonicalizer.Canonicalize(path)
125
126   def GetContentAndType(self, path):
127     '''Returns the ContentAndType of the file at |path|.
128     '''
129     AssertIsValid(path)
130     base, ext = posixpath.splitext(path)
131
132     # Check for a zip file first, if zip is enabled.
133     if self._directory_zipper and ext == '.zip':
134       zip_future = self._directory_zipper.Zip(ToDirectory(base))
135       return Future(delegate=Gettable(
136           lambda: ContentAndType(zip_future.Get(), 'application/zip')))
137
138     # If there is no file extension, look for a file with one of the default
139     # extensions.
140     #
141     # Note that it would make sense to guard this on Exists(path), since a file
142     # without an extension may actually exist, but it's such an uncommon case
143     # it hardly seems worth the potential performance hit.
144     if not ext:
145       for default_ext in self._default_extensions:
146         if self.file_system.Exists(path + default_ext).Get():
147           path += default_ext
148           break
149
150     return self._content_cache.GetFromFile(path)
151
152   def Cron(self):
153     futures = [self._path_canonicalizer.Cron()]
154     for root, _, files in self.file_system.Walk(''):
155       for f in files:
156         futures.append(self.GetContentAndType(Join(root, f)))
157         # Also cache the extension-less version of the file if needed.
158         base, ext = posixpath.splitext(f)
159         if f != SITE_VERIFICATION_FILE and ext in self._default_extensions:
160           futures.append(self.GetContentAndType(Join(root, base)))
161       # TODO(kalman): Cache .zip files for each directory (if supported).
162     return Future(delegate=Gettable(lambda: [f.Get() for f in futures]))
163
164   def __repr__(self):
165     return 'ContentProvider of <%s>' % repr(self.file_system)