zope.app.file


Namezope.app.file JSON
Version 5.0 PyPI version JSON
download
home_pagehttps://github.com/zopefoundation/zope.app.file
SummaryFile and Image -- Zope 3 Content Components
upload_time2024-12-04 07:41:42
maintainerNone
docs_urlNone
authorZope Foundation and Contributors
requires_python>=3.8
licenseZPL 2.1
keywords zope3 file image content
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            This package provides two basic Zope 3 content components, File and Image, and
their ZMI-compliant browser views.


.. contents::

File objects
============

Adding Files
------------

You can add File objects from the common tasks menu in the ZMI.

  >>> result = http(b"""
  ... GET /@@contents.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... """)
  >>> "http://localhost/@@+/action.html?type_name=zope.app.file.File" in str(result)
  True

Let's follow that link.

  >>> print(http(b"""
  ... GET /@@+/action.html?type_name=zope.app.file.File HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... """, handle_errors=False))
  HTTP/1.1 303 See Other
  Content-Length: ...
  Location: http://localhost/+/zope.app.file.File=
  <BLANKLINE>

The file add form lets you specify the content type, the object name, and
optionally upload the contents of the file.

  >>> print(http(b"""
  ... GET /+/zope.app.file.File= HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... """))
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  <BLANKLINE>
  ...
      <title>Z3: +</title>
  ...
  ...
    <form action="http://localhost/+/zope.app.file.File%3D"
          method="post" enctype="multipart/form-data">
      <h3>Add a File</h3>
      ...<input class="textType" id="field.contentType"
                name="field.contentType" size="20" type="text" value="" />...
      ...<input class="fileType" id="field.data" name="field.data" size="20"
                type="file" />...
        <div class="controls"><hr />
          <input type="submit" value="Refresh" />
          <input type="submit" value="Add"
                 name="UPDATE_SUBMIT" />
          &nbsp;&nbsp;<b>Object Name</b>&nbsp;&nbsp;
          <input type="text" name="add_input_name" value="" />
        </div>
  ...
    </form>
  ...

Binary Files
------------

Let us upload a binary file.

  >>> hello_txt_gz = (
  ...     b'\x1f\x8b\x08\x08\xcb\x48\xea\x42\x00\x03\x68\x65\x6c\x6c\x6f\x2e'
  ...     b'\x74\x78\x74\x00\xcb\x48\xcd\xc9\xc9\xe7\x02\x00\x20\x30\x3a\x36'
  ...     b'\x06\x00\x00\x00')

  >>> content_type, content = encodeMultipartFormdata([
  ...     ('field.contentType', 'application/octet-stream'),
  ...     ('UPDATE_SUBMIT', 'Add'),
  ...     ('add_input_name', '')],
  ...    [('field.data', 'hello.txt.gz', hello_txt_gz, 'application/x-gzip')])
  >>> print(http(b"""
  ... POST /+/zope.app.file.File%%3D HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: %b
  ...
  ... %b
  ... """ % (content_type, content)))
  HTTP/1.1 303 See Other
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  Location: http://localhost/@@contents.html
  <BLANKLINE>
  ...

Since we did not specify the object name in the form, Zope 3 will use the
filename.

  >>> response = http(b"""
  ... GET /hello.txt.gz HTTP/1.1
  ... """)
  >>> print(response)
  HTTP/1.1 200 Ok
  Content-Length: 36
  Content-Type: application/octet-stream
  <BLANKLINE>
  ...

Let's make sure the (binary) content of the file is correct

  >>> response.getBody() == hello_txt_gz
  True

Also, lets test a (bad) filename with full path that generates MS Internet Explorer,
Zope should process it successfully and get the actual filename. Let's upload the
same file with bad filename.

  >>> test_gz = (
  ...   b'\x1f\x8b\x08\x08\xcb\x48\xea\x42\x00\x03\x68\x65\x6c\x6c\x6f\x2e'
  ...   b'\x74\x78\x74\x00\xcb\x48\xcd\xc9\xc9\xe7\x02\x00\x20\x30\x3a\x36'
  ...   b'\x06\x00\x00\x00')
  >>> content_type, content = encodeMultipartFormdata([
  ...     ('field.contentType', 'application/octet-stream'),
  ...     ('UPDATE_SUBMIT', 'Add'),
  ...     ('add_input_name', '')],
  ...    [('field.data', 'c:\\windows\\test.gz', test_gz, 'application/x-gzip')])
  >>> print(http(b"""
  ... POST /+/zope.app.file.File%%3D HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: %b
  ...
  ... %b
  ... """ % (content_type, content)))
  HTTP/1.1 303 See Other
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  Location: http://localhost/@@contents.html
  <BLANKLINE>
  ...

The file should be saved as "test.gz", let's check it name and contents.

  >>> response = http(b"""
  ... GET /test.gz HTTP/1.1
  ... """)
  >>> print(response)
  HTTP/1.1 200 Ok
  Content-Length: 36
  Content-Type: application/octet-stream
  <BLANKLINE>
  ...


  >>> response.getBody() == test_gz
  True

Text Files
----------

Let us now create a text file.

  >>> content_type, content = encodeMultipartFormdata([
  ...     ('field.contentType', 'text/plain'),
  ...     ('UPDATE_SUBMIT', 'Add'),
  ...     ('add_input_name', 'sample.txt')],
  ...    [('field.data', '', b'', 'application/octet-stream')])
  >>> print(http(b"""
  ... POST /+/zope.app.file.File%%3D HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: %b
  ...
  ... %b
  ... """ % (content_type, content)))
  HTTP/1.1 303 See Other
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  Location: http://localhost/@@contents.html
  <BLANKLINE>
  ...

The file is initially empty, since we did not upload anything.

  >>> print(http(b"""
  ... GET /sample.txt HTTP/1.1
  ... """))
  HTTP/1.1 200 Ok
  Content-Length: 0
  Content-Type: text/plain
  Last-Modified: ...
  <BLANKLINE>

Since it is a text file, we can edit it directly in a web form.

  >>> print(http(b"""
  ... GET /sample.txt/edit.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... """, handle_errors=False))
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  <BLANKLINE>
  ...
      <title>Z3: sample.txt</title>
  ...
      <form action="http://localhost/sample.txt/edit.html"
            method="post" enctype="multipart/form-data">
        <div>
          <h3>Change a file</h3>
  ...<input class="textType" id="field.contentType" name="field.contentType"
            size="20" type="text" value="text/plain"  />...
  ...<textarea cols="60" id="field.data" name="field.data" rows="15" ></textarea>...
  ...
          <div class="controls">
            <input type="submit" value="Refresh" />
            <input type="submit" name="UPDATE_SUBMIT"
                   value="Change" />
          </div>
  ...
      </form>
  ...

