downloadable.py 6.96 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved.
#                    Nicolas Delaby <nicolas@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################
29
from AccessControl import ClassSecurityInfo, Unauthorized
30
from Products.ERP5Type import Permissions
31
from Products.ERP5Type.Utils import fill_args_from_request
32
from Products.CMFCore.utils import getToolByName, _checkConditionalGET, _setCacheHeaders,\
33
    _ViewEmulator
Nicolas Delaby's avatar
Nicolas Delaby committed
34
import warnings
35
from zExceptions import Forbidden
36

37 38
_MARKER = []

39 40 41 42 43
class DownloadableMixin:
  security = ClassSecurityInfo()

  ### Content processing methods
  security.declareProtected(Permissions.View, 'index_html')
44
  @fill_args_from_request('display', 'quality', 'resolution', 'frame', 'pre_converted_only')
45
  def index_html(self, REQUEST, RESPONSE, format=_MARKER, inline=_MARKER, **kw):
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
    """
      We follow here the standard Zope API for files and images
      and extend it to support format conversion. The idea
      is that an image which ID is "something.jpg" should
      ne directly accessible through the URL
      /a/b/something.jpg. The same is true for a file and
      for any document type which primary purpose is to
      be used by a helper application rather than displayed
      as HTML in a web browser. Exceptions to this approach
      include Web Pages which are intended to be primarily rendered
      withing the layout of a Web Site or withing a standard ERP5 page.
      Please refer to the index_html of TextDocument.

      Should return appropriate format (calling convert
      if necessary) and set headers.

      format -- the format specied in the form of an extension
      string (ex. jpeg, html, text, txt, etc.)

      **kw -- can be various things - e.g. resolution

    """
    from Products.ERP5.Document.Document import VALID_TEXT_FORMAT_LIST,\
                                                        VALID_IMAGE_FORMAT_LIST
70 71 72 73 74
    if format is _MARKER and not kw:
      # conversion parameters is mandatory to download the converted content.
      # By default allways return view action.
      # for all WevDAV access return raw content.
      return self.view()
75 76
    if format is _MARKER:
      format = None
77 78 79 80

    web_cache_kw = kw.copy()
    if format:
      web_cache_kw['format'] = format
81 82 83 84 85 86 87
    view = _ViewEmulator().__of__(self)
    # If we have a conditional get, set status 304 and return
    # no content
    if _checkConditionalGET(view, web_cache_kw):
      return ''
    # call caching policy manager.
    _setCacheHeaders(view, web_cache_kw)
88

89 90 91
    if not self.checkConversionFormatPermission(format, **kw):
      raise Forbidden('You are not allowed to get this document in this ' \
                      'format')
92
    mime, data = self.convert(format, **kw)
93
    output_format = None
94
    if not format or format == 'base-data':
95
      # Guess the format from original mimetype
96 97 98 99 100 101 102 103 104 105 106
      if mime:
        mimetypes_registry = getToolByName(self.getPortalObject(),
                                                            'mimetypes_registry')
        mimetype_object_list = mimetypes_registry.lookup(mime)
        for mimetype_object in mimetype_object_list:
          if mimetype_object.extensions:
            output_format = mimetype_object.extensions[0]
            break
          elif mimetype_object.globs:
            output_format = mimetype_object.globs.strip('*.')
            break
107
    if output_format is None:
108
      output_format = format
109 110

    RESPONSE.setHeader('Content-Length', len(data))
111
    if output_format in VALID_TEXT_FORMAT_LIST:
112 113 114
      RESPONSE.setHeader('Content-Type', '%s; charset=utf-8' % mime)
    else:
      RESPONSE.setHeader('Content-Type', mime)
115 116 117 118 119
    if inline is _MARKER:
      # by default, use inline for text and image formats
      inline = output_format in (VALID_TEXT_FORMAT_LIST + VALID_IMAGE_FORMAT_LIST)
    if not inline:
      # need to return it as attachment
120 121 122 123
      if format == 'base-data':
        filename = self.getStandardFilename(format=output_format)
      else:
        filename = self.getStandardFilename(format=format)
124
      RESPONSE.setHeader('Cache-Control', 'Private') # workaround for Internet Explorer's bug
125 126
      # workaround for IE's bug to download files over SSL
      RESPONSE.setHeader('Pragma', '')
127 128 129
      RESPONSE.setHeader('Content-Disposition',
                         'attachment; filename="%s"' % filename)
      RESPONSE.setHeader('Accept-Ranges', 'bytes')
130 131 132
    return str(data)

  security.declareProtected(Permissions.AccessContentsInformation,
Nicolas Delaby's avatar
Nicolas Delaby committed
133 134
                            'getStandardFilename')
  def getStandardFilename(self, format=None):
135 136 137
    """Returns the document coordinates as a standard file name. This
    method is the reverse of getPropertyDictFromFileName.
    """
138 139
    try:
      method = self._getTypeBasedMethod('getStandardFilename',
Nicolas Delaby's avatar
Nicolas Delaby committed
140
                             fallback_script_id='Document_getStandardFilename')
141
    except AttributeError:
Nicolas Delaby's avatar
Nicolas Delaby committed
142 143
      # backward compatibility
      method = self._getTypeBasedMethod('getStandardFileName',
144
                             fallback_script_id='Document_getStandardFileName')
145 146 147 148 149
    try:
      return method(format=format)
    except TypeError:
      # Old versions of this script did not support 'format' parameter
      return method()
150

Nicolas Delaby's avatar
Nicolas Delaby committed
151 152 153 154 155 156 157 158 159
  # backward compatibility
  security.declareProtected(Permissions.AccessContentsInformation,
                            'getStandardFileName')
  def getStandardFileName(self, format=None):
    """(deprecated) use getStandardFilename() instead."""
    warnings.warn('getStandardFileName() is deprecated. '
                  'use getStandardFilename() instead.')
    return self.getStandardFilename(format=format)

160 161 162 163 164 165
  def manage_FTPget(self):
    """Return body for ftp. and WebDAV
    """
    # pass format argument to force downloading raw content
    REQUEST = self.REQUEST
    return self.index_html(REQUEST, REQUEST.RESPONSE, format=None)