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" />
<b>Object Name</b>
<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 <b>Object Name</b> \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"
}