Files of type text/plain without any charset information can contain UTF-8 text.
So you can use ASCII text.

  >>> content_type, content = encodeMultipartFormdata([
  ...     ('field.contentType', 'text/plain'),
  ...     ('field.data', 'This is a sample text file.\n\nIt can contain US-ASCII characters.'),
  ...     ('UPDATE_SUBMIT', 'Change')])
  >>> print(http(b"""
  ... POST /sample.txt/edit.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: %b
  ...
  ... %b
  ... """ % (content_type, content), handle_errors=False))
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  <BLANKLINE>
  ...
      <title>Z3: sample.txt</title>
  ...
      <form action="http://localhost/sample.txt/edit.html"
            method="post" enctype="multipart/form-data">
        <div>
          <h3>Change a file</h3>
  <BLANKLINE>
          <p>Updated on ...</p>
  <BLANKLINE>
        <div class="row">
  ...<input class="textType" id="field.contentType" name="field.contentType"
            size="20" type="text" value="text/plain"  />...
        <div class="row">
  ...<textarea cols="60" id="field.data" name="field.data" rows="15"
  >This is a sample text file.
  <BLANKLINE>
  It can contain US-ASCII characters.</textarea></div>
  ...
          <div class="controls">
            <input type="submit" value="Refresh" />
            <input type="submit" name="UPDATE_SUBMIT"
                   value="Change" />
          </div>
  ...
      </form>
  ...

Here's the file

  >>> print(http(b"""
  ... GET /sample.txt HTTP/1.1
  ... """))
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/plain
  Last-Modified: ...
  <BLANKLINE>
  This is a sample text file.
  <BLANKLINE>
  It can contain US-ASCII characters.


Non-ASCII Text Files
--------------------

We can also use non-ASCII charactors in text file.

  >>> content_type, content = encodeMultipartFormdata([
  ...     ('field.contentType', 'text/plain'),
  ...     ('field.data', 'This is a sample text file.\n\nIt can contain non-ASCII(UTF-8) characters, e.g. \u263B (U+263B BLACK SMILING FACE).'),
  ...     ('UPDATE_SUBMIT', 'Change')])
  >>> print(http(b"""
  ... POST /sample.txt/edit.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: %b
  ...
  ... %b
  ... """ % (content_type, content)))
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  <BLANKLINE>
  ...
      <title>Z3: sample.txt</title>
  ...
      <form action="http://localhost/sample.txt/edit.html"
            method="post" enctype="multipart/form-data">
        <div>
          <h3>Change a file</h3>
  <BLANKLINE>
          <p>Updated on ...</p>
  <BLANKLINE>
        <div class="row">
  ...<input class="textType" id="field.contentType" name="field.contentType"
            size="20" type="text" value="text/plain"  />...
        <div class="row">
  ...<textarea cols="60" id="field.data" name="field.data" rows="15"
  >This is a sample text file.
  <BLANKLINE>
  It can contain non-ASCII(UTF-8) characters, e.g. ... (U+263B BLACK SMILING FACE).</textarea></div>
  ...
          <div class="controls">
            <input type="submit" value="Refresh" />
            <input type="submit" name="UPDATE_SUBMIT"
                   value="Change" />
          </div>
  ...
      </form>
  ...

Here's the file

  >>> response = http(b"""
  ... GET /sample.txt HTTP/1.1
  ... """)
  >>> print(response)
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/plain
  Last-Modified: ...
  <BLANKLINE>
  This is a sample text file.
  <BLANKLINE>
  It can contain non-ASCII(UTF-8) characters, e.g. ... (U+263B BLACK SMILING FACE).

  >>> u'\u263B' in response.getBody().decode('UTF-8')
  True

And you can explicitly specify the charset. Note that the browser form is always UTF-8.

  >>> content_type, content = encodeMultipartFormdata([
  ...     ('field.contentType', 'text/plain; charset=ISO-8859-1'),
  ...     ('field.data', 'This is a sample text file.\n\nIt now contains Latin-1 characters, e.g. \xa7 (U+00A7 SECTION SIGN).'),
  ...     ('UPDATE_SUBMIT', 'Change')])
  >>> print(http(b"""
  ... POST /sample.txt/edit.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: %b
  ...
  ... %b
  ... """ % (content_type, content)))
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  <BLANKLINE>
  ...
      <title>Z3: sample.txt</title>
  ...
      <form action="http://localhost/sample.txt/edit.html"
            method="post" enctype="multipart/form-data">
        <div>
          <h3>Change a file</h3>
  <BLANKLINE>
          <p>Updated on ...</p>
  <BLANKLINE>
        <div class="row">
  ...<input class="textType" id="field.contentType" name="field.contentType"
            size="20" type="text" value="text/plain; charset=ISO-8859-1"  />...
        <div class="row">
  ...<textarea cols="60" id="field.data" name="field.data" rows="15"
  >This is a sample text file.
  <BLANKLINE>
  It now contains Latin-1 characters, e.g. ... (U+00A7 SECTION SIGN).</textarea></div>
  ...
          <div class="controls">
            <input type="submit" value="Refresh" />
            <input type="submit" name="UPDATE_SUBMIT"
                   value="Change" />
          </div>
  ...
      </form>
  ...

Here's the file

  >>> response = http(b"""
  ... GET /sample.txt HTTP/1.1
  ... """)
  >>> print(response)
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/plain; charset=ISO-8859-1
  Last-Modified: ...
  <BLANKLINE>
  This is a sample text file.
  <BLANKLINE>
  It now contains Latin-1 characters, e.g. ... (U+00A7 SECTION SIGN).

Body is actually encoded in ISO-8859-1, and not UTF-8

  >>> response.getBody().splitlines()[-1].decode('latin-1')
  'It now contains Latin-1 characters, e.g. \xa7 (U+00A7 SECTION SIGN).'

