--- a/web/views/staticcontrollers.py Mon Jan 04 18:40:30 2016 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,272 +0,0 @@
-# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of CubicWeb.
-#
-# CubicWeb is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""Set of static resources controllers for :
-
-- /data/...
-- /static/...
-- /fckeditor/...
-"""
-
-import os
-import os.path as osp
-import hashlib
-import mimetypes
-import threading
-import tempfile
-from time import mktime
-from datetime import datetime, timedelta
-from logging import getLogger
-
-from cubicweb import Forbidden
-from cubicweb.web import NotFound, Redirect
-from cubicweb.web.http_headers import generateDateTime
-from cubicweb.web.controller import Controller
-from cubicweb.web.views.urlrewrite import URLRewriter
-
-
-
-class StaticFileController(Controller):
- """an abtract class to serve static file
-
- Make sure to add your subclass to the STATIC_CONTROLLERS list"""
- __abstract__ = True
- directory_listing_allowed = False
-
- def max_age(self, path):
- """max cache TTL"""
- return 60*60*24*7
-
- def static_file(self, path):
- """Return full content of a static file.
-
- XXX iterable content would be better
- """
- debugmode = self._cw.vreg.config.debugmode
- if osp.isdir(path):
- if self.directory_listing_allowed:
- return u''
- raise Forbidden(path)
- if not osp.isfile(path):
- raise NotFound()
- if not debugmode:
- # XXX: Don't provide additional resource information to error responses
- #
- # the HTTP RFC recommends not going further than 1 year ahead
- expires = datetime.now() + timedelta(seconds=self.max_age(path))
- self._cw.set_header('Expires', generateDateTime(mktime(expires.timetuple())))
- self._cw.set_header('Cache-Control', 'max-age=%s' % self.max_age(path))
-
- # XXX system call to os.stats could be cached once and for all in
- # production mode (where static files are not expected to change)
- #
- # Note that: we do a osp.isdir + osp.isfile before and a potential
- # os.read after. Improving this specific call will not help
- #
- # Real production environment should use dedicated static file serving.
- self._cw.set_header('last-modified', generateDateTime(os.stat(path).st_mtime))
- if self._cw.is_client_cache_valid():
- return ''
- # XXX elif uri.startswith('/https/'): uri = uri[6:]
- mimetype, encoding = mimetypes.guess_type(path)
- if mimetype is None:
- mimetype = 'application/octet-stream'
- self._cw.set_content_type(mimetype, osp.basename(path), encoding)
- with open(path, 'rb') as resource:
- return resource.read()
-
- @property
- def relpath(self):
- """path of a requested file relative to the controller"""
- path = self._cw.form.get('static_relative_path')
- if path is None:
- path = self._cw.relative_path(includeparams=True)
- return path
-
-
-class ConcatFilesHandler(object):
- """Emulating the behavior of modconcat
-
- this serve multiple file as a single one.
- """
-
- def __init__(self, config):
- self._resources = {}
- self.config = config
- self.logger = getLogger('cubicweb.web')
- self.lock = threading.Lock()
-
- def _resource(self, path):
- """get the resouce"""
- try:
- return self._resources[path]
- except KeyError:
- self._resources[path] = self.config.locate_resource(path)
- return self._resources[path]
-
- def _up_to_date(self, filepath, paths):
- """
- The concat-file is considered up-to-date if it exists.
- In debug mode, an additional check is performed to make sure that
- concat-file is more recent than all concatenated files
- """
- if not osp.isfile(filepath):
- return False
- if self.config.debugmode:
- concat_lastmod = os.stat(filepath).st_mtime
- for path in paths:
- dirpath, rid = self._resource(path)
- if rid is None:
- raise NotFound(path)
- path = osp.join(dirpath, rid)
- if os.stat(path).st_mtime > concat_lastmod:
- return False
- return True
-
- def build_filepath(self, paths):
- """return the filepath that will be used to cache concatenation of `paths`
- """
- _, ext = osp.splitext(paths[0])
- fname = 'cache_concat_' + hashlib.md5((';'.join(paths)).encode('ascii')).hexdigest() + ext
- return osp.join(self.config.appdatahome, 'uicache', fname)
-
- def concat_cached_filepath(self, paths):
- filepath = self.build_filepath(paths)
- if not self._up_to_date(filepath, paths):
- with self.lock:
- if self._up_to_date(filepath, paths):
- # first check could have raced with some other thread
- # updating the file
- return filepath
- fd, tmpfile = tempfile.mkstemp(dir=os.path.dirname(filepath))
- try:
- f = os.fdopen(fd, 'wb')
- for path in paths:
- dirpath, rid = self._resource(path)
- if rid is None:
- # In production mode log an error, do not return a 404
- # XXX the erroneous content is cached anyway
- self.logger.error('concatenated data url error: %r file '
- 'does not exist', path)
- if self.config.debugmode:
- raise NotFound(path)
- else:
- with open(osp.join(dirpath, rid), 'rb') as source:
- for line in source:
- f.write(line)
- f.write(b'\n')
- f.close()
- except:
- os.remove(tmpfile)
- raise
- else:
- os.rename(tmpfile, filepath)
- return filepath
-
-
-class DataController(StaticFileController):
- """Controller in charge of serving static files in /data/
-
- Handles mod_concat-like URLs.
- """
-
- __regid__ = 'data'
-
- def __init__(self, *args, **kwargs):
- super(DataController, self).__init__(*args, **kwargs)
- config = self._cw.vreg.config
- self.base_datapath = config.data_relpath()
- self.data_modconcat_basepath = '%s??' % self.base_datapath
- self.concat_files_registry = ConcatFilesHandler(config)
-
- def publish(self, rset=None):
- config = self._cw.vreg.config
- # includeparams=True for modconcat-like urls
- relpath = self.relpath
- if relpath.startswith(self.data_modconcat_basepath):
- paths = relpath[len(self.data_modconcat_basepath):].split(',')
- filepath = self.concat_files_registry.concat_cached_filepath(paths)
- else:
- if not relpath.startswith(self.base_datapath):
- # /data/foo, redirect to /data/{hash}/foo
- prefix = 'data/'
- relpath = relpath[len(prefix):]
- raise Redirect(self._cw.data_url(relpath), 302)
- # skip leading '/data/{hash}/' and url params
- prefix = self.base_datapath
- relpath = relpath[len(prefix):]
- relpath = relpath.split('?', 1)[0]
- dirpath, rid = config.locate_resource(relpath)
- if dirpath is None:
- raise NotFound()
- filepath = osp.join(dirpath, rid)
- return self.static_file(filepath)
-
-
-class FCKEditorController(StaticFileController):
- """Controller in charge of serving FCKEditor related file
-
- The motivational for a dedicated controller have been lost.
- """
-
- __regid__ = 'fckeditor'
-
- def publish(self, rset=None):
- config = self._cw.vreg.config
- if self._cw.https:
- uiprops = config.https_uiprops
- else:
- uiprops = config.uiprops
- relpath = self.relpath
- if relpath.startswith('fckeditor/'):
- relpath = relpath[len('fckeditor/'):]
- relpath = relpath.split('?', 1)[0]
- return self.static_file(osp.join(uiprops['FCKEDITOR_PATH'], relpath))
-
-
-class StaticDirectoryController(StaticFileController):
- """Controller in charge of serving static file in /static/
- """
- __regid__ = 'static'
-
- def publish(self, rset=None):
- staticdir = self._cw.vreg.config.static_directory
- relpath = self.relpath[len(self.__regid__) + 1:]
- return self.static_file(osp.join(staticdir, relpath))
-
-STATIC_CONTROLLERS = [DataController, FCKEditorController,
- StaticDirectoryController]
-
-class StaticControlerRewriter(URLRewriter):
- """a quick and dirty rewritter in charge of server static file.
-
- This is a work around the flatness of url handling in cubicweb."""
-
- __regid__ = 'static'
-
- priority = 10
-
- def rewrite(self, req, uri):
- for ctrl in STATIC_CONTROLLERS:
- if uri.startswith('/%s/' % ctrl.__regid__):
- break
- else:
- self.debug("not a static file uri: %s", uri)
- raise KeyError(uri)
- relpath = self._cw.relative_path(includeparams=False)
- self._cw.form['static_relative_path'] = self._cw.relative_path(includeparams=True)
- return ctrl.__regid__, None