diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 290e89e14e3..1c3723fa3b4 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -15,7 +15,6 @@ import re import json import sys import time -import urllib2 import zlib from xml.etree import ElementTree from cStringIO import StringIO @@ -33,13 +32,12 @@ except ImportError: import openerp import openerp.modules.registry from openerp.addons.base.ir.ir_qweb import AssetsBundle, QWebTemplateNotFound -from openerp.modules import get_module_path, get_resource_path +from openerp.modules import get_resource_path from openerp.tools import topological_sort from openerp.tools.translate import _ from openerp.tools import ustr from openerp import http -import mimetypes -from openerp.http import request, serialize_exception as _serialize_exception, STATIC_CACHE +from openerp.http import request, serialize_exception as _serialize_exception from openerp.exceptions import AccessError _logger = logging.getLogger(__name__) @@ -421,113 +419,13 @@ def xml2json_from_elementtree(el, preserve_whitespaces=False): return res def content_disposition(filename): - filename = ustr(filename) - escaped = urllib2.quote(filename.encode('utf8')) - browser = request.httprequest.user_agent.browser - version = int((request.httprequest.user_agent.version or '0').split('.')[0]) - if browser == 'msie' and version < 9: - return "attachment; filename=%s" % escaped - elif browser == 'safari' and version < 537: - return u"attachment; filename=%s" % filename.encode('ascii', 'replace') - else: - return "attachment; filename*=UTF-8''%s" % escaped + return request.registry['ir.http'].content_disposition(filename) + def binary_content(xmlid=None, model='ir.attachment', id=None, field='datas', unique=False, filename=None, filename_field='datas_fname', download=False, mimetype=None, default_mimetype='application/octet-stream', env=None): - """ Get file, attachment or downloadable content - - If the ``xmlid`` and ``id`` parameter is omitted, fetches the default value for the - binary field (via ``default_get``), otherwise fetches the field for - that precise record. - - :param str xmlid: xmlid of the record - :param str model: name of the model to fetch the binary from - :param int id: id of the record from which to fetch the binary - :param str field: binary field - :param bool unique: add a max-age for the cache control - :param str filename: choose a filename - :param str filename_field: if not create an filename with model-id-field - :param bool download: apply headers to download the file - :param str mimetype: mintype of the field (for headers) - :param str default_mimetype: default mintype if no mintype found - :param Environment env: by default use request.env - :returns: (status, headers, content) - """ - env = env or request.env - # get object and content - obj = None - if xmlid: - obj = env.ref(xmlid, False) - elif id and model in env.registry: - obj = env[model].browse(int(id)) - - # obj exists - if not obj or not obj.exists() or field not in obj: - return (404, [], None) - - # check read access - try: - last_update = obj['__last_update'] - except AccessError: - return (403, [], None) - - status, headers, content = None, [], None - - # attachment by url check - module_resource_path = None - if model == 'ir.attachment' and obj.type == 'url' and obj.url: - url_match = re.match("^/(\w+)/(.+)$", obj.url) - if url_match: - module = url_match.group(1) - module_path = get_module_path(module) - module_resource_path = get_resource_path(module, url_match.group(2)) - if module_path and module_resource_path: - module_path = os.path.join(os.path.normpath(module_path), '') # join ensures the path ends with '/' - module_resource_path = os.path.normpath(module_resource_path) - if module_resource_path.startswith(module_path): - with open(module_resource_path, 'r') as f: - content = base64.b64encode(f.read()) - last_update = str(os.path.getmtime(module_resource_path)) - - if not module_resource_path: - module_resource_path = obj.url - - if not content: - status = 301 - content = module_resource_path - else: - content = obj[field] or '' - - # filename - if not filename: - if filename_field in obj: - filename = obj[filename_field] - elif module_resource_path: - filename = os.path.basename(module_resource_path) - else: - filename = "%s-%s-%s" % (obj._model._name, obj.id, field) - - # mimetype - if not mimetype: - if 'mimetype' in obj and obj.mimetype and obj.mimetype != 'application/octet-stream': - mimetype = obj.mimetype - elif filename: - mimetype = mimetypes.guess_type(filename)[0] - if not mimetype: - mimetype = default_mimetype - headers.append(('Content-Type', mimetype)) - - # cache - etag = hasattr(request, 'httprequest') and request.httprequest.headers.get('If-None-Match') - retag = hashlib.md5(last_update).hexdigest() - status = status or (304 if etag == retag else 200) - headers.append(('ETag', retag)) - headers.append(('Cache-Control', 'max-age=%s' % (STATIC_CACHE if unique else 0))) - - # content-disposition default name - if download: - headers.append(('Content-Disposition', content_disposition(filename))) - - return (status, headers, content) + return request.registry['ir.http'].binary_content( + xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, filename_field=filename_field, + download=download, mimetype=mimetype, default_mimetype=default_mimetype, env=env) def db_info(): version_info = openerp.service.common.exp_version() diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index 72d710d2fda..5b5f175338f 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -296,6 +296,18 @@ class ir_http(orm.AbstractModel): html = request.website._render('website.http_error', values) return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8') + def binary_content(self, xmlid=None, model='ir.attachment', id=None, field='datas', unique=False, filename=None, filename_field='datas_fname', download=False, mimetype=None, default_mimetype='application/octet-stream', env=None): + env = env or request.env + obj = None + if xmlid: + obj = env.ref(xmlid, False) + elif id and model in self.pool: + obj = env[model].browse(int(id)) + if obj and 'website_published' in obj._fields: + if env[obj._name].sudo().search([('id', '=', obj.id), ('website_published', '=', True)]): + env = env(user=openerp.SUPERUSER_ID) + return super(ir_http, self).binary_content(xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename, filename_field=filename_field, download=download, mimetype=mimetype, default_mimetype=default_mimetype, env=env) + class ModelConverter(ir.ir_http.ModelConverter): def __init__(self, url_map, model=False, domain='[]'): super(ModelConverter, self).__init__(url_map, model) diff --git a/openerp/addons/base/ir/ir_http.py b/openerp/addons/base/ir/ir_http.py index f0bfd353f33..72e66e24c33 100644 --- a/openerp/addons/base/ir/ir_http.py +++ b/openerp/addons/base/ir/ir_http.py @@ -2,11 +2,15 @@ #---------------------------------------------------------- # ir_http modular http routing #---------------------------------------------------------- +import base64 import datetime import hashlib import logging +import mimetypes +import os import re import sys +import urllib2 import werkzeug import werkzeug.exceptions @@ -18,7 +22,8 @@ import openerp import openerp.exceptions import openerp.models from openerp import http -from openerp.http import request +from openerp.http import request, STATIC_CACHE +from openerp.modules.module import get_resource_path, get_module_path from openerp.osv import osv, orm _logger = logging.getLogger(__name__) @@ -203,6 +208,116 @@ class ir_http(osv.AbstractModel): return self._routing_map + def content_disposition(self, filename): + filename = openerp.tools.ustr(filename) + escaped = urllib2.quote(filename.encode('utf8')) + browser = request.httprequest.user_agent.browser + version = int((request.httprequest.user_agent.version or '0').split('.')[0]) + if browser == 'msie' and version < 9: + return "attachment; filename=%s" % escaped + elif browser == 'safari' and version < 537: + return u"attachment; filename=%s" % filename.encode('ascii', 'replace') + else: + return "attachment; filename*=UTF-8''%s" % escaped + + def binary_content(self, xmlid=None, model='ir.attachment', id=None, field='datas', unique=False, filename=None, filename_field='datas_fname', download=False, mimetype=None, default_mimetype='application/octet-stream', env=None): + """ Get file, attachment or downloadable content + + If the ``xmlid`` and ``id`` parameter is omitted, fetches the default value for the + binary field (via ``default_get``), otherwise fetches the field for + that precise record. + + :param str xmlid: xmlid of the record + :param str model: name of the model to fetch the binary from + :param int id: id of the record from which to fetch the binary + :param str field: binary field + :param bool unique: add a max-age for the cache control + :param str filename: choose a filename + :param str filename_field: if not create an filename with model-id-field + :param bool download: apply headers to download the file + :param str mimetype: mintype of the field (for headers) + :param str default_mimetype: default mintype if no mintype found + :param Environment env: by default use request.env + :returns: (status, headers, content) + """ + env = env or request.env + # get object and content + obj = None + if xmlid: + obj = env.ref(xmlid, False) + elif id and model in env.registry: + obj = env[model].browse(int(id)) + + # obj exists + if not obj or not obj.exists() or field not in obj: + return (404, [], None) + + # check read access + try: + last_update = obj['__last_update'] + except openerp.exceptions.AccessError: + return (403, [], None) + + status, headers, content = None, [], None + + # attachment by url check + module_resource_path = None + if model == 'ir.attachment' and obj.type == 'url' and obj.url: + url_match = re.match("^/(\w+)/(.+)$", obj.url) + if url_match: + module = url_match.group(1) + module_path = get_module_path(module) + module_resource_path = get_resource_path(module, url_match.group(2)) + if module_path and module_resource_path: + module_path = os.path.join(os.path.normpath(module_path), '') # join ensures the path ends with '/' + module_resource_path = os.path.normpath(module_resource_path) + if module_resource_path.startswith(module_path): + with open(module_resource_path, 'r') as f: + content = base64.b64encode(f.read()) + last_update = str(os.path.getmtime(module_resource_path)) + + if not module_resource_path: + module_resource_path = obj.url + + if not content: + status = 301 + content = module_resource_path + else: + content = obj[field] or '' + + # filename + if not filename: + if filename_field in obj: + filename = obj[filename_field] + elif module_resource_path: + filename = os.path.basename(module_resource_path) + else: + filename = "%s-%s-%s" % (obj._model._name, obj.id, field) + + # mimetype + if not mimetype: + if 'mimetype' in obj and obj.mimetype and obj.mimetype != 'application/octet-stream': + mimetype = obj.mimetype + elif filename: + mimetype = mimetypes.guess_type(filename)[0] + if not mimetype: + mimetype = default_mimetype + headers.append(('Content-Type', mimetype)) + + # cache + etag = hasattr(request, 'httprequest') and request.httprequest.headers.get('If-None-Match') + retag = hashlib.md5(last_update).hexdigest() + status = status or (304 if etag == retag else 200) + headers.append(('ETag', retag)) + headers.append(('Cache-Control', 'max-age=%s' % (STATIC_CACHE if unique else 0))) + + # content-disposition default name + if download: + headers.append(('Content-Disposition', self.content_disposition(filename))) + + return (status, headers, content) + + def convert_exception_to(to_type, with_message=False): """ Should only be called from an exception handler. Fetches the current exception data from sys.exc_info() and creates a new exception of type