The user is not allowed to specify a character set that cannot represent all
the characters.

  >>> content_type, content = encodeMultipartFormdata([
  ...     ('field.contentType', 'text/plain; charset=US-ASCII'),
  ...     ('field.data', 'This is a slightly changed sample text file.\n\nIt now contains Latin-1 characters, e.g. \xa7 (U+00A7 SECTION SIGN).'),
  ...     ('UPDATE_SUBMIT', 'Change')])
  >>> print(http(b"""
  ... POST /sample.txt/edit.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: %b
  ...
  ... %b
  ... """ % (content_type, content), handle_errors=False))
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  <BLANKLINE>
  ...
      <title>Z3: sample.txt</title>
  ...
      <form action="http://localhost/sample.txt/edit.html"
            method="post" enctype="multipart/form-data">
        <div>
          <h3>Change a file</h3>
  <BLANKLINE>
          <p>The character set you specified (US-ASCII) cannot encode all characters in text.</p>
  <BLANKLINE>
        <div class="row">
  ...<input class="textType" id="field.contentType" name="field.contentType" size="20" type="text" value="text/plain; charset=US-ASCII"  />...
        <div class="row">
  ...<textarea cols="60" id="field.data" name="field.data" rows="15" >This is a slightly changed sample text file.
  <BLANKLINE>
  It now contains Latin-1 characters, e.g. ... (U+00A7 SECTION SIGN).</textarea></div>
  ...
          <div class="controls">
            <input type="submit" value="Refresh" />
            <input type="submit" name="UPDATE_SUBMIT"
                   value="Change" />
          </div>
  ...
      </form>
  ...

Likewise, the user is not allowed to specify a character set that is not supported by Python.

  >>> content_type, content = encodeMultipartFormdata([
  ...     ('field.contentType', 'text/plain; charset=I-INVENT-MY-OWN'),
  ...     ('field.data', 'This is a slightly changed sample text file.\n\nIt now contains just ASCII characters.'),
  ...     ('UPDATE_SUBMIT', 'Change')])
  >>> print(http(b"""
  ... POST /sample.txt/edit.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: %b
  ...
  ... %b
  ... """ % (content_type, content), handle_errors=False))
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  <BLANKLINE>
  ...
      <title>Z3: sample.txt</title>
  ...
      <form action="http://localhost/sample.txt/edit.html"
            method="post" enctype="multipart/form-data">
        <div>
          <h3>Change a file</h3>
  <BLANKLINE>
          <p>The character set you specified (I-INVENT-MY-OWN) is not supported.</p>
  <BLANKLINE>
        <div class="row">
  ...<input class="textType" id="field.contentType" name="field.contentType" size="20" type="text" value="text/plain; charset=I-INVENT-MY-OWN"  />...
        <div class="row">
  ...<textarea cols="60" id="field.data" name="field.data" rows="15" >This is a slightly changed sample text file.
  <BLANKLINE>
  It now contains just ASCII characters.</textarea></div>
  ...
          <div class="controls">
            <input type="submit" value="Refresh" />
            <input type="submit" name="UPDATE_SUBMIT"
                   value="Change" />
          </div>
  ...
      </form>
  ...

If you trick Zope and upload a file with a content type that does not
match the file contents, you will not be able to access the edit view:

  >>> print(http(b"""
  ... GET /hello.txt.gz/@@edit.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... """, handle_errors=True))
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  <BLANKLINE>
  ...
     <li>The character set specified in the content type (UTF-8) does not match file content.</li>
  ...

Non-ASCII Filenames
-------------------

Filenames are not restricted to ASCII.

  >>> björn_txt_gz = (
  ...     b'\x1f\x8b\x08\x08\xcb\x48\xea\x42\x00\x03\x68\x65\x6c\x6c\x6f\x2e'
  ...     b'\x74\x78\x74\x00\xcb\x48\xcd\xc9\xc9\xe7\x02\x00\x20\x30\x3a\x36'
  ...     b'\x06\x00\x00\x00')
  >>> content_type, content = encodeMultipartFormdata([
  ...     ('field.contentType', 'application/octet-stream'),
  ...     ('UPDATE_SUBMIT', 'Add'),
  ...     ('add_input_name', '')],
  ...    [('field.data', 'björn.txt.gz', björn_txt_gz, 'application/x-gzip')])
  >>> print(http(b"""
  ... POST /+/zope.app.file.File%%3D HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: %b
  ...
  ... %b
  ... """ % (content_type, content)))
  HTTP/1.1 303 See Other
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  Location: http://localhost/@@contents.html
  <BLANKLINE>
  ...

Since we did not specify the object name in the form, Zope 3 will use the
filename.

  >>> response = http(b"""
  ... GET /bj%C3%B6rn.txt.gz HTTP/1.1
  ... """)
  >>> print(response)
  HTTP/1.1 200 Ok
  Content-Length: 36
  Content-Type: application/octet-stream
  Last-Modified: ...
  <BLANKLINE>
  ...


Special URL handling for DTML pages
===================================

When an HTML File page containing a head tag is visited, without a
trailing slash, the base href isn't set.  When visited with a slash,
it is:

  >>> content_type, content = encodeMultipartFormdata([
  ...     ('field.contentType', 'text/html'),
  ...     ('UPDATE_SUBMIT', 'Add'),
  ...     ('add_input_name', 'file.html')],
  ...    [('field.data', '', b'', 'application/octet-stream')])
  >>> print(http(b"""
  ... POST /+/zope.app.file.File%%3D HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: %b
  ... Referer: http://localhost:8081/+/zope.app.file.File=
  ...
  ... %b
  ... """ % (content_type, content)))
  HTTP/1.1 303 See Other
  ...

  >>> content_type, content = encodeMultipartFormdata([
  ...     ('field.contentType', 'text/html'),
  ...     ('field.data', b'<html>\n<head></head>\n<body>\n<a href="eek.html">Eek</a>\n</body>\n</html>'),
  ...     ('UPDATE_SUBMIT', 'Change')])
  >>> print(http(b"""
  ... POST /file.html/edit.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: %b
  ... Referer: http://localhost:8081/file.html/edit.html
  ...
  ... %b
  ... """ % (content_type, content)))
  HTTP/1.1 200 Ok
  ...

  >>> print(http(b"""
  ... GET /file.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... """))
  HTTP/1.1 200 Ok
  ...
  <html>
  <head></head>
  <body>
  <a href="eek.html">Eek</a>
  </body>
  </html>


  >>> print(http(b"""
  ... GET /file.html/ HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... """))
  HTTP/1.1 200 Ok
  ...
  <html>
  <head>
  <base href="http://localhost/file.html" />
  </head>
  <body>
  <a href="eek.html">Eek</a>
  </body>
  </html>


Changes
=======

5.0 (2024-12-04)
----------------

