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

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

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

  >>> print http(r"""
  ... GET /@@contents.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... """)
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  <BLANKLINE>
  ...
      Z3: 
  ...

Add:
                              
  ...
      
        File (http://localhost/@@+/action.html?type_name=zope.app.file.File)
      
  ...

Let's follow that link.

  >>> print http(r"""
  ... 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(r"""
  ... 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>
  ...
      Z3: +
  ...
  ...

Add a File
      ......
      ......

            Object Name  

  ...
    
  ...

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

Let us upload a binary file.

  >>> print http("""
  ... POST /+/zope.app.file.File%3D HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: multipart/form-data; boundary=---------------------------73793505419963331401738523176
  ...
  ... -----------------------------73793505419963331401738523176
  ... Content-Disposition: form-data; name="field.contentType"
  ...
  ... application/octet-stream
  ... -----------------------------73793505419963331401738523176
  ... Content-Disposition: form-data; name="field.data"; filename="hello.txt.gz"
  ... Content-Type: application/x-gzip
  ...
  ... \x1f\x8b\x08\x08\xcb\x48\xea\x42\x00\x03\x68\x65\x6c\x6c\x6f\x2e\
  ... \x74\x78\x74\x00\xcb\x48\xcd\xc9\xc9\xe7\x02\x00\x20\x30\x3a\x36\
  ... \x06\x00\x00\x00
  ... -----------------------------73793505419963331401738523176
  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
  ...
  ... Add
  ... -----------------------------73793505419963331401738523176
  ... Content-Disposition: form-data; name="add_input_name"
  ...
  ...
  ... -----------------------------73793505419963331401738523176--
  ... """)
  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("""
  ... 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().encode('base64')
  'H4sICMtI6kIAA2hlbGxvLnR4dADLSM3JyecCACAwOjYGAAAA\n'

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

Let us now create a text file.

  >>> print http(r"""
  ... POST /+/zope.app.file.File%3D HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: multipart/form-data; boundary=---------------------------167769037320366690221542301033
  ...
  ... -----------------------------167769037320366690221542301033
  ... Content-Disposition: form-data; name="field.contentType"
  ...
  ... text/plain
  ... -----------------------------167769037320366690221542301033
  ... Content-Disposition: form-data; name="field.data"; filename=""
  ... Content-Type: application/octet-stream
  ...
  ...
  ... -----------------------------167769037320366690221542301033
  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
  ...
  ... Add
  ... -----------------------------167769037320366690221542301033
  ... Content-Disposition: form-data; name="add_input_name"
  ...
  ... sample.txt
  ... -----------------------------167769037320366690221542301033--
  ... """)
  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("""
  ... 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(r"""
  ... 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>
  ...
      Z3: sample.txt
  ...

Change a file
  ......
  ......
  ...

  ...
      
  ...

Files of type text/plain without any charset information can only contain ASCII
text.

  >>> print http(r"""
  ... POST /sample.txt/edit.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Length: ...
  ... Content-Type: multipart/form-data; boundary=---------------------------165727764114325486311042046845
  ...
  ... -----------------------------165727764114325486311042046845
  ... Content-Disposition: form-data; name="field.contentType"
  ...
  ... text/plain
  ... -----------------------------165727764114325486311042046845
  ... Content-Disposition: form-data; name="field.data"
  ...
  ... This is a sample text file.
  ...
  ... It can only contain US-ASCII characters.
  ... -----------------------------165727764114325486311042046845
  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
  ...
  ... Change
  ... -----------------------------165727764114325486311042046845--
  ... """, handle_errors=False)
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  <BLANKLINE>
  ...
      Z3: sample.txt
  ...

Change a file
  <BLANKLINE>
          
Updated on ...
  <BLANKLINE>
        
  ......
        
  ...This is a sample text file.
  <BLANKLINE>
  It can only contain US-ASCII characters.
  ...

  ...
      
  ...

Here's the file

  >>> print http(r"""
  ... 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 only contain US-ASCII characters.

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

If we want an uploaded text file to contain non-ASCII characters, we have to
explicitly specify the charset.

  >>> print http("""
  ... POST /sample.txt/edit.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Length: ...
  ... Content-Type: multipart/form-data; boundary=---------------------------165727764114325486311042046845
  ...
  ... -----------------------------165727764114325486311042046845
  ... Content-Disposition: form-data; name="field.contentType"
  ...
  ... text/plain; charset=UTF-8
  ... -----------------------------165727764114325486311042046845
  ... Content-Disposition: form-data; name="field.data"
  ...
  ... This is a sample text file.
  ...
  ... It can now contain UTF-8 characters, e.g. \xe2\x98\xbb (U+263B BLACK SMILING FACE).
  ... -----------------------------165727764114325486311042046845
  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
  ...
  ... Change
  ... -----------------------------165727764114325486311042046845--
  ... """)
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  <BLANKLINE>
  ...
      Z3: sample.txt
  ...

Change a file
  <BLANKLINE>
          
Updated on ...
  <BLANKLINE>
        
  ......
        
  ...This is a sample text file.
  <BLANKLINE>
  It can now contain UTF-8 characters, e.g. ... (U+263B BLACK SMILING FACE).
  ...

  ...
      
  ...

Here's the file

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

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

You can use other charsets too.  Note that the browser form is always UTF-8.

  >>> print http("""
  ... POST /sample.txt/edit.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Length: ...
  ... Content-Type: multipart/form-data; boundary=---------------------------165727764114325486311042046845
  ...
  ... -----------------------------165727764114325486311042046845
  ... Content-Disposition: form-data; name="field.contentType"
  ...
  ... text/plain; charset=ISO-8859-1
  ... -----------------------------165727764114325486311042046845
  ... Content-Disposition: form-data; name="field.data"
  ...
  ... This is a sample text file.
  ...
  ... It now contains Latin-1 characters, e.g. \xc2\xa7 (U+00A7 SECTION SIGN).
  ... -----------------------------165727764114325486311042046845
  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
  ...
  ... Change
  ... -----------------------------165727764114325486311042046845--
  ... """)
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  <BLANKLINE>
  ...
      Z3: sample.txt
  ...

Change a file
  <BLANKLINE>
          
Updated on ...
  <BLANKLINE>
        
  ......
        
  ...This is a sample text file.
  <BLANKLINE>
  It now contains Latin-1 characters, e.g. ... (U+00A7 SECTION SIGN).
  ...

  ...
      
  ...

Here's the file

  >>> response = http(r"""
  ... 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]
  '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.

  >>> print http("""
  ... POST /sample.txt/edit.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Length: ...
  ... Content-Type: multipart/form-data; boundary=---------------------------165727764114325486311042046845
  ...
  ... -----------------------------165727764114325486311042046845
  ... Content-Disposition: form-data; name="field.contentType"
  ...
  ... text/plain
  ... -----------------------------165727764114325486311042046845
  ... Content-Disposition: form-data; name="field.data"
  ...
  ... This is a slightly changed sample text file.
  ...
  ... It now contains Latin-1 characters, e.g. \xc2\xa7 (U+00A7 SECTION SIGN).
  ... -----------------------------165727764114325486311042046845
  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
  ...
  ... Change
  ... -----------------------------165727764114325486311042046845--
  ... """, handle_errors=False)
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  <BLANKLINE>
  ...
      Z3: sample.txt
  ...

Change a file
  <BLANKLINE>
          
The character set you specified (ASCII) cannot encode all characters in text.
  <BLANKLINE>
        
  ......
        
  ...This is a slightly changed sample text file.
  <BLANKLINE>
  It now contains Latin-1 characters, e.g. ... (U+00A7 SECTION SIGN).
  ...

  ...
      
  ...

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

  >>> print http("""
  ... POST /sample.txt/edit.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Length: ...
  ... Content-Type: multipart/form-data; boundary=---------------------------165727764114325486311042046845
  ...
  ... -----------------------------165727764114325486311042046845
  ... Content-Disposition: form-data; name="field.contentType"
  ...
  ... text/plain; charset=I-INVENT-MY-OWN
  ... -----------------------------165727764114325486311042046845
  ... Content-Disposition: form-data; name="field.data"
  ...
  ... This is a slightly changed sample text file.
  ...
  ... It now contains just ASCII characters.
  ... -----------------------------165727764114325486311042046845
  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
  ...
  ... Change
  ... -----------------------------165727764114325486311042046845--
  ... """, handle_errors=False)
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  <BLANKLINE>
  ...
      Z3: sample.txt
  ...

Change a file
  <BLANKLINE>
          
The character set you specified (I-INVENT-MY-OWN) is not supported.
  <BLANKLINE>
        
  ......
        
  ...This is a slightly changed sample text file.
  <BLANKLINE>
  It now contains just ASCII characters.
  ...

  ...
      
  ...

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(r"""
  ... GET /hello.txt.gz/@@edit.html HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... """)
  HTTP/1.1 200 Ok
  Content-Length: ...
  Content-Type: text/html;charset=utf-8
  <BLANKLINE>
  ...
     
- The character set specified in the content type (ASCII) does not match file content.
  ...

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

Filenames are not restricted to ASCII.

  >>> print http("""
  ... POST /+/zope.app.file.File%3D HTTP/1.1
  ... Authorization: Basic mgr:mgrpw
  ... Content-Type: multipart/form-data; boundary=---------------------------73793505419963331401738523176
  ...
  ... -----------------------------73793505419963331401738523176
  ... Content-Disposition: form-data; name="field.contentType"
  ...
  ... application/octet-stream
  ... -----------------------------73793505419963331401738523176
  ... Content-Disposition: form-data; name="field.data"; filename="bj\xc3\xb6rn.txt.gz"
  ... Content-Type: application/x-gzip
  ...
  ... \x1f\x8b\x08\x08\xcb\x48\xea\x42\x00\x03\x68\x65\x6c\x6c\x6f\x2e\
  ... \x74\x78\x74\x00\xcb\x48\xcd\xc9\xc9\xe7\x02\x00\x20\x30\x3a\x36\
  ... \x06\x00\x00\x00
  ... -----------------------------73793505419963331401738523176
  ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
  ...
  ... Add
  ... -----------------------------73793505419963331401738523176
  ... Content-Disposition: form-data; name="add_input_name"
  ...
  ...
  ... -----------------------------73793505419963331401738523176--
  ... """)
  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("""
  ... GET /bj%C3%B6rn.txt.gz HTTP/1.1
  ... """)
  >>> print response
  HTTP/1.1 200 Ok
  Content-Length: 36
  Content-Type: application/octet-stream
  <BLANKLINE>
  ...