- Add support for Python 3.8, 3.9, 3.10, 3.11, 3.12, 3.13.

- Drop support for Python 2.7, 3.4, 3.5, 3.6.

- Fix tests to support ``multipart >= 1.1``.

4.0.0 (2017-05-16)
------------------

- Add support for Python 3.4, 3.5, 3.6 and PyPy.

- Remove test dependency on ``zope.app.testing`` and ``zope.app.zcmlfiles``,
  among others.

- Change dependency from ZODB3 to persistent and add missing
  dependencies on ``zope.app.content``.


3.6.1 (2010-09-17)
------------------

- Removed ZPKG slugs and ZCML ones.

- Moved a functional test here from `zope.app.http`.

- Using Python's ``doctest`` instead of deprecated ``zope.testing.doctest``.


3.6.0 (2010-08-19)
------------------

- Updated ``ftesting.zcml`` to use the new permission names exported by
  ``zope.dublincore`` 3.7.

- Using python's `doctest` instead of deprecated `zope.testing.doctest`.


3.5.1 (2010-01-08)
------------------

- Fix ftesting.zcml due to zope.securitypolicy update.

- Added missing dependency on transaction.

- Import content-type parser from zope.contenttype, reducing zope.publisher to
  a test dependency.

- Fix tests using a newer zope.publisher that requires zope.login.

3.5.0 (2009-01-31)
------------------

- Replace ``zope.app.folder`` use by ``zope.site``. Add missing
  dependency in ``setup.py``.

3.4.6 (2009-01-27)
------------------

- Remove zope.app.zapi dependency again. Previous release
  was wrong. We removed the zope.app.zapi uses before, so
  we don't need it anymore.

3.4.5 (2009-01-27)
------------------

- added missing dependency: zope.app.zapi

3.4.4 (2008-09-05)
------------------

- Bug: Get actual filename instead of full filesystem path when adding
  file/image using Internet Explorer.

3.4.3 (2008-06-18)
------------------

- Using IDCTimes interface instead of IZopeDublinCore to determine the
  modification date of a file.

3.4.2 (2007-11-09)
------------------

- Include information about which attributes changed in the
  ``IObjectModifiedEvent`` after upload.

  This fixes https://bugs.launchpad.net/zope3/+bug/98483.

3.4.1 (2007-10-31)
------------------

- Resolve ``ZopeSecurityPolicy`` deprecation warning.


3.4.0 (2007-10-24)
------------------

- Initial release independent of the main Zope tree.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/zopefoundation/zope.app.file",
    "name": "zope.app.file",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "zope3 file image content",
    "author": "Zope Foundation and Contributors",
    "author_email": "zope-dev@zope.org",
    "download_url": "https://files.pythonhosted.org/packages/dc/d3/42b618d8fa9fc7ebcff58394b1f92ced3386fe4b617cb5271ba0a0ef61ed/zope_app_file-5.0.tar.gz",
    "platform": null,
    "description": "This package provides two basic Zope 3 content components, File and Image, and\ntheir ZMI-compliant browser views.\n\n\n.. contents::\n\nFile objects\n============\n\nAdding Files\n------------\n\nYou can add File objects from the common tasks menu in the ZMI.\n\n  >>> result = http(b\"\"\"\n  ... GET /@@contents.html HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... \"\"\")\n  >>> \"http://localhost/@@+/action.html?type_name=zope.app.file.File\" in str(result)\n  True\n\nLet's follow that link.\n\n  >>> print(http(b\"\"\"\n  ... GET /@@+/action.html?type_name=zope.app.file.File HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... \"\"\", handle_errors=False))\n  HTTP/1.1 303 See Other\n  Content-Length: ...\n  Location: http://localhost/+/zope.app.file.File=\n  <BLANKLINE>\n\nThe file add form lets you specify the content type, the object name, and\noptionally upload the contents of the file.\n\n  >>> print(http(b\"\"\"\n  ... GET /+/zope.app.file.File= HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... \"\"\"))\n  HTTP/1.1 200 Ok\n  Content-Length: ...\n  Content-Type: text/html;charset=utf-8\n  <BLANKLINE>\n  ...\n      <title>Z3: +</title>\n  ...\n  ...\n    <form action=\"http://localhost/+/zope.app.file.File%3D\"\n          method=\"post\" enctype=\"multipart/form-data\">\n      <h3>Add a File</h3>\n      ...<input class=\"textType\" id=\"field.contentType\"\n                name=\"field.contentType\" size=\"20\" type=\"text\" value=\"\" />...\n      ...<input class=\"fileType\" id=\"field.data\" name=\"field.data\" size=\"20\"\n                type=\"file\" />...\n        <div class=\"controls\"><hr />\n          <input type=\"submit\" value=\"Refresh\" />\n          <input type=\"submit\" value=\"Add\"\n                 name=\"UPDATE_SUBMIT\" />\n          &nbsp;&nbsp;<b>Object Name</b>&nbsp;&nbsp;\n          <input type=\"text\" name=\"add_input_name\" value=\"\" />\n        </div>\n  ...\n    </form>\n  ...\n\nBinary Files\n------------\n\nLet us upload a binary file.\n\n  >>> hello_txt_gz = (\n  ...     b'\\x1f\\x8b\\x08\\x08\\xcb\\x48\\xea\\x42\\x00\\x03\\x68\\x65\\x6c\\x6c\\x6f\\x2e'\n  ...     b'\\x74\\x78\\x74\\x00\\xcb\\x48\\xcd\\xc9\\xc9\\xe7\\x02\\x00\\x20\\x30\\x3a\\x36'\n  ...     b'\\x06\\x00\\x00\\x00')\n\n  >>> content_type, content = encodeMultipartFormdata([\n  ...     ('field.contentType', 'application/octet-stream'),\n  ...     ('UPDATE_SUBMIT', 'Add'),\n  ...     ('add_input_name', '')],\n  ...    [('field.data', 'hello.txt.gz', hello_txt_gz, 'application/x-gzip')])\n  >>> print(http(b\"\"\"\n  ... POST /+/zope.app.file.File%%3D HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... Content-Type: %b\n  ...\n  ... %b\n  ... \"\"\" % (content_type, content)))\n  HTTP/1.1 303 See Other\n  Content-Length: ...\n  Content-Type: text/html;charset=utf-8\n  Location: http://localhost/@@contents.html\n  <BLANKLINE>\n  ...\n\nSince we did not specify the object name in the form, Zope 3 will use the\nfilename.\n\n  >>> response = http(b\"\"\"\n  ... GET /hello.txt.gz HTTP/1.1\n  ... \"\"\")\n  >>> print(response)\n  HTTP/1.1 200 Ok\n  Content-Length: 36\n  Content-Type: application/octet-stream\n  <BLANKLINE>\n  ...\n\nLet's make sure the (binary) content of the file is correct\n\n  >>> response.getBody() == hello_txt_gz\n  True\n\nAlso, lets test a (bad) filename with full path that generates MS Internet Explorer,\nZope should process it successfully and get the actual filename. Let's upload the\nsame file with bad filename.\n\n  >>> test_gz = (\n  ...   b'\\x1f\\x8b\\x08\\x08\\xcb\\x48\\xea\\x42\\x00\\x03\\x68\\x65\\x6c\\x6c\\x6f\\x2e'\n  ...   b'\\x74\\x78\\x74\\x00\\xcb\\x48\\xcd\\xc9\\xc9\\xe7\\x02\\x00\\x20\\x30\\x3a\\x36'\n  ...   b'\\x06\\x00\\x00\\x00')\n  >>> content_type, content = encodeMultipartFormdata([\n  ...     ('field.contentType', 'application/octet-stream'),\n  ...     ('UPDATE_SUBMIT', 'Add'),\n  ...     ('add_input_name', '')],\n  ...    [('field.data', 'c:\\\\windows\\\\test.gz', test_gz, 'application/x-gzip')])\n  >>> print(http(b\"\"\"\n  ... POST /+/zope.app.file.File%%3D HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... Content-Type: %b\n  ...\n  ... %b\n  ... \"\"\" % (content_type, content)))\n  HTTP/1.1 303 See Other\n  Content-Length: ...\n  Content-Type: text/html;charset=utf-8\n  Location: http://localhost/@@contents.html\n  <BLANKLINE>\n  ...\n\nThe file should be saved as \"test.gz\", let's check it name and contents.\n\n  >>> response = http(b\"\"\"\n  ... GET /test.gz HTTP/1.1\n  ... \"\"\")\n  >>> print(response)\n  HTTP/1.1 200 Ok\n  Content-Length: 36\n  Content-Type: application/octet-stream\n  <BLANKLINE>\n  ...\n\n\n  >>> response.getBody() == test_gz\n  True\n\nText Files\n----------\n\nLet us now create a text file.\n\n  >>> content_type, content = encodeMultipartFormdata([\n  ...     ('field.contentType', 'text/plain'),\n  ...     ('UPDATE_SUBMIT', 'Add'),\n  ...     ('add_input_name', 'sample.txt')],\n  ...    [('field.data', '', b'', 'application/octet-stream')])\n  >>> print(http(b\"\"\"\n  ... POST /+/zope.app.file.File%%3D HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... Content-Type: %b\n  ...\n  ... %b\n  ... \"\"\" % (content_type, content)))\n  HTTP/1.1 303 See Other\n  Content-Length: ...\n  Content-Type: text/html;charset=utf-8\n  Location: http://localhost/@@contents.html\n  <BLANKLINE>\n  ...\n\nThe file is initially empty, since we did not upload anything.\n\n  >>> print(http(b\"\"\"\n  ... GET /sample.txt HTTP/1.1\n  ... \"\"\"))\n  HTTP/1.1 200 Ok\n  Content-Length: 0\n  Content-Type: text/plain\n  Last-Modified: ...\n  <BLANKLINE>\n\nSince it is a text file, we can edit it directly in a web form.\n\n  >>> print(http(b\"\"\"\n  ... GET /sample.txt/edit.html HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... \"\"\", handle_errors=False))\n  HTTP/1.1 200 Ok\n  Content-Length: ...\n  Content-Type: text/html;charset=utf-8\n  <BLANKLINE>\n  ...\n      <title>Z3: sample.txt</title>\n  ...\n      <form action=\"http://localhost/sample.txt/edit.html\"\n            method=\"post\" enctype=\"multipart/form-data\">\n        <div>\n          <h3>Change a file</h3>\n  ...<input class=\"textType\" id=\"field.contentType\" name=\"field.contentType\"\n            size=\"20\" type=\"text\" value=\"text/plain\"  />...\n  ...<textarea cols=\"60\" id=\"field.data\" name=\"field.data\" rows=\"15\" ></textarea>...\n  ...\n          <div class=\"controls\">\n            <input type=\"submit\" value=\"Refresh\" />\n            <input type=\"submit\" name=\"UPDATE_SUBMIT\"\n                   value=\"Change\" />\n          </div>\n  ...\n      </form>\n  ...\n\nFiles of type text/plain without any charset information can contain UTF-8 text.\nSo you can use ASCII text.\n\n  >>> content_type, content = encodeMultipartFormdata([\n  ...     ('field.contentType', 'text/plain'),\n  ...     ('field.data', 'This is a sample text file.\\n\\nIt can contain US-ASCII characters.'),\n  ...     ('UPDATE_SUBMIT', 'Change')])\n  >>> print(http(b\"\"\"\n  ... POST /sample.txt/edit.html HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... Content-Type: %b\n  ...\n  ... %b\n  ... \"\"\" % (content_type, content), handle_errors=False))\n  HTTP/1.1 200 Ok\n  Content-Length: ...\n  Content-Type: text/html;charset=utf-8\n  <BLANKLINE>\n  ...\n      <title>Z3: sample.txt</title>\n  ...\n      <form action=\"http://localhost/sample.txt/edit.html\"\n            method=\"post\" enctype=\"multipart/form-data\">\n        <div>\n          <h3>Change a file</h3>\n  <BLANKLINE>\n          <p>Updated on ...</p>\n  <BLANKLINE>\n        <div class=\"row\">\n  ...<input class=\"textType\" id=\"field.contentType\" name=\"field.contentType\"\n            size=\"20\" type=\"text\" value=\"text/plain\"  />...\n        <div class=\"row\">\n  ...<textarea cols=\"60\" id=\"field.data\" name=\"field.data\" rows=\"15\"\n  >This is a sample text file.\n  <BLANKLINE>\n  It can contain US-ASCII characters.</textarea></div>\n  ...\n          <div class=\"controls\">\n            <input type=\"submit\" value=\"Refresh\" />\n            <input type=\"submit\" name=\"UPDATE_SUBMIT\"\n                   value=\"Change\" />\n          </div>\n  ...\n      </form>\n  ...\n\nHere's the file\n\n  >>> print(http(b\"\"\"\n  ... GET /sample.txt HTTP/1.1\n  ... \"\"\"))\n  HTTP/1.1 200 Ok\n  Content-Length: ...\n  Content-Type: text/plain\n  Last-Modified: ...\n  <BLANKLINE>\n  This is a sample text file.\n  <BLANKLINE>\n  It can contain US-ASCII characters.\n\n\nNon-ASCII Text Files\n--------------------\n\nWe can also use non-ASCII charactors in text file.\n\n  >>> content_type, content = encodeMultipartFormdata([\n  ...     ('field.contentType', 'text/plain'),\n  ...     ('field.data', 'This is a sample text file.\\n\\nIt can contain non-ASCII(UTF-8) characters, e.g. \\u263B (U+263B BLACK SMILING FACE).'),\n  ...     ('UPDATE_SUBMIT', 'Change')])\n  >>> print(http(b\"\"\"\n  ... POST /sample.txt/edit.html HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... Content-Type: %b\n  ...\n  ... %b\n  ... \"\"\" % (content_type, content)))\n  HTTP/1.1 200 Ok\n  Content-Length: ...\n  Content-Type: text/html;charset=utf-8\n  <BLANKLINE>\n  ...\n      <title>Z3: sample.txt</title>\n  ...\n      <form action=\"http://localhost/sample.txt/edit.html\"\n            method=\"post\" enctype=\"multipart/form-data\">\n        <div>\n          <h3>Change a file</h3>\n  <BLANKLINE>\n          <p>Updated on ...</p>\n  <BLANKLINE>\n        <div class=\"row\">\n  ...<input class=\"textType\" id=\"field.contentType\" name=\"field.contentType\"\n            size=\"20\" type=\"text\" value=\"text/plain\"  />...\n        <div class=\"row\">\n  ...<textarea cols=\"60\" id=\"field.data\" name=\"field.data\" rows=\"15\"\n  >This is a sample text file.\n  <BLANKLINE>\n  It can contain non-ASCII(UTF-8) characters, e.g. ... (U+263B BLACK SMILING FACE).</textarea></div>\n  ...\n          <div class=\"controls\">\n            <input type=\"submit\" value=\"Refresh\" />\n            <input type=\"submit\" name=\"UPDATE_SUBMIT\"\n                   value=\"Change\" />\n          </div>\n  ...\n      </form>\n  ...\n\nHere's the file\n\n  >>> response = http(b\"\"\"\n  ... GET /sample.txt HTTP/1.1\n  ... \"\"\")\n  >>> print(response)\n  HTTP/1.1 200 Ok\n  Content-Length: ...\n  Content-Type: text/plain\n  Last-Modified: ...\n  <BLANKLINE>\n  This is a sample text file.\n  <BLANKLINE>\n  It can contain non-ASCII(UTF-8) characters, e.g. ... (U+263B BLACK SMILING FACE).\n\n  >>> u'\\u263B' in response.getBody().decode('UTF-8')\n  True\n\nAnd you can explicitly specify the charset. Note that the browser form is always UTF-8.\n\n  >>> content_type, content = encodeMultipartFormdata([\n  ...     ('field.contentType', 'text/plain; charset=ISO-8859-1'),\n  ...     ('field.data', 'This is a sample text file.\\n\\nIt now contains Latin-1 characters, e.g. \\xa7 (U+00A7 SECTION SIGN).'),\n  ...     ('UPDATE_SUBMIT', 'Change')])\n  >>> print(http(b\"\"\"\n  ... POST /sample.txt/edit.html HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... Content-Type: %b\n  ...\n  ... %b\n  ... \"\"\" % (content_type, content)))\n  HTTP/1.1 200 Ok\n  Content-Length: ...\n  Content-Type: text/html;charset=utf-8\n  <BLANKLINE>\n  ...\n      <title>Z3: sample.txt</title>\n  ...\n      <form action=\"http://localhost/sample.txt/edit.html\"\n            method=\"post\" enctype=\"multipart/form-data\">\n        <div>\n          <h3>Change a file</h3>\n  <BLANKLINE>\n          <p>Updated on ...</p>\n  <BLANKLINE>\n        <div class=\"row\">\n  ...<input class=\"textType\" id=\"field.contentType\" name=\"field.contentType\"\n            size=\"20\" type=\"text\" value=\"text/plain; charset=ISO-8859-1\"  />...\n        <div class=\"row\">\n  ...<textarea cols=\"60\" id=\"field.data\" name=\"field.data\" rows=\"15\"\n  >This is a sample text file.\n  <BLANKLINE>\n  It now contains Latin-1 characters, e.g. ... (U+00A7 SECTION SIGN).</textarea></div>\n  ...\n          <div class=\"controls\">\n            <input type=\"submit\" value=\"Refresh\" />\n            <input type=\"submit\" name=\"UPDATE_SUBMIT\"\n                   value=\"Change\" />\n          </div>\n  ...\n      </form>\n  ...\n\nHere's the file\n\n  >>> response = http(b\"\"\"\n  ... GET /sample.txt HTTP/1.1\n  ... \"\"\")\n  >>> print(response)\n  HTTP/1.1 200 Ok\n  Content-Length: ...\n  Content-Type: text/plain; charset=ISO-8859-1\n  Last-Modified: ...\n  <BLANKLINE>\n  This is a sample text file.\n  <BLANKLINE>\n  It now contains Latin-1 characters, e.g. ... (U+00A7 SECTION SIGN).\n\nBody is actually encoded in ISO-8859-1, and not UTF-8\n\n  >>> response.getBody().splitlines()[-1].decode('latin-1')\n  'It now contains Latin-1 characters, e.g. \\xa7 (U+00A7 SECTION SIGN).'\n\nThe user is not allowed to specify a character set that cannot represent all\nthe characters.\n\n  >>> content_type, content = encodeMultipartFormdata([\n  ...     ('field.contentType', 'text/plain; charset=US-ASCII'),\n  ...     ('field.data', 'This is a slightly changed sample text file.\\n\\nIt now contains Latin-1 characters, e.g. \\xa7 (U+00A7 SECTION SIGN).'),\n  ...     ('UPDATE_SUBMIT', 'Change')])\n  >>> print(http(b\"\"\"\n  ... POST /sample.txt/edit.html HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... Content-Type: %b\n  ...\n  ... %b\n  ... \"\"\" % (content_type, content), handle_errors=False))\n  HTTP/1.1 200 Ok\n  Content-Length: ...\n  Content-Type: text/html;charset=utf-8\n  <BLANKLINE>\n  ...\n      <title>Z3: sample.txt</title>\n  ...\n      <form action=\"http://localhost/sample.txt/edit.html\"\n            method=\"post\" enctype=\"multipart/form-data\">\n        <div>\n          <h3>Change a file</h3>\n  <BLANKLINE>\n          <p>The character set you specified (US-ASCII) cannot encode all characters in text.</p>\n  <BLANKLINE>\n        <div class=\"row\">\n  ...<input class=\"textType\" id=\"field.contentType\" name=\"field.contentType\" size=\"20\" type=\"text\" value=\"text/plain; charset=US-ASCII\"  />...\n        <div class=\"row\">\n  ...<textarea cols=\"60\" id=\"field.data\" name=\"field.data\" rows=\"15\" >This is a slightly changed sample text file.\n  <BLANKLINE>\n  It now contains Latin-1 characters, e.g. ... (U+00A7 SECTION SIGN).</textarea></div>\n  ...\n          <div class=\"controls\">\n            <input type=\"submit\" value=\"Refresh\" />\n            <input type=\"submit\" name=\"UPDATE_SUBMIT\"\n                   value=\"Change\" />\n          </div>\n  ...\n      </form>\n  ...\n\nLikewise, the user is not allowed to specify a character set that is not supported by Python.\n\n  >>> content_type, content = encodeMultipartFormdata([\n  ...     ('field.contentType', 'text/plain; charset=I-INVENT-MY-OWN'),\n  ...     ('field.data', 'This is a slightly changed sample text file.\\n\\nIt now contains just ASCII characters.'),\n  ...     ('UPDATE_SUBMIT', 'Change')])\n  >>> print(http(b\"\"\"\n  ... POST /sample.txt/edit.html HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... Content-Type: %b\n  ...\n  ... %b\n  ... \"\"\" % (content_type, content), handle_errors=False))\n  HTTP/1.1 200 Ok\n  Content-Length: ...\n  Content-Type: text/html;charset=utf-8\n  <BLANKLINE>\n  ...\n      <title>Z3: sample.txt</title>\n  ...\n      <form action=\"http://localhost/sample.txt/edit.html\"\n            method=\"post\" enctype=\"multipart/form-data\">\n        <div>\n          <h3>Change a file</h3>\n  <BLANKLINE>\n          <p>The character set you specified (I-INVENT-MY-OWN) is not supported.</p>\n  <BLANKLINE>\n        <div class=\"row\">\n  ...<input class=\"textType\" id=\"field.contentType\" name=\"field.contentType\" size=\"20\" type=\"text\" value=\"text/plain; charset=I-INVENT-MY-OWN\"  />...\n        <div class=\"row\">\n  ...<textarea cols=\"60\" id=\"field.data\" name=\"field.data\" rows=\"15\" >This is a slightly changed sample text file.\n  <BLANKLINE>\n  It now contains just ASCII characters.</textarea></div>\n  ...\n          <div class=\"controls\">\n            <input type=\"submit\" value=\"Refresh\" />\n            <input type=\"submit\" name=\"UPDATE_SUBMIT\"\n                   value=\"Change\" />\n          </div>\n  ...\n      </form>\n  ...\n\nIf you trick Zope and upload a file with a content type that does not\nmatch the file contents, you will not be able to access the edit view:\n\n  >>> print(http(b\"\"\"\n  ... GET /hello.txt.gz/@@edit.html HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... \"\"\", handle_errors=True))\n  HTTP/1.1 200 Ok\n  Content-Length: ...\n  Content-Type: text/html;charset=utf-8\n  <BLANKLINE>\n  ...\n     <li>The character set specified in the content type (UTF-8) does not match file content.</li>\n  ...\n\nNon-ASCII Filenames\n-------------------\n\nFilenames are not restricted to ASCII.\n\n  >>> bj\u00f6rn_txt_gz = (\n  ...     b'\\x1f\\x8b\\x08\\x08\\xcb\\x48\\xea\\x42\\x00\\x03\\x68\\x65\\x6c\\x6c\\x6f\\x2e'\n  ...     b'\\x74\\x78\\x74\\x00\\xcb\\x48\\xcd\\xc9\\xc9\\xe7\\x02\\x00\\x20\\x30\\x3a\\x36'\n  ...     b'\\x06\\x00\\x00\\x00')\n  >>> content_type, content = encodeMultipartFormdata([\n  ...     ('field.contentType', 'application/octet-stream'),\n  ...     ('UPDATE_SUBMIT', 'Add'),\n  ...     ('add_input_name', '')],\n  ...    [('field.data', 'bj\u00f6rn.txt.gz', bj\u00f6rn_txt_gz, 'application/x-gzip')])\n  >>> print(http(b\"\"\"\n  ... POST /+/zope.app.file.File%%3D HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... Content-Type: %b\n  ...\n  ... %b\n  ... \"\"\" % (content_type, content)))\n  HTTP/1.1 303 See Other\n  Content-Length: ...\n  Content-Type: text/html;charset=utf-8\n  Location: http://localhost/@@contents.html\n  <BLANKLINE>\n  ...\n\nSince we did not specify the object name in the form, Zope 3 will use the\nfilename.\n\n  >>> response = http(b\"\"\"\n  ... GET /bj%C3%B6rn.txt.gz HTTP/1.1\n  ... \"\"\")\n  >>> print(response)\n  HTTP/1.1 200 Ok\n  Content-Length: 36\n  Content-Type: application/octet-stream\n  Last-Modified: ...\n  <BLANKLINE>\n  ...\n\n\nSpecial URL handling for DTML pages\n===================================\n\nWhen an HTML File page containing a head tag is visited, without a\ntrailing slash, the base href isn't set.  When visited with a slash,\nit is:\n\n  >>> content_type, content = encodeMultipartFormdata([\n  ...     ('field.contentType', 'text/html'),\n  ...     ('UPDATE_SUBMIT', 'Add'),\n  ...     ('add_input_name', 'file.html')],\n  ...    [('field.data', '', b'', 'application/octet-stream')])\n  >>> print(http(b\"\"\"\n  ... POST /+/zope.app.file.File%%3D HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... Content-Type: %b\n  ... Referer: http://localhost:8081/+/zope.app.file.File=\n  ...\n  ... %b\n  ... \"\"\" % (content_type, content)))\n  HTTP/1.1 303 See Other\n  ...\n\n  >>> content_type, content = encodeMultipartFormdata([\n  ...     ('field.contentType', 'text/html'),\n  ...     ('field.data', b'<html>\\n<head></head>\\n<body>\\n<a href=\"eek.html\">Eek</a>\\n</body>\\n</html>'),\n  ...     ('UPDATE_SUBMIT', 'Change')])\n  >>> print(http(b\"\"\"\n  ... POST /file.html/edit.html HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... Content-Type: %b\n  ... Referer: http://localhost:8081/file.html/edit.html\n  ...\n  ... %b\n  ... \"\"\" % (content_type, content)))\n  HTTP/1.1 200 Ok\n  ...\n\n  >>> print(http(b\"\"\"\n  ... GET /file.html HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... \"\"\"))\n  HTTP/1.1 200 Ok\n  ...\n  <html>\n  <head></head>\n  <body>\n  <a href=\"eek.html\">Eek</a>\n  </body>\n  </html>\n\n\n  >>> print(http(b\"\"\"\n  ... GET /file.html/ HTTP/1.1\n  ... Authorization: Basic mgr:mgrpw\n  ... \"\"\"))\n  HTTP/1.1 200 Ok\n  ...\n  <html>\n  <head>\n  <base href=\"http://localhost/file.html\" />\n  </head>\n  <body>\n  <a href=\"eek.html\">Eek</a>\n  </body>\n  </html>\n\n\nChanges\n=======\n\n5.0 (2024-12-04)\n----------------\n\n- Add support for Python 3.8, 3.9, 3.10, 3.11, 3.12, 3.13.\n\n- Drop support for Python 2.7, 3.4, 3.5, 3.6.\n\n- Fix tests to support ``multipart >= 1.1``.\n\n4.0.0 (2017-05-16)\n------------------\n\n- Add support for Python 3.4, 3.5, 3.6 and PyPy.\n\n- Remove test dependency on ``zope.app.testing`` and ``zope.app.zcmlfiles``,\n  among others.\n\n- Change dependency from ZODB3 to persistent and add missing\n  dependencies on ``zope.app.content``.\n\n\n3.6.1 (2010-09-17)\n------------------\n\n- Removed ZPKG slugs and ZCML ones.\n\n- Moved a functional test here from `zope.app.http`.\n\n- Using Python's ``doctest`` instead of deprecated ``zope.testing.doctest``.\n\n\n3.6.0 (2010-08-19)\n------------------\n\n- Updated ``ftesting.zcml`` to use the new permission names exported by\n  ``zope.dublincore`` 3.7.\n\n- Using python's `doctest` instead of deprecated `zope.testing.doctest`.\n\n\n3.5.1 (2010-01-08)\n------------------\n\n- Fix ftesting.zcml due to zope.securitypolicy update.\n\n- Added missing dependency on transaction.\n\n- Import content-type parser from zope.contenttype, reducing zope.publisher to\n  a test dependency.\n\n- Fix tests using a newer zope.publisher that requires zope.login.\n\n3.5.0 (2009-01-31)\n------------------\n\n- Replace ``zope.app.folder`` use by ``zope.site``. Add missing\n  dependency in ``setup.py``.\n\n3.4.6 (2009-01-27)\n------------------\n\n- Remove zope.app.zapi dependency again. Previous release\n  was wrong. We removed the zope.app.zapi uses before, so\n  we don't need it anymore.\n\n3.4.5 (2009-01-27)\n------------------\n\n- added missing dependency: zope.app.zapi\n\n3.4.4 (2008-09-05)\n------------------\n\n- Bug: Get actual filename instead of full filesystem path when adding\n  file/image using Internet Explorer.\n\n3.4.3 (2008-06-18)\n------------------\n\n- Using IDCTimes interface instead of IZopeDublinCore to determine the\n  modification date of a file.\n\n3.4.2 (2007-11-09)\n------------------\n\n- Include information about which attributes changed in the\n  ``IObjectModifiedEvent`` after upload.\n\n  This fixes https://bugs.launchpad.net/zope3/+bug/98483.\n\n3.4.1 (2007-10-31)\n------------------\n\n- Resolve ``ZopeSecurityPolicy`` deprecation warning.\n\n\n3.4.0 (2007-10-24)\n------------------\n\n- Initial release independent of the main Zope tree.\n",
    "bugtrack_url": null,
    "license": "ZPL 2.1",
    "summary": "File and Image -- Zope 3 Content Components",
    "version": "5.0",
    "project_urls": {
        "Homepage": "https://github.com/zopefoundation/zope.app.file"
    },
    "split_keywords": [
        "zope3",
        "file",
        "image",
        "content"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "6e091cd495e0462a2f98b1a8e5af8444b35f491d5889d328c26565190682de07",
                "md5": "ee51d3da1b26fd08c1b9c5ce5c894313",
                "sha256": "a019977779085b41c67fbe9b863662391457f648c915c487acff1d5c1ac2f0f9"
            },
            "downloads": -1,
            "filename": "zope.app.file-5.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "ee51d3da1b26fd08c1b9c5ce5c894313",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 39493,
            "upload_time": "2024-12-04T07:41:40",
            "upload_time_iso_8601": "2024-12-04T07:41:40.447911Z",
            "url": "https://files.pythonhosted.org/packages/6e/09/1cd495e0462a2f98b1a8e5af8444b35f491d5889d328c26565190682de07/zope.app.file-5.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "dcd342b618d8fa9fc7ebcff58394b1f92ced3386fe4b617cb5271ba0a0ef61ed",
                "md5": "9f5c42ae081cfd6c8a57ad2a2829ba7a",
                "sha256": "7cbad21cc329b3f64c4bc6db2a03c12dbdca87fbdad8272353ebdbdc5b77f3ff"
            },
            "downloads": -1,
            "filename": "zope_app_file-5.0.tar.gz",
            "has_sig": false,
            "md5_digest": "9f5c42ae081cfd6c8a57ad2a2829ba7a",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 35372,
            "upload_time": "2024-12-04T07:41:42",
            "upload_time_iso_8601": "2024-12-04T07:41:42.795842Z",
            "url": "https://files.pythonhosted.org/packages/dc/d3/42b618d8fa9fc7ebcff58394b1f92ced3386fe4b617cb5271ba0a0ef61ed/zope_app_file-5.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-12-04 07:41:42",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "zopefoundation",
    "github_project": "zope.app.file",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "zope.app.file"
}
        
Elapsed time: 0.79491s