mirror of https://github.com/python/cpython
gh-104773: PEP 594: Remove cgi and cgitb modules (#104775)
* Replace "cgi" with "!cgi" in the Sphinx documentation to avoid warnings on broken references. * test_pyclbr no longer tests the cgi module.
This commit is contained in:
parent
e561c09975
commit
08d5923896
|
@ -1,564 +0,0 @@
|
|||
:mod:`cgi` --- Common Gateway Interface support
|
||||
===============================================
|
||||
|
||||
.. module:: cgi
|
||||
:synopsis: Helpers for running Python scripts via the Common Gateway Interface.
|
||||
:deprecated:
|
||||
|
||||
**Source code:** :source:`Lib/cgi.py`
|
||||
|
||||
.. index::
|
||||
pair: WWW; server
|
||||
pair: CGI; protocol
|
||||
pair: HTTP; protocol
|
||||
pair: MIME; headers
|
||||
single: URL
|
||||
single: Common Gateway Interface
|
||||
|
||||
.. deprecated-removed:: 3.11 3.13
|
||||
The :mod:`cgi` module is deprecated
|
||||
(see :pep:`PEP 594 <594#cgi>` for details and alternatives).
|
||||
|
||||
The :class:`FieldStorage` class can typically be replaced with
|
||||
:func:`urllib.parse.parse_qsl` for ``GET`` and ``HEAD`` requests,
|
||||
and the :mod:`email.message` module or
|
||||
`multipart <https://pypi.org/project/multipart/>`_ for ``POST`` and ``PUT``.
|
||||
Most :ref:`utility functions <functions-in-cgi-module>` have replacements.
|
||||
|
||||
--------------
|
||||
|
||||
Support module for Common Gateway Interface (CGI) scripts.
|
||||
|
||||
This module defines a number of utilities for use by CGI scripts written in
|
||||
Python.
|
||||
|
||||
The global variable ``maxlen`` can be set to an integer indicating the maximum
|
||||
size of a POST request. POST requests larger than this size will result in a
|
||||
:exc:`ValueError` being raised during parsing. The default value of this
|
||||
variable is ``0``, meaning the request size is unlimited.
|
||||
|
||||
.. include:: ../includes/wasm-notavail.rst
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
.. _cgi-intro:
|
||||
|
||||
A CGI script is invoked by an HTTP server, usually to process user input
|
||||
submitted through an HTML ``<FORM>`` or ``<ISINDEX>`` element.
|
||||
|
||||
Most often, CGI scripts live in the server's special :file:`cgi-bin` directory.
|
||||
The HTTP server places all sorts of information about the request (such as the
|
||||
client's hostname, the requested URL, the query string, and lots of other
|
||||
goodies) in the script's shell environment, executes the script, and sends the
|
||||
script's output back to the client.
|
||||
|
||||
The script's input is connected to the client too, and sometimes the form data
|
||||
is read this way; at other times the form data is passed via the "query string"
|
||||
part of the URL. This module is intended to take care of the different cases
|
||||
and provide a simpler interface to the Python script. It also provides a number
|
||||
of utilities that help in debugging scripts, and the latest addition is support
|
||||
for file uploads from a form (if your browser supports it).
|
||||
|
||||
The output of a CGI script should consist of two sections, separated by a blank
|
||||
line. The first section contains a number of headers, telling the client what
|
||||
kind of data is following. Python code to generate a minimal header section
|
||||
looks like this::
|
||||
|
||||
print("Content-Type: text/html") # HTML is following
|
||||
print() # blank line, end of headers
|
||||
|
||||
The second section is usually HTML, which allows the client software to display
|
||||
nicely formatted text with header, in-line images, etc. Here's Python code that
|
||||
prints a simple piece of HTML::
|
||||
|
||||
print("<TITLE>CGI script output</TITLE>")
|
||||
print("<H1>This is my first CGI script</H1>")
|
||||
print("Hello, world!")
|
||||
|
||||
|
||||
.. _using-the-cgi-module:
|
||||
|
||||
Using the cgi module
|
||||
--------------------
|
||||
|
||||
Begin by writing ``import cgi``.
|
||||
|
||||
When you write a new script, consider adding these lines::
|
||||
|
||||
import cgitb
|
||||
cgitb.enable()
|
||||
|
||||
This activates a special exception handler that will display detailed reports in
|
||||
the web browser if any errors occur. If you'd rather not show the guts of your
|
||||
program to users of your script, you can have the reports saved to files
|
||||
instead, with code like this::
|
||||
|
||||
import cgitb
|
||||
cgitb.enable(display=0, logdir="/path/to/logdir")
|
||||
|
||||
It's very helpful to use this feature during script development. The reports
|
||||
produced by :mod:`cgitb` provide information that can save you a lot of time in
|
||||
tracking down bugs. You can always remove the ``cgitb`` line later when you
|
||||
have tested your script and are confident that it works correctly.
|
||||
|
||||
To get at submitted form data, use the :class:`FieldStorage` class. If the form
|
||||
contains non-ASCII characters, use the *encoding* keyword parameter set to the
|
||||
value of the encoding defined for the document. It is usually contained in the
|
||||
META tag in the HEAD section of the HTML document or by the
|
||||
:mailheader:`Content-Type` header. This reads the form contents from the
|
||||
standard input or the environment (depending on the value of various
|
||||
environment variables set according to the CGI standard). Since it may consume
|
||||
standard input, it should be instantiated only once.
|
||||
|
||||
The :class:`FieldStorage` instance can be indexed like a Python dictionary.
|
||||
It allows membership testing with the :keyword:`in` operator, and also supports
|
||||
the standard dictionary method :meth:`~dict.keys` and the built-in function
|
||||
:func:`len`. Form fields containing empty strings are ignored and do not appear
|
||||
in the dictionary; to keep such values, provide a true value for the optional
|
||||
*keep_blank_values* keyword parameter when creating the :class:`FieldStorage`
|
||||
instance.
|
||||
|
||||
For instance, the following code (which assumes that the
|
||||
:mailheader:`Content-Type` header and blank line have already been printed)
|
||||
checks that the fields ``name`` and ``addr`` are both set to a non-empty
|
||||
string::
|
||||
|
||||
form = cgi.FieldStorage()
|
||||
if "name" not in form or "addr" not in form:
|
||||
print("<H1>Error</H1>")
|
||||
print("Please fill in the name and addr fields.")
|
||||
return
|
||||
print("<p>name:", form["name"].value)
|
||||
print("<p>addr:", form["addr"].value)
|
||||
...further form processing here...
|
||||
|
||||
Here the fields, accessed through ``form[key]``, are themselves instances of
|
||||
:class:`FieldStorage` (or :class:`MiniFieldStorage`, depending on the form
|
||||
encoding). The :attr:`~FieldStorage.value` attribute of the instance yields
|
||||
the string value of the field. The :meth:`~FieldStorage.getvalue` method
|
||||
returns this string value directly; it also accepts an optional second argument
|
||||
as a default to return if the requested key is not present.
|
||||
|
||||
If the submitted form data contains more than one field with the same name, the
|
||||
object retrieved by ``form[key]`` is not a :class:`FieldStorage` or
|
||||
:class:`MiniFieldStorage` instance but a list of such instances. Similarly, in
|
||||
this situation, ``form.getvalue(key)`` would return a list of strings. If you
|
||||
expect this possibility (when your HTML form contains multiple fields with the
|
||||
same name), use the :meth:`~FieldStorage.getlist` method, which always returns
|
||||
a list of values (so that you do not need to special-case the single item
|
||||
case). For example, this code concatenates any number of username fields,
|
||||
separated by commas::
|
||||
|
||||
value = form.getlist("username")
|
||||
usernames = ",".join(value)
|
||||
|
||||
If a field represents an uploaded file, accessing the value via the
|
||||
:attr:`~FieldStorage.value` attribute or the :meth:`~FieldStorage.getvalue`
|
||||
method reads the entire file in memory as bytes. This may not be what you
|
||||
want. You can test for an uploaded file by testing either the
|
||||
:attr:`~FieldStorage.filename` attribute or the :attr:`~FieldStorage.file`
|
||||
attribute. You can then read the data from the :attr:`!file`
|
||||
attribute before it is automatically closed as part of the garbage collection of
|
||||
the :class:`FieldStorage` instance
|
||||
(the :func:`~io.RawIOBase.read` and :func:`~io.IOBase.readline` methods will
|
||||
return bytes)::
|
||||
|
||||
fileitem = form["userfile"]
|
||||
if fileitem.file:
|
||||
# It's an uploaded file; count lines
|
||||
linecount = 0
|
||||
while True:
|
||||
line = fileitem.file.readline()
|
||||
if not line: break
|
||||
linecount = linecount + 1
|
||||
|
||||
:class:`FieldStorage` objects also support being used in a :keyword:`with`
|
||||
statement, which will automatically close them when done.
|
||||
|
||||
If an error is encountered when obtaining the contents of an uploaded file
|
||||
(for example, when the user interrupts the form submission by clicking on
|
||||
a Back or Cancel button) the :attr:`~FieldStorage.done` attribute of the
|
||||
object for the field will be set to the value -1.
|
||||
|
||||
The file upload draft standard entertains the possibility of uploading multiple
|
||||
files from one field (using a recursive :mimetype:`multipart/\*` encoding).
|
||||
When this occurs, the item will be a dictionary-like :class:`FieldStorage` item.
|
||||
This can be determined by testing its :attr:`!type` attribute, which should be
|
||||
:mimetype:`multipart/form-data` (or perhaps another MIME type matching
|
||||
:mimetype:`multipart/\*`). In this case, it can be iterated over recursively
|
||||
just like the top-level form object.
|
||||
|
||||
When a form is submitted in the "old" format (as the query string or as a single
|
||||
data part of type :mimetype:`application/x-www-form-urlencoded`), the items will
|
||||
actually be instances of the class :class:`MiniFieldStorage`. In this case, the
|
||||
:attr:`!list`, :attr:`!file`, and :attr:`filename` attributes are always ``None``.
|
||||
|
||||
A form submitted via POST that also has a query string will contain both
|
||||
:class:`FieldStorage` and :class:`MiniFieldStorage` items.
|
||||
|
||||
.. versionchanged:: 3.4
|
||||
The :attr:`~FieldStorage.file` attribute is automatically closed upon the
|
||||
garbage collection of the creating :class:`FieldStorage` instance.
|
||||
|
||||
.. versionchanged:: 3.5
|
||||
Added support for the context management protocol to the
|
||||
:class:`FieldStorage` class.
|
||||
|
||||
|
||||
Higher Level Interface
|
||||
----------------------
|
||||
|
||||
The previous section explains how to read CGI form data using the
|
||||
:class:`FieldStorage` class. This section describes a higher level interface
|
||||
which was added to this class to allow one to do it in a more readable and
|
||||
intuitive way. The interface doesn't make the techniques described in previous
|
||||
sections obsolete --- they are still useful to process file uploads efficiently,
|
||||
for example.
|
||||
|
||||
.. XXX: Is this true ?
|
||||
|
||||
The interface consists of two simple methods. Using the methods you can process
|
||||
form data in a generic way, without the need to worry whether only one or more
|
||||
values were posted under one name.
|
||||
|
||||
In the previous section, you learned to write following code anytime you
|
||||
expected a user to post more than one value under one name::
|
||||
|
||||
item = form.getvalue("item")
|
||||
if isinstance(item, list):
|
||||
# The user is requesting more than one item.
|
||||
else:
|
||||
# The user is requesting only one item.
|
||||
|
||||
This situation is common for example when a form contains a group of multiple
|
||||
checkboxes with the same name::
|
||||
|
||||
<input type="checkbox" name="item" value="1" />
|
||||
<input type="checkbox" name="item" value="2" />
|
||||
|
||||
In most situations, however, there's only one form control with a particular
|
||||
name in a form and then you expect and need only one value associated with this
|
||||
name. So you write a script containing for example this code::
|
||||
|
||||
user = form.getvalue("user").upper()
|
||||
|
||||
The problem with the code is that you should never expect that a client will
|
||||
provide valid input to your scripts. For example, if a curious user appends
|
||||
another ``user=foo`` pair to the query string, then the script would crash,
|
||||
because in this situation the ``getvalue("user")`` method call returns a list
|
||||
instead of a string. Calling the :meth:`~str.upper` method on a list is not valid
|
||||
(since lists do not have a method of this name) and results in an
|
||||
:exc:`AttributeError` exception.
|
||||
|
||||
Therefore, the appropriate way to read form data values was to always use the
|
||||
code which checks whether the obtained value is a single value or a list of
|
||||
values. That's annoying and leads to less readable scripts.
|
||||
|
||||
A more convenient approach is to use the methods :meth:`~FieldStorage.getfirst`
|
||||
and :meth:`~FieldStorage.getlist` provided by this higher level interface.
|
||||
|
||||
|
||||
.. method:: FieldStorage.getfirst(name, default=None)
|
||||
|
||||
This method always returns only one value associated with form field *name*.
|
||||
The method returns only the first value in case that more values were posted
|
||||
under such name. Please note that the order in which the values are received
|
||||
may vary from browser to browser and should not be counted on. [#]_ If no such
|
||||
form field or value exists then the method returns the value specified by the
|
||||
optional parameter *default*. This parameter defaults to ``None`` if not
|
||||
specified.
|
||||
|
||||
|
||||
.. method:: FieldStorage.getlist(name)
|
||||
|
||||
This method always returns a list of values associated with form field *name*.
|
||||
The method returns an empty list if no such form field or value exists for
|
||||
*name*. It returns a list consisting of one item if only one such value exists.
|
||||
|
||||
Using these methods you can write nice compact code::
|
||||
|
||||
import cgi
|
||||
form = cgi.FieldStorage()
|
||||
user = form.getfirst("user", "").upper() # This way it's safe.
|
||||
for item in form.getlist("item"):
|
||||
do_something(item)
|
||||
|
||||
|
||||
.. _functions-in-cgi-module:
|
||||
|
||||
Functions
|
||||
---------
|
||||
|
||||
These are useful if you want more control, or if you want to employ some of the
|
||||
algorithms implemented in this module in other circumstances.
|
||||
|
||||
|
||||
.. function:: parse(fp=None, environ=os.environ, keep_blank_values=False, strict_parsing=False, separator="&")
|
||||
|
||||
Parse a query in the environment or from a file (the file defaults to
|
||||
``sys.stdin``). The *keep_blank_values*, *strict_parsing* and *separator* parameters are
|
||||
passed to :func:`urllib.parse.parse_qs` unchanged.
|
||||
|
||||
.. deprecated-removed:: 3.11 3.13
|
||||
This function, like the rest of the :mod:`cgi` module, is deprecated.
|
||||
It can be replaced by calling :func:`urllib.parse.parse_qs` directly
|
||||
on the desired query string (except for ``multipart/form-data`` input,
|
||||
which can be handled as described for :func:`parse_multipart`).
|
||||
|
||||
|
||||
.. function:: parse_multipart(fp, pdict, encoding="utf-8", errors="replace", separator="&")
|
||||
|
||||
Parse input of type :mimetype:`multipart/form-data` (for file uploads).
|
||||
Arguments are *fp* for the input file, *pdict* for a dictionary containing
|
||||
other parameters in the :mailheader:`Content-Type` header, and *encoding*,
|
||||
the request encoding.
|
||||
|
||||
Returns a dictionary just like :func:`urllib.parse.parse_qs`: keys are the
|
||||
field names, each value is a list of values for that field. For non-file
|
||||
fields, the value is a list of strings.
|
||||
|
||||
This is easy to use but not much good if you are expecting megabytes to be
|
||||
uploaded --- in that case, use the :class:`FieldStorage` class instead
|
||||
which is much more flexible.
|
||||
|
||||
.. versionchanged:: 3.7
|
||||
Added the *encoding* and *errors* parameters. For non-file fields, the
|
||||
value is now a list of strings, not bytes.
|
||||
|
||||
.. versionchanged:: 3.10
|
||||
Added the *separator* parameter.
|
||||
|
||||
.. deprecated-removed:: 3.11 3.13
|
||||
This function, like the rest of the :mod:`cgi` module, is deprecated.
|
||||
It can be replaced with the functionality in the :mod:`email` package
|
||||
(e.g. :class:`email.message.EmailMessage`/:class:`email.message.Message`)
|
||||
which implements the same MIME RFCs, or with the
|
||||
`multipart <https://pypi.org/project/multipart/>`__ PyPI project.
|
||||
|
||||
|
||||
.. function:: parse_header(string)
|
||||
|
||||
Parse a MIME header (such as :mailheader:`Content-Type`) into a main value and a
|
||||
dictionary of parameters.
|
||||
|
||||
.. deprecated-removed:: 3.11 3.13
|
||||
This function, like the rest of the :mod:`cgi` module, is deprecated.
|
||||
It can be replaced with the functionality in the :mod:`email` package,
|
||||
which implements the same MIME RFCs.
|
||||
|
||||
For example, with :class:`email.message.EmailMessage`::
|
||||
|
||||
from email.message import EmailMessage
|
||||
msg = EmailMessage()
|
||||
msg['content-type'] = 'application/json; charset="utf8"'
|
||||
main, params = msg.get_content_type(), msg['content-type'].params
|
||||
|
||||
|
||||
.. function:: test()
|
||||
|
||||
Robust test CGI script, usable as main program. Writes minimal HTTP headers and
|
||||
formats all information provided to the script in HTML format.
|
||||
|
||||
|
||||
.. function:: print_environ()
|
||||
|
||||
Format the shell environment in HTML.
|
||||
|
||||
|
||||
.. function:: print_form(form)
|
||||
|
||||
Format a form in HTML.
|
||||
|
||||
|
||||
.. function:: print_directory()
|
||||
|
||||
Format the current directory in HTML.
|
||||
|
||||
|
||||
.. function:: print_environ_usage()
|
||||
|
||||
Print a list of useful (used by CGI) environment variables in HTML.
|
||||
|
||||
|
||||
.. _cgi-security:
|
||||
|
||||
Caring about security
|
||||
---------------------
|
||||
|
||||
.. index:: pair: CGI; security
|
||||
|
||||
There's one important rule: if you invoke an external program (via
|
||||
:func:`os.system`, :func:`os.popen` or other functions with similar
|
||||
functionality), make very sure you don't pass arbitrary strings received from
|
||||
the client to the shell. This is a well-known security hole whereby clever
|
||||
hackers anywhere on the web can exploit a gullible CGI script to invoke
|
||||
arbitrary shell commands. Even parts of the URL or field names cannot be
|
||||
trusted, since the request doesn't have to come from your form!
|
||||
|
||||
To be on the safe side, if you must pass a string gotten from a form to a shell
|
||||
command, you should make sure the string contains only alphanumeric characters,
|
||||
dashes, underscores, and periods.
|
||||
|
||||
|
||||
Installing your CGI script on a Unix system
|
||||
-------------------------------------------
|
||||
|
||||
Read the documentation for your HTTP server and check with your local system
|
||||
administrator to find the directory where CGI scripts should be installed;
|
||||
usually this is in a directory :file:`cgi-bin` in the server tree.
|
||||
|
||||
Make sure that your script is readable and executable by "others"; the Unix file
|
||||
mode should be ``0o755`` octal (use ``chmod 0755 filename``). Make sure that the
|
||||
first line of the script contains ``#!`` starting in column 1 followed by the
|
||||
pathname of the Python interpreter, for instance::
|
||||
|
||||
#!/usr/local/bin/python
|
||||
|
||||
Make sure the Python interpreter exists and is executable by "others".
|
||||
|
||||
Make sure that any files your script needs to read or write are readable or
|
||||
writable, respectively, by "others" --- their mode should be ``0o644`` for
|
||||
readable and ``0o666`` for writable. This is because, for security reasons, the
|
||||
HTTP server executes your script as user "nobody", without any special
|
||||
privileges. It can only read (write, execute) files that everybody can read
|
||||
(write, execute). The current directory at execution time is also different (it
|
||||
is usually the server's cgi-bin directory) and the set of environment variables
|
||||
is also different from what you get when you log in. In particular, don't count
|
||||
on the shell's search path for executables (:envvar:`PATH`) or the Python module
|
||||
search path (:envvar:`PYTHONPATH`) to be set to anything interesting.
|
||||
|
||||
If you need to load modules from a directory which is not on Python's default
|
||||
module search path, you can change the path in your script, before importing
|
||||
other modules. For example::
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, "/usr/home/joe/lib/python")
|
||||
sys.path.insert(0, "/usr/local/lib/python")
|
||||
|
||||
(This way, the directory inserted last will be searched first!)
|
||||
|
||||
Instructions for non-Unix systems will vary; check your HTTP server's
|
||||
documentation (it will usually have a section on CGI scripts).
|
||||
|
||||
|
||||
Testing your CGI script
|
||||
-----------------------
|
||||
|
||||
Unfortunately, a CGI script will generally not run when you try it from the
|
||||
command line, and a script that works perfectly from the command line may fail
|
||||
mysteriously when run from the server. There's one reason why you should still
|
||||
test your script from the command line: if it contains a syntax error, the
|
||||
Python interpreter won't execute it at all, and the HTTP server will most likely
|
||||
send a cryptic error to the client.
|
||||
|
||||
Assuming your script has no syntax errors, yet it does not work, you have no
|
||||
choice but to read the next section.
|
||||
|
||||
|
||||
Debugging CGI scripts
|
||||
---------------------
|
||||
|
||||
.. index:: pair: CGI; debugging
|
||||
|
||||
First of all, check for trivial installation errors --- reading the section
|
||||
above on installing your CGI script carefully can save you a lot of time. If
|
||||
you wonder whether you have understood the installation procedure correctly, try
|
||||
installing a copy of this module file (:file:`cgi.py`) as a CGI script. When
|
||||
invoked as a script, the file will dump its environment and the contents of the
|
||||
form in HTML format. Give it the right mode etc., and send it a request. If it's
|
||||
installed in the standard :file:`cgi-bin` directory, it should be possible to
|
||||
send it a request by entering a URL into your browser of the form:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
http://yourhostname/cgi-bin/cgi.py?name=Joe+Blow&addr=At+Home
|
||||
|
||||
If this gives an error of type 404, the server cannot find the script -- perhaps
|
||||
you need to install it in a different directory. If it gives another error,
|
||||
there's an installation problem that you should fix before trying to go any
|
||||
further. If you get a nicely formatted listing of the environment and form
|
||||
content (in this example, the fields should be listed as "addr" with value "At
|
||||
Home" and "name" with value "Joe Blow"), the :file:`cgi.py` script has been
|
||||
installed correctly. If you follow the same procedure for your own script, you
|
||||
should now be able to debug it.
|
||||
|
||||
The next step could be to call the :mod:`cgi` module's :func:`test` function
|
||||
from your script: replace its main code with the single statement ::
|
||||
|
||||
cgi.test()
|
||||
|
||||
This should produce the same results as those gotten from installing the
|
||||
:file:`cgi.py` file itself.
|
||||
|
||||
When an ordinary Python script raises an unhandled exception (for whatever
|
||||
reason: of a typo in a module name, a file that can't be opened, etc.), the
|
||||
Python interpreter prints a nice traceback and exits. While the Python
|
||||
interpreter will still do this when your CGI script raises an exception, most
|
||||
likely the traceback will end up in one of the HTTP server's log files, or be
|
||||
discarded altogether.
|
||||
|
||||
Fortunately, once you have managed to get your script to execute *some* code,
|
||||
you can easily send tracebacks to the web browser using the :mod:`cgitb` module.
|
||||
If you haven't done so already, just add the lines::
|
||||
|
||||
import cgitb
|
||||
cgitb.enable()
|
||||
|
||||
to the top of your script. Then try running it again; when a problem occurs,
|
||||
you should see a detailed report that will likely make apparent the cause of the
|
||||
crash.
|
||||
|
||||
If you suspect that there may be a problem in importing the :mod:`cgitb` module,
|
||||
you can use an even more robust approach (which only uses built-in modules)::
|
||||
|
||||
import sys
|
||||
sys.stderr = sys.stdout
|
||||
print("Content-Type: text/plain")
|
||||
print()
|
||||
...your code here...
|
||||
|
||||
This relies on the Python interpreter to print the traceback. The content type
|
||||
of the output is set to plain text, which disables all HTML processing. If your
|
||||
script works, the raw HTML will be displayed by your client. If it raises an
|
||||
exception, most likely after the first two lines have been printed, a traceback
|
||||
will be displayed. Because no HTML interpretation is going on, the traceback
|
||||
will be readable.
|
||||
|
||||
|
||||
Common problems and solutions
|
||||
-----------------------------
|
||||
|
||||
* Most HTTP servers buffer the output from CGI scripts until the script is
|
||||
completed. This means that it is not possible to display a progress report on
|
||||
the client's display while the script is running.
|
||||
|
||||
* Check the installation instructions above.
|
||||
|
||||
* Check the HTTP server's log files. (``tail -f logfile`` in a separate window
|
||||
may be useful!)
|
||||
|
||||
* Always check a script for syntax errors first, by doing something like
|
||||
``python script.py``.
|
||||
|
||||
* If your script does not have any syntax errors, try adding ``import cgitb;
|
||||
cgitb.enable()`` to the top of the script.
|
||||
|
||||
* When invoking external programs, make sure they can be found. Usually, this
|
||||
means using absolute path names --- :envvar:`PATH` is usually not set to a very
|
||||
useful value in a CGI script.
|
||||
|
||||
* When reading or writing external files, make sure they can be read or written
|
||||
by the userid under which your CGI script will be running: this is typically the
|
||||
userid under which the web server is running, or some explicitly specified
|
||||
userid for a web server's ``suexec`` feature.
|
||||
|
||||
* Don't try to give a CGI script a set-uid mode. This doesn't work on most
|
||||
systems, and is a security liability as well.
|
||||
|
||||
.. rubric:: Footnotes
|
||||
|
||||
.. [#] Note that some recent versions of the HTML specification do state what
|
||||
order the field values should be supplied in, but knowing whether a request
|
||||
was received from a conforming browser, or even from a browser at all, is
|
||||
tedious and error-prone.
|
|
@ -1,89 +0,0 @@
|
|||
:mod:`cgitb` --- Traceback manager for CGI scripts
|
||||
==================================================
|
||||
|
||||
.. module:: cgitb
|
||||
:synopsis: Configurable traceback handler for CGI scripts.
|
||||
:deprecated:
|
||||
|
||||
.. moduleauthor:: Ka-Ping Yee <ping@lfw.org>
|
||||
.. sectionauthor:: Fred L. Drake, Jr. <fdrake@acm.org>
|
||||
|
||||
**Source code:** :source:`Lib/cgitb.py`
|
||||
|
||||
.. index::
|
||||
single: CGI; exceptions
|
||||
single: CGI; tracebacks
|
||||
single: exceptions; in CGI scripts
|
||||
single: tracebacks; in CGI scripts
|
||||
|
||||
.. deprecated-removed:: 3.11 3.13
|
||||
The :mod:`cgitb` module is deprecated
|
||||
(see :pep:`PEP 594 <594#cgitb>` for details).
|
||||
|
||||
--------------
|
||||
|
||||
The :mod:`cgitb` module provides a special exception handler for Python scripts.
|
||||
(Its name is a bit misleading. It was originally designed to display extensive
|
||||
traceback information in HTML for CGI scripts. It was later generalized to also
|
||||
display this information in plain text.) After this module is activated, if an
|
||||
uncaught exception occurs, a detailed, formatted report will be displayed. The
|
||||
report includes a traceback showing excerpts of the source code for each level,
|
||||
as well as the values of the arguments and local variables to currently running
|
||||
functions, to help you debug the problem. Optionally, you can save this
|
||||
information to a file instead of sending it to the browser.
|
||||
|
||||
To enable this feature, simply add this to the top of your CGI script::
|
||||
|
||||
import cgitb
|
||||
cgitb.enable()
|
||||
|
||||
The options to the :func:`enable` function control whether the report is
|
||||
displayed in the browser and whether the report is logged to a file for later
|
||||
analysis.
|
||||
|
||||
|
||||
.. function:: enable(display=1, logdir=None, context=5, format="html")
|
||||
|
||||
.. index:: single: excepthook() (in module sys)
|
||||
|
||||
This function causes the :mod:`cgitb` module to take over the interpreter's
|
||||
default handling for exceptions by setting the value of :attr:`sys.excepthook`.
|
||||
|
||||
The optional argument *display* defaults to ``1`` and can be set to ``0`` to
|
||||
suppress sending the traceback to the browser. If the argument *logdir* is
|
||||
present, the traceback reports are written to files. The value of *logdir*
|
||||
should be a directory where these files will be placed. The optional argument
|
||||
*context* is the number of lines of context to display around the current line
|
||||
of source code in the traceback; this defaults to ``5``. If the optional
|
||||
argument *format* is ``"html"``, the output is formatted as HTML. Any other
|
||||
value forces plain text output. The default value is ``"html"``.
|
||||
|
||||
|
||||
.. function:: text(info, context=5)
|
||||
|
||||
This function handles the exception described by *info* (a 3-tuple containing
|
||||
the result of :func:`sys.exc_info`), formatting its traceback as text and
|
||||
returning the result as a string. The optional argument *context* is the
|
||||
number of lines of context to display around the current line of source code
|
||||
in the traceback; this defaults to ``5``.
|
||||
|
||||
|
||||
.. function:: html(info, context=5)
|
||||
|
||||
This function handles the exception described by *info* (a 3-tuple containing
|
||||
the result of :func:`sys.exc_info`), formatting its traceback as HTML and
|
||||
returning the result as a string. The optional argument *context* is the
|
||||
number of lines of context to display around the current line of source code
|
||||
in the traceback; this defaults to ``5``.
|
||||
|
||||
|
||||
.. function:: handler(info=None)
|
||||
|
||||
This function handles an exception using the default settings (that is, show a
|
||||
report in the browser, but don't log to a file). This can be used when you've
|
||||
caught an exception and want to report it using :mod:`cgitb`. The optional
|
||||
*info* argument should be a 3-tuple containing an exception type, exception
|
||||
value, and traceback object, exactly like the tuple returned by
|
||||
:func:`sys.exc_info`. If the *info* argument is not supplied, the current
|
||||
exception is obtained from :func:`sys.exc_info`.
|
||||
|
|
@ -9,7 +9,6 @@ The following modules have specific security considerations:
|
|||
|
||||
* :mod:`base64`: :ref:`base64 security considerations <base64-security>` in
|
||||
:rfc:`4648`
|
||||
* :mod:`cgi`: :ref:`CGI security considerations <cgi-security>`
|
||||
* :mod:`hashlib`: :ref:`all constructors take a "usedforsecurity" keyword-only
|
||||
argument disabling known insecure and blocked algorithms
|
||||
<hashlib-usedforsecurity>`
|
||||
|
|
|
@ -12,8 +12,6 @@ backwards compatibility. They have been superseded by other modules.
|
|||
|
||||
aifc.rst
|
||||
audioop.rst
|
||||
cgi.rst
|
||||
cgitb.rst
|
||||
chunk.rst
|
||||
crypt.rst
|
||||
imghdr.rst
|
||||
|
|
|
@ -1030,7 +1030,7 @@ Module changes
|
|||
|
||||
Lots of improvements and bugfixes were made to Python's extensive standard
|
||||
library; some of the affected modules include :mod:`readline`,
|
||||
:mod:`ConfigParser`, :mod:`cgi`, :mod:`calendar`, :mod:`posix`, :mod:`readline`,
|
||||
:mod:`ConfigParser`, :mod:`!cgi`, :mod:`calendar`, :mod:`posix`, :mod:`readline`,
|
||||
:mod:`xmllib`, :mod:`aifc`, :mod:`chunk, wave`, :mod:`random`, :mod:`shelve`,
|
||||
and :mod:`nntplib`. Consult the CVS logs for the exact patch-by-patch details.
|
||||
|
||||
|
|
|
@ -1805,15 +1805,15 @@ changes, or look through the Subversion logs for all the details.
|
|||
available, instead of restricting itself to protocol 1.
|
||||
(Contributed by W. Barnes.)
|
||||
|
||||
* The :mod:`cgi` module will now read variables from the query string
|
||||
* The :mod:`!cgi` module will now read variables from the query string
|
||||
of an HTTP POST request. This makes it possible to use form actions
|
||||
with URLs that include query strings such as
|
||||
"/cgi-bin/add.py?category=1". (Contributed by Alexandre Fiori and
|
||||
Nubis; :issue:`1817`.)
|
||||
|
||||
The :func:`parse_qs` and :func:`parse_qsl` functions have been
|
||||
relocated from the :mod:`cgi` module to the :mod:`urlparse` module.
|
||||
The versions still available in the :mod:`cgi` module will
|
||||
relocated from the :mod:`!cgi` module to the :mod:`urlparse` module.
|
||||
The versions still available in the :mod:`!cgi` module will
|
||||
trigger :exc:`PendingDeprecationWarning` messages in 2.6
|
||||
(:issue:`600362`).
|
||||
|
||||
|
|
|
@ -1508,7 +1508,7 @@ query parameter separators in :func:`urllib.parse.parse_qs` and
|
|||
:func:`urllib.parse.parse_qsl`. Due to security concerns, and to conform with
|
||||
newer W3C recommendations, this has been changed to allow only a single
|
||||
separator key, with ``&`` as the default. This change also affects
|
||||
:func:`cgi.parse` and :func:`cgi.parse_multipart` as they use the affected
|
||||
:func:`!cgi.parse` and :func:`!cgi.parse_multipart` as they use the affected
|
||||
functions internally. For more details, please see their respective
|
||||
documentation.
|
||||
(Contributed by Adam Goldschmidt, Senthil Kumaran and Ken Jin in :issue:`42967`.)
|
||||
|
|
|
@ -1735,9 +1735,9 @@ Modules
|
|||
+---------------------+---------------------+---------------------+---------------------+---------------------+
|
||||
| :mod:`audioop` | :mod:`crypt` | :mod:`nis` | :mod:`sndhdr` | :mod:`uu` |
|
||||
+---------------------+---------------------+---------------------+---------------------+---------------------+
|
||||
| :mod:`cgi` | :mod:`imghdr` | :mod:`nntplib` | :mod:`spwd` | :mod:`xdrlib` |
|
||||
| :mod:`!cgi` | :mod:`imghdr` | :mod:`nntplib` | :mod:`spwd` | :mod:`xdrlib` |
|
||||
+---------------------+---------------------+---------------------+---------------------+---------------------+
|
||||
| :mod:`cgitb` | :mod:`mailcap` | :mod:`ossaudiodev` | :mod:`sunau` | |
|
||||
| :mod:`!cgitb` | :mod:`mailcap` | :mod:`ossaudiodev` | :mod:`sunau` | |
|
||||
+---------------------+---------------------+---------------------+---------------------+---------------------+
|
||||
|
||||
(Contributed by Brett Cannon in :issue:`47061` and Victor Stinner in
|
||||
|
|
|
@ -805,8 +805,8 @@ Modules (see :pep:`594`):
|
|||
|
||||
* :mod:`aifc`
|
||||
* :mod:`audioop`
|
||||
* :mod:`cgi`
|
||||
* :mod:`cgitb`
|
||||
* :mod:`!cgi`
|
||||
* :mod:`!cgitb`
|
||||
* :mod:`chunk`
|
||||
* :mod:`crypt`
|
||||
* :mod:`imghdr`
|
||||
|
|
|
@ -118,6 +118,36 @@ Removed
|
|||
* Remove support for using :class:`pathlib.Path` objects as context managers.
|
||||
This functionality was deprecated and made a no-op in Python 3.9.
|
||||
|
||||
* :pep:`594`: Remove the :mod:`!cgi`` and :mod:`!cgitb` modules,
|
||||
deprecated in Python 3.11.
|
||||
|
||||
* ``cgi.FieldStorage`` can typically be replaced with
|
||||
:func:`urllib.parse.parse_qsl` for ``GET`` and ``HEAD`` requests, and the
|
||||
:mod:`email.message` module or `multipart
|
||||
<https://pypi.org/project/multipart/>`__ PyPI project for ``POST`` and
|
||||
``PUT``.
|
||||
|
||||
* ``cgi.parse()`` can be replaced by calling :func:`urllib.parse.parse_qs`
|
||||
directly on the desired query string, except for ``multipart/form-data``
|
||||
input, which can be handled as described for ``cgi.parse_multipart()``.
|
||||
|
||||
* ``cgi.parse_multipart()`` can be replaced with the functionality in the
|
||||
:mod:`email` package (e.g. :class:`email.message.EmailMessage` and
|
||||
:class:`email.message.Message`) which implements the same MIME RFCs, or
|
||||
with the `multipart <https://pypi.org/project/multipart/>`__ PyPI project.
|
||||
|
||||
* ``cgi.parse_header()`` can be replaced with the functionality in the
|
||||
:mod:`email` package, which implements the same MIME RFCs. For example,
|
||||
with :class:`email.message.EmailMessage`::
|
||||
|
||||
from email.message import EmailMessage
|
||||
msg = EmailMessage()
|
||||
msg['content-type'] = 'application/json; charset="utf8"'
|
||||
main, params = msg.get_content_type(), msg['content-type'].params
|
||||
|
||||
(Contributed by Victor Stinner in :gh:`104773`.)
|
||||
|
||||
|
||||
Porting to Python 3.13
|
||||
======================
|
||||
|
||||
|
|
|
@ -2371,11 +2371,11 @@ Changes in the Python API
|
|||
3.3.3.
|
||||
|
||||
* The :attr:`~cgi.FieldStorage.file` attribute is now automatically closed when
|
||||
the creating :class:`cgi.FieldStorage` instance is garbage collected. If you
|
||||
were pulling the file object out separately from the :class:`cgi.FieldStorage`
|
||||
the creating :class:`!cgi.FieldStorage` instance is garbage collected. If you
|
||||
were pulling the file object out separately from the :class:`!cgi.FieldStorage`
|
||||
instance and not keeping the instance alive, then you should either store the
|
||||
entire :class:`cgi.FieldStorage` instance or read the contents of the file
|
||||
before the :class:`cgi.FieldStorage` instance is garbage collected.
|
||||
entire :class:`!cgi.FieldStorage` instance or read the contents of the file
|
||||
before the :class:`!cgi.FieldStorage` instance is garbage collected.
|
||||
|
||||
* Calling ``read`` or ``write`` on a closed SSL socket now raises an
|
||||
informative :exc:`ValueError` rather than the previous more mysterious
|
||||
|
|
|
@ -2185,7 +2185,7 @@ Changes in the Python API
|
|||
|
||||
* The following modules have had missing APIs added to their :attr:`__all__`
|
||||
attributes to match the documented APIs:
|
||||
:mod:`calendar`, :mod:`cgi`, :mod:`csv`,
|
||||
:mod:`calendar`, :mod:`!cgi`, :mod:`csv`,
|
||||
:mod:`~xml.etree.ElementTree`, :mod:`enum`,
|
||||
:mod:`fileinput`, :mod:`ftplib`, :mod:`logging`, :mod:`mailbox`,
|
||||
:mod:`mimetypes`, :mod:`optparse`, :mod:`plistlib`, :mod:`smtpd`,
|
||||
|
@ -2455,7 +2455,7 @@ query parameter separators in :func:`urllib.parse.parse_qs` and
|
|||
:func:`urllib.parse.parse_qsl`. Due to security concerns, and to conform with
|
||||
newer W3C recommendations, this has been changed to allow only a single
|
||||
separator key, with ``&`` as the default. This change also affects
|
||||
:func:`cgi.parse` and :func:`cgi.parse_multipart` as they use the affected
|
||||
:func:`!cgi.parse` and :func:`!cgi.parse_multipart` as they use the affected
|
||||
functions internally. For more details, please see their respective
|
||||
documentation.
|
||||
(Contributed by Adam Goldschmidt, Senthil Kumaran and Ken Jin in :issue:`42967`.)
|
||||
|
|
|
@ -2567,7 +2567,7 @@ query parameter separators in :func:`urllib.parse.parse_qs` and
|
|||
:func:`urllib.parse.parse_qsl`. Due to security concerns, and to conform with
|
||||
newer W3C recommendations, this has been changed to allow only a single
|
||||
separator key, with ``&`` as the default. This change also affects
|
||||
:func:`cgi.parse` and :func:`cgi.parse_multipart` as they use the affected
|
||||
:func:`!cgi.parse` and :func:`!cgi.parse_multipart` as they use the affected
|
||||
functions internally. For more details, please see their respective
|
||||
documentation.
|
||||
(Contributed by Adam Goldschmidt, Senthil Kumaran and Ken Jin in :issue:`42967`.)
|
||||
|
|
|
@ -1774,7 +1774,7 @@ The following features and APIs have been removed from Python 3.8:
|
|||
to help eliminate confusion as to what Python interpreter the ``pyvenv``
|
||||
script is tied to. (Contributed by Brett Cannon in :issue:`25427`.)
|
||||
|
||||
* ``parse_qs``, ``parse_qsl``, and ``escape`` are removed from the :mod:`cgi`
|
||||
* ``parse_qs``, ``parse_qsl``, and ``escape`` are removed from the :mod:`!cgi`
|
||||
module. They are deprecated in Python 3.2 or older. They should be imported
|
||||
from the ``urllib.parse`` and ``html`` modules instead.
|
||||
|
||||
|
@ -2251,7 +2251,7 @@ query parameter separators in :func:`urllib.parse.parse_qs` and
|
|||
:func:`urllib.parse.parse_qsl`. Due to security concerns, and to conform with
|
||||
newer W3C recommendations, this has been changed to allow only a single
|
||||
separator key, with ``&`` as the default. This change also affects
|
||||
:func:`cgi.parse` and :func:`cgi.parse_multipart` as they use the affected
|
||||
:func:`!cgi.parse` and :func:`!cgi.parse_multipart` as they use the affected
|
||||
functions internally. For more details, please see their respective
|
||||
documentation.
|
||||
(Contributed by Adam Goldschmidt, Senthil Kumaran and Ken Jin in :issue:`42967`.)
|
||||
|
|
|
@ -1559,7 +1559,7 @@ query parameter separators in :func:`urllib.parse.parse_qs` and
|
|||
:func:`urllib.parse.parse_qsl`. Due to security concerns, and to conform with
|
||||
newer W3C recommendations, this has been changed to allow only a single
|
||||
separator key, with ``&`` as the default. This change also affects
|
||||
:func:`cgi.parse` and :func:`cgi.parse_multipart` as they use the affected
|
||||
:func:`!cgi.parse` and :func:`!cgi.parse_multipart` as they use the affected
|
||||
functions internally. For more details, please see their respective
|
||||
documentation.
|
||||
(Contributed by Adam Goldschmidt, Senthil Kumaran and Ken Jin in :issue:`42967`.)
|
||||
|
|
1012
Lib/cgi.py
1012
Lib/cgi.py
File diff suppressed because it is too large
Load Diff
332
Lib/cgitb.py
332
Lib/cgitb.py
|
@ -1,332 +0,0 @@
|
|||
"""More comprehensive traceback formatting for Python scripts.
|
||||
|
||||
To enable this module, do:
|
||||
|
||||
import cgitb; cgitb.enable()
|
||||
|
||||
at the top of your script. The optional arguments to enable() are:
|
||||
|
||||
display - if true, tracebacks are displayed in the web browser
|
||||
logdir - if set, tracebacks are written to files in this directory
|
||||
context - number of lines of source code to show for each stack frame
|
||||
format - 'text' or 'html' controls the output format
|
||||
|
||||
By default, tracebacks are displayed but not saved, the context is 5 lines
|
||||
and the output format is 'html' (for backwards compatibility with the
|
||||
original use of this module)
|
||||
|
||||
Alternatively, if you have caught an exception and want cgitb to display it
|
||||
for you, call cgitb.handler(). The optional argument to handler() is a
|
||||
3-item tuple (etype, evalue, etb) just like the value of sys.exc_info().
|
||||
The default handler displays output as HTML.
|
||||
|
||||
"""
|
||||
import inspect
|
||||
import keyword
|
||||
import linecache
|
||||
import os
|
||||
import pydoc
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import tokenize
|
||||
import traceback
|
||||
import warnings
|
||||
from html import escape as html_escape
|
||||
|
||||
warnings._deprecated(__name__, remove=(3, 13))
|
||||
|
||||
|
||||
def reset():
|
||||
"""Return a string that resets the CGI and browser to a known state."""
|
||||
return '''<!--: spam
|
||||
Content-Type: text/html
|
||||
|
||||
<body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> -->
|
||||
<body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> --> -->
|
||||
</font> </font> </font> </script> </object> </blockquote> </pre>
|
||||
</table> </table> </table> </table> </table> </font> </font> </font>'''
|
||||
|
||||
__UNDEF__ = [] # a special sentinel object
|
||||
def small(text):
|
||||
if text:
|
||||
return '<small>' + text + '</small>'
|
||||
else:
|
||||
return ''
|
||||
|
||||
def strong(text):
|
||||
if text:
|
||||
return '<strong>' + text + '</strong>'
|
||||
else:
|
||||
return ''
|
||||
|
||||
def grey(text):
|
||||
if text:
|
||||
return '<font color="#909090">' + text + '</font>'
|
||||
else:
|
||||
return ''
|
||||
|
||||
def lookup(name, frame, locals):
|
||||
"""Find the value for a given name in the given environment."""
|
||||
if name in locals:
|
||||
return 'local', locals[name]
|
||||
if name in frame.f_globals:
|
||||
return 'global', frame.f_globals[name]
|
||||
if '__builtins__' in frame.f_globals:
|
||||
builtins = frame.f_globals['__builtins__']
|
||||
if isinstance(builtins, dict):
|
||||
if name in builtins:
|
||||
return 'builtin', builtins[name]
|
||||
else:
|
||||
if hasattr(builtins, name):
|
||||
return 'builtin', getattr(builtins, name)
|
||||
return None, __UNDEF__
|
||||
|
||||
def scanvars(reader, frame, locals):
|
||||
"""Scan one logical line of Python and look up values of variables used."""
|
||||
vars, lasttoken, parent, prefix, value = [], None, None, '', __UNDEF__
|
||||
for ttype, token, start, end, line in tokenize.generate_tokens(reader):
|
||||
if ttype == tokenize.NEWLINE: break
|
||||
if ttype == tokenize.NAME and token not in keyword.kwlist:
|
||||
if lasttoken == '.':
|
||||
if parent is not __UNDEF__:
|
||||
value = getattr(parent, token, __UNDEF__)
|
||||
vars.append((prefix + token, prefix, value))
|
||||
else:
|
||||
where, value = lookup(token, frame, locals)
|
||||
vars.append((token, where, value))
|
||||
elif token == '.':
|
||||
prefix += lasttoken + '.'
|
||||
parent = value
|
||||
else:
|
||||
parent, prefix = None, ''
|
||||
lasttoken = token
|
||||
return vars
|
||||
|
||||
def html(einfo, context=5):
|
||||
"""Return a nice HTML document describing a given traceback."""
|
||||
etype, evalue, etb = einfo
|
||||
if isinstance(etype, type):
|
||||
etype = etype.__name__
|
||||
pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
|
||||
date = time.ctime(time.time())
|
||||
head = f'''
|
||||
<body bgcolor="#f0f0f8">
|
||||
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
|
||||
<tr bgcolor="#6622aa">
|
||||
<td valign=bottom> <br>
|
||||
<font color="#ffffff" face="helvetica, arial"> <br>
|
||||
<big><big><strong>{html_escape(str(etype))}</strong></big></big></font></td>
|
||||
<td align=right valign=bottom>
|
||||
<font color="#ffffff" face="helvetica, arial">{pyver}<br>{date}</font></td>
|
||||
</tr></table>
|
||||
<p>A problem occurred in a Python script. Here is the sequence of
|
||||
function calls leading up to the error, in the order they occurred.</p>'''
|
||||
|
||||
indent = '<tt>' + small(' ' * 5) + ' </tt>'
|
||||
frames = []
|
||||
records = inspect.getinnerframes(etb, context)
|
||||
for frame, file, lnum, func, lines, index in records:
|
||||
if file:
|
||||
file = os.path.abspath(file)
|
||||
link = '<a href="file://%s">%s</a>' % (file, pydoc.html.escape(file))
|
||||
else:
|
||||
file = link = '?'
|
||||
args, varargs, varkw, locals = inspect.getargvalues(frame)
|
||||
call = ''
|
||||
if func != '?':
|
||||
call = 'in ' + strong(pydoc.html.escape(func))
|
||||
if func != "<module>":
|
||||
call += inspect.formatargvalues(args, varargs, varkw, locals,
|
||||
formatvalue=lambda value: '=' + pydoc.html.repr(value))
|
||||
|
||||
highlight = {}
|
||||
def reader(lnum=[lnum]):
|
||||
highlight[lnum[0]] = 1
|
||||
try: return linecache.getline(file, lnum[0])
|
||||
finally: lnum[0] += 1
|
||||
vars = scanvars(reader, frame, locals)
|
||||
|
||||
rows = ['<tr><td bgcolor="#d8bbff">%s%s %s</td></tr>' %
|
||||
('<big> </big>', link, call)]
|
||||
if index is not None:
|
||||
i = lnum - index
|
||||
for line in lines:
|
||||
num = small(' ' * (5-len(str(i))) + str(i)) + ' '
|
||||
if i in highlight:
|
||||
line = '<tt>=>%s%s</tt>' % (num, pydoc.html.preformat(line))
|
||||
rows.append('<tr><td bgcolor="#ffccee">%s</td></tr>' % line)
|
||||
else:
|
||||
line = '<tt> %s%s</tt>' % (num, pydoc.html.preformat(line))
|
||||
rows.append('<tr><td>%s</td></tr>' % grey(line))
|
||||
i += 1
|
||||
|
||||
done, dump = {}, []
|
||||
for name, where, value in vars:
|
||||
if name in done: continue
|
||||
done[name] = 1
|
||||
if value is not __UNDEF__:
|
||||
if where in ('global', 'builtin'):
|
||||
name = ('<em>%s</em> ' % where) + strong(name)
|
||||
elif where == 'local':
|
||||
name = strong(name)
|
||||
else:
|
||||
name = where + strong(name.split('.')[-1])
|
||||
dump.append('%s = %s' % (name, pydoc.html.repr(value)))
|
||||
else:
|
||||
dump.append(name + ' <em>undefined</em>')
|
||||
|
||||
rows.append('<tr><td>%s</td></tr>' % small(grey(', '.join(dump))))
|
||||
frames.append('''
|
||||
<table width="100%%" cellspacing=0 cellpadding=0 border=0>
|
||||
%s</table>''' % '\n'.join(rows))
|
||||
|
||||
exception = ['<p>%s: %s' % (strong(pydoc.html.escape(str(etype))),
|
||||
pydoc.html.escape(str(evalue)))]
|
||||
for name in dir(evalue):
|
||||
if name[:1] == '_': continue
|
||||
value = pydoc.html.repr(getattr(evalue, name))
|
||||
exception.append('\n<br>%s%s =\n%s' % (indent, name, value))
|
||||
|
||||
return head + ''.join(frames) + ''.join(exception) + '''
|
||||
|
||||
|
||||
<!-- The above is a description of an error in a Python program, formatted
|
||||
for a web browser because the 'cgitb' module was enabled. In case you
|
||||
are not reading this in a web browser, here is the original traceback:
|
||||
|
||||
%s
|
||||
-->
|
||||
''' % pydoc.html.escape(
|
||||
''.join(traceback.format_exception(etype, evalue, etb)))
|
||||
|
||||
def text(einfo, context=5):
|
||||
"""Return a plain text document describing a given traceback."""
|
||||
etype, evalue, etb = einfo
|
||||
if isinstance(etype, type):
|
||||
etype = etype.__name__
|
||||
pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
|
||||
date = time.ctime(time.time())
|
||||
head = "%s\n%s\n%s\n" % (str(etype), pyver, date) + '''
|
||||
A problem occurred in a Python script. Here is the sequence of
|
||||
function calls leading up to the error, in the order they occurred.
|
||||
'''
|
||||
|
||||
frames = []
|
||||
records = inspect.getinnerframes(etb, context)
|
||||
for frame, file, lnum, func, lines, index in records:
|
||||
file = file and os.path.abspath(file) or '?'
|
||||
args, varargs, varkw, locals = inspect.getargvalues(frame)
|
||||
call = ''
|
||||
if func != '?':
|
||||
call = 'in ' + func
|
||||
if func != "<module>":
|
||||
call += inspect.formatargvalues(args, varargs, varkw, locals,
|
||||
formatvalue=lambda value: '=' + pydoc.text.repr(value))
|
||||
|
||||
highlight = {}
|
||||
def reader(lnum=[lnum]):
|
||||
highlight[lnum[0]] = 1
|
||||
try: return linecache.getline(file, lnum[0])
|
||||
finally: lnum[0] += 1
|
||||
vars = scanvars(reader, frame, locals)
|
||||
|
||||
rows = [' %s %s' % (file, call)]
|
||||
if index is not None:
|
||||
i = lnum - index
|
||||
for line in lines:
|
||||
num = '%5d ' % i
|
||||
rows.append(num+line.rstrip())
|
||||
i += 1
|
||||
|
||||
done, dump = {}, []
|
||||
for name, where, value in vars:
|
||||
if name in done: continue
|
||||
done[name] = 1
|
||||
if value is not __UNDEF__:
|
||||
if where == 'global': name = 'global ' + name
|
||||
elif where != 'local': name = where + name.split('.')[-1]
|
||||
dump.append('%s = %s' % (name, pydoc.text.repr(value)))
|
||||
else:
|
||||
dump.append(name + ' undefined')
|
||||
|
||||
rows.append('\n'.join(dump))
|
||||
frames.append('\n%s\n' % '\n'.join(rows))
|
||||
|
||||
exception = ['%s: %s' % (str(etype), str(evalue))]
|
||||
for name in dir(evalue):
|
||||
value = pydoc.text.repr(getattr(evalue, name))
|
||||
exception.append('\n%s%s = %s' % (" "*4, name, value))
|
||||
|
||||
return head + ''.join(frames) + ''.join(exception) + '''
|
||||
|
||||
The above is a description of an error in a Python program. Here is
|
||||
the original traceback:
|
||||
|
||||
%s
|
||||
''' % ''.join(traceback.format_exception(etype, evalue, etb))
|
||||
|
||||
class Hook:
|
||||
"""A hook to replace sys.excepthook that shows tracebacks in HTML."""
|
||||
|
||||
def __init__(self, display=1, logdir=None, context=5, file=None,
|
||||
format="html"):
|
||||
self.display = display # send tracebacks to browser if true
|
||||
self.logdir = logdir # log tracebacks to files if not None
|
||||
self.context = context # number of source code lines per frame
|
||||
self.file = file or sys.stdout # place to send the output
|
||||
self.format = format
|
||||
|
||||
def __call__(self, etype, evalue, etb):
|
||||
self.handle((etype, evalue, etb))
|
||||
|
||||
def handle(self, info=None):
|
||||
info = info or sys.exc_info()
|
||||
if self.format == "html":
|
||||
self.file.write(reset())
|
||||
|
||||
formatter = (self.format=="html") and html or text
|
||||
plain = False
|
||||
try:
|
||||
doc = formatter(info, self.context)
|
||||
except: # just in case something goes wrong
|
||||
doc = ''.join(traceback.format_exception(*info))
|
||||
plain = True
|
||||
|
||||
if self.display:
|
||||
if plain:
|
||||
doc = pydoc.html.escape(doc)
|
||||
self.file.write('<pre>' + doc + '</pre>\n')
|
||||
else:
|
||||
self.file.write(doc + '\n')
|
||||
else:
|
||||
self.file.write('<p>A problem occurred in a Python script.\n')
|
||||
|
||||
if self.logdir is not None:
|
||||
suffix = ['.txt', '.html'][self.format=="html"]
|
||||
(fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir)
|
||||
|
||||
try:
|
||||
with os.fdopen(fd, 'w') as file:
|
||||
file.write(doc)
|
||||
msg = '%s contains the description of this error.' % path
|
||||
except:
|
||||
msg = 'Tried to save traceback to %s, but failed.' % path
|
||||
|
||||
if self.format == 'html':
|
||||
self.file.write('<p>%s</p>\n' % msg)
|
||||
else:
|
||||
self.file.write(msg + '\n')
|
||||
try:
|
||||
self.file.flush()
|
||||
except: pass
|
||||
|
||||
handler = Hook().handle
|
||||
def enable(display=1, logdir=None, context=5, format="html"):
|
||||
"""Install an exception handler that formats tracebacks as HTML.
|
||||
|
||||
The optional argument 'display' can be set to 0 to suppress sending the
|
||||
traceback to the browser, and 'logdir' can be set to a directory to cause
|
||||
tracebacks to be written to files there."""
|
||||
sys.excepthook = Hook(display=display, logdir=logdir,
|
||||
context=context, format=format)
|
|
@ -1,641 +0,0 @@
|
|||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from collections import namedtuple
|
||||
from io import StringIO, BytesIO
|
||||
from test import support
|
||||
from test.support import warnings_helper
|
||||
|
||||
cgi = warnings_helper.import_deprecated("cgi")
|
||||
|
||||
|
||||
class HackedSysModule:
|
||||
# The regression test will have real values in sys.argv, which
|
||||
# will completely confuse the test of the cgi module
|
||||
argv = []
|
||||
stdin = sys.stdin
|
||||
|
||||
cgi.sys = HackedSysModule()
|
||||
|
||||
class ComparableException:
|
||||
def __init__(self, err):
|
||||
self.err = err
|
||||
|
||||
def __str__(self):
|
||||
return str(self.err)
|
||||
|
||||
def __eq__(self, anExc):
|
||||
if not isinstance(anExc, Exception):
|
||||
return NotImplemented
|
||||
return (self.err.__class__ == anExc.__class__ and
|
||||
self.err.args == anExc.args)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.err, attr)
|
||||
|
||||
def do_test(buf, method):
|
||||
env = {}
|
||||
if method == "GET":
|
||||
fp = None
|
||||
env['REQUEST_METHOD'] = 'GET'
|
||||
env['QUERY_STRING'] = buf
|
||||
elif method == "POST":
|
||||
fp = BytesIO(buf.encode('latin-1')) # FieldStorage expects bytes
|
||||
env['REQUEST_METHOD'] = 'POST'
|
||||
env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
|
||||
env['CONTENT_LENGTH'] = str(len(buf))
|
||||
else:
|
||||
raise ValueError("unknown method: %s" % method)
|
||||
try:
|
||||
return cgi.parse(fp, env, strict_parsing=1)
|
||||
except Exception as err:
|
||||
return ComparableException(err)
|
||||
|
||||
parse_strict_test_cases = [
|
||||
("", {}),
|
||||
("&", ValueError("bad query field: ''")),
|
||||
("&&", ValueError("bad query field: ''")),
|
||||
# Should the next few really be valid?
|
||||
("=", {}),
|
||||
("=&=", {}),
|
||||
# This rest seem to make sense
|
||||
("=a", {'': ['a']}),
|
||||
("&=a", ValueError("bad query field: ''")),
|
||||
("=a&", ValueError("bad query field: ''")),
|
||||
("=&a", ValueError("bad query field: 'a'")),
|
||||
("b=a", {'b': ['a']}),
|
||||
("b+=a", {'b ': ['a']}),
|
||||
("a=b=a", {'a': ['b=a']}),
|
||||
("a=+b=a", {'a': [' b=a']}),
|
||||
("&b=a", ValueError("bad query field: ''")),
|
||||
("b&=a", ValueError("bad query field: 'b'")),
|
||||
("a=a+b&b=b+c", {'a': ['a b'], 'b': ['b c']}),
|
||||
("a=a+b&a=b+a", {'a': ['a b', 'b a']}),
|
||||
("x=1&y=2.0&z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}),
|
||||
("Hbc5161168c542333633315dee1182227:key_store_seqid=400006&cuyer=r&view=bustomer&order_id=0bb2e248638833d48cb7fed300000f1b&expire=964546263&lobale=en-US&kid=130003.300038&ss=env",
|
||||
{'Hbc5161168c542333633315dee1182227:key_store_seqid': ['400006'],
|
||||
'cuyer': ['r'],
|
||||
'expire': ['964546263'],
|
||||
'kid': ['130003.300038'],
|
||||
'lobale': ['en-US'],
|
||||
'order_id': ['0bb2e248638833d48cb7fed300000f1b'],
|
||||
'ss': ['env'],
|
||||
'view': ['bustomer'],
|
||||
}),
|
||||
|
||||
("group_id=5470&set=custom&_assigned_to=31392&_status=1&_category=100&SUBMIT=Browse",
|
||||
{'SUBMIT': ['Browse'],
|
||||
'_assigned_to': ['31392'],
|
||||
'_category': ['100'],
|
||||
'_status': ['1'],
|
||||
'group_id': ['5470'],
|
||||
'set': ['custom'],
|
||||
})
|
||||
]
|
||||
|
||||
def norm(seq):
|
||||
return sorted(seq, key=repr)
|
||||
|
||||
def first_elts(list):
|
||||
return [p[0] for p in list]
|
||||
|
||||
def first_second_elts(list):
|
||||
return [(p[0], p[1][0]) for p in list]
|
||||
|
||||
def gen_result(data, environ):
|
||||
encoding = 'latin-1'
|
||||
fake_stdin = BytesIO(data.encode(encoding))
|
||||
fake_stdin.seek(0)
|
||||
form = cgi.FieldStorage(fp=fake_stdin, environ=environ, encoding=encoding)
|
||||
|
||||
result = {}
|
||||
for k, v in dict(form).items():
|
||||
result[k] = isinstance(v, list) and form.getlist(k) or v.value
|
||||
|
||||
return result
|
||||
|
||||
class CgiTests(unittest.TestCase):
|
||||
|
||||
def test_parse_multipart(self):
|
||||
fp = BytesIO(POSTDATA.encode('latin1'))
|
||||
env = {'boundary': BOUNDARY.encode('latin1'),
|
||||
'CONTENT-LENGTH': '558'}
|
||||
result = cgi.parse_multipart(fp, env)
|
||||
expected = {'submit': [' Add '], 'id': ['1234'],
|
||||
'file': [b'Testing 123.\n'], 'title': ['']}
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_parse_multipart_without_content_length(self):
|
||||
POSTDATA = '''--JfISa01
|
||||
Content-Disposition: form-data; name="submit-name"
|
||||
|
||||
just a string
|
||||
|
||||
--JfISa01--
|
||||
'''
|
||||
fp = BytesIO(POSTDATA.encode('latin1'))
|
||||
env = {'boundary': 'JfISa01'.encode('latin1')}
|
||||
result = cgi.parse_multipart(fp, env)
|
||||
expected = {'submit-name': ['just a string\n']}
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_parse_multipart_invalid_encoding(self):
|
||||
BOUNDARY = "JfISa01"
|
||||
POSTDATA = """--JfISa01
|
||||
Content-Disposition: form-data; name="submit-name"
|
||||
Content-Length: 3
|
||||
|
||||
\u2603
|
||||
--JfISa01"""
|
||||
fp = BytesIO(POSTDATA.encode('utf8'))
|
||||
env = {'boundary': BOUNDARY.encode('latin1'),
|
||||
'CONTENT-LENGTH': str(len(POSTDATA.encode('utf8')))}
|
||||
result = cgi.parse_multipart(fp, env, encoding="ascii",
|
||||
errors="surrogateescape")
|
||||
expected = {'submit-name': ["\udce2\udc98\udc83"]}
|
||||
self.assertEqual(result, expected)
|
||||
self.assertEqual("\u2603".encode('utf8'),
|
||||
result["submit-name"][0].encode('utf8', 'surrogateescape'))
|
||||
|
||||
def test_fieldstorage_properties(self):
|
||||
fs = cgi.FieldStorage()
|
||||
self.assertFalse(fs)
|
||||
self.assertIn("FieldStorage", repr(fs))
|
||||
self.assertEqual(list(fs), list(fs.keys()))
|
||||
fs.list.append(namedtuple('MockFieldStorage', 'name')('fieldvalue'))
|
||||
self.assertTrue(fs)
|
||||
|
||||
def test_fieldstorage_invalid(self):
|
||||
self.assertRaises(TypeError, cgi.FieldStorage, "not-a-file-obj",
|
||||
environ={"REQUEST_METHOD":"PUT"})
|
||||
self.assertRaises(TypeError, cgi.FieldStorage, "foo", "bar")
|
||||
fs = cgi.FieldStorage(headers={'content-type':'text/plain'})
|
||||
self.assertRaises(TypeError, bool, fs)
|
||||
|
||||
def test_strict(self):
|
||||
for orig, expect in parse_strict_test_cases:
|
||||
# Test basic parsing
|
||||
d = do_test(orig, "GET")
|
||||
self.assertEqual(d, expect, "Error parsing %s method GET" % repr(orig))
|
||||
d = do_test(orig, "POST")
|
||||
self.assertEqual(d, expect, "Error parsing %s method POST" % repr(orig))
|
||||
|
||||
env = {'QUERY_STRING': orig}
|
||||
fs = cgi.FieldStorage(environ=env)
|
||||
if isinstance(expect, dict):
|
||||
# test dict interface
|
||||
self.assertEqual(len(expect), len(fs))
|
||||
self.assertCountEqual(expect.keys(), fs.keys())
|
||||
##self.assertEqual(norm(expect.values()), norm(fs.values()))
|
||||
##self.assertEqual(norm(expect.items()), norm(fs.items()))
|
||||
self.assertEqual(fs.getvalue("nonexistent field", "default"), "default")
|
||||
# test individual fields
|
||||
for key in expect.keys():
|
||||
expect_val = expect[key]
|
||||
self.assertIn(key, fs)
|
||||
if len(expect_val) > 1:
|
||||
self.assertEqual(fs.getvalue(key), expect_val)
|
||||
else:
|
||||
self.assertEqual(fs.getvalue(key), expect_val[0])
|
||||
|
||||
def test_separator(self):
|
||||
parse_semicolon = [
|
||||
("x=1;y=2.0", {'x': ['1'], 'y': ['2.0']}),
|
||||
("x=1;y=2.0;z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}),
|
||||
(";", ValueError("bad query field: ''")),
|
||||
(";;", ValueError("bad query field: ''")),
|
||||
("=;a", ValueError("bad query field: 'a'")),
|
||||
(";b=a", ValueError("bad query field: ''")),
|
||||
("b;=a", ValueError("bad query field: 'b'")),
|
||||
("a=a+b;b=b+c", {'a': ['a b'], 'b': ['b c']}),
|
||||
("a=a+b;a=b+a", {'a': ['a b', 'b a']}),
|
||||
]
|
||||
for orig, expect in parse_semicolon:
|
||||
env = {'QUERY_STRING': orig}
|
||||
fs = cgi.FieldStorage(separator=';', environ=env)
|
||||
if isinstance(expect, dict):
|
||||
for key in expect.keys():
|
||||
expect_val = expect[key]
|
||||
self.assertIn(key, fs)
|
||||
if len(expect_val) > 1:
|
||||
self.assertEqual(fs.getvalue(key), expect_val)
|
||||
else:
|
||||
self.assertEqual(fs.getvalue(key), expect_val[0])
|
||||
|
||||
@warnings_helper.ignore_warnings(category=DeprecationWarning)
|
||||
def test_log(self):
|
||||
cgi.log("Testing")
|
||||
|
||||
cgi.logfp = StringIO()
|
||||
cgi.initlog("%s", "Testing initlog 1")
|
||||
cgi.log("%s", "Testing log 2")
|
||||
self.assertEqual(cgi.logfp.getvalue(), "Testing initlog 1\nTesting log 2\n")
|
||||
if os.path.exists(os.devnull):
|
||||
cgi.logfp = None
|
||||
cgi.logfile = os.devnull
|
||||
cgi.initlog("%s", "Testing log 3")
|
||||
self.addCleanup(cgi.closelog)
|
||||
cgi.log("Testing log 4")
|
||||
|
||||
def test_fieldstorage_readline(self):
|
||||
# FieldStorage uses readline, which has the capacity to read all
|
||||
# contents of the input file into memory; we use readline's size argument
|
||||
# to prevent that for files that do not contain any newlines in
|
||||
# non-GET/HEAD requests
|
||||
class TestReadlineFile:
|
||||
def __init__(self, file):
|
||||
self.file = file
|
||||
self.numcalls = 0
|
||||
|
||||
def readline(self, size=None):
|
||||
self.numcalls += 1
|
||||
if size:
|
||||
return self.file.readline(size)
|
||||
else:
|
||||
return self.file.readline()
|
||||
|
||||
def __getattr__(self, name):
|
||||
file = self.__dict__['file']
|
||||
a = getattr(file, name)
|
||||
if not isinstance(a, int):
|
||||
setattr(self, name, a)
|
||||
return a
|
||||
|
||||
f = TestReadlineFile(tempfile.TemporaryFile("wb+"))
|
||||
self.addCleanup(f.close)
|
||||
f.write(b'x' * 256 * 1024)
|
||||
f.seek(0)
|
||||
env = {'REQUEST_METHOD':'PUT'}
|
||||
fs = cgi.FieldStorage(fp=f, environ=env)
|
||||
self.addCleanup(fs.file.close)
|
||||
# if we're not chunking properly, readline is only called twice
|
||||
# (by read_binary); if we are chunking properly, it will be called 5 times
|
||||
# as long as the chunksize is 1 << 16.
|
||||
self.assertGreater(f.numcalls, 2)
|
||||
f.close()
|
||||
|
||||
def test_fieldstorage_multipart(self):
|
||||
#Test basic FieldStorage multipart parsing
|
||||
env = {
|
||||
'REQUEST_METHOD': 'POST',
|
||||
'CONTENT_TYPE': 'multipart/form-data; boundary={}'.format(BOUNDARY),
|
||||
'CONTENT_LENGTH': '558'}
|
||||
fp = BytesIO(POSTDATA.encode('latin-1'))
|
||||
fs = cgi.FieldStorage(fp, environ=env, encoding="latin-1")
|
||||
self.assertEqual(len(fs.list), 4)
|
||||
expect = [{'name':'id', 'filename':None, 'value':'1234'},
|
||||
{'name':'title', 'filename':None, 'value':''},
|
||||
{'name':'file', 'filename':'test.txt', 'value':b'Testing 123.\n'},
|
||||
{'name':'submit', 'filename':None, 'value':' Add '}]
|
||||
for x in range(len(fs.list)):
|
||||
for k, exp in expect[x].items():
|
||||
got = getattr(fs.list[x], k)
|
||||
self.assertEqual(got, exp)
|
||||
|
||||
def test_fieldstorage_multipart_leading_whitespace(self):
|
||||
env = {
|
||||
'REQUEST_METHOD': 'POST',
|
||||
'CONTENT_TYPE': 'multipart/form-data; boundary={}'.format(BOUNDARY),
|
||||
'CONTENT_LENGTH': '560'}
|
||||
# Add some leading whitespace to our post data that will cause the
|
||||
# first line to not be the innerboundary.
|
||||
fp = BytesIO(b"\r\n" + POSTDATA.encode('latin-1'))
|
||||
fs = cgi.FieldStorage(fp, environ=env, encoding="latin-1")
|
||||
self.assertEqual(len(fs.list), 4)
|
||||
expect = [{'name':'id', 'filename':None, 'value':'1234'},
|
||||
{'name':'title', 'filename':None, 'value':''},
|
||||
{'name':'file', 'filename':'test.txt', 'value':b'Testing 123.\n'},
|
||||
{'name':'submit', 'filename':None, 'value':' Add '}]
|
||||
for x in range(len(fs.list)):
|
||||
for k, exp in expect[x].items():
|
||||
got = getattr(fs.list[x], k)
|
||||
self.assertEqual(got, exp)
|
||||
|
||||
def test_fieldstorage_multipart_non_ascii(self):
|
||||
#Test basic FieldStorage multipart parsing
|
||||
env = {'REQUEST_METHOD':'POST',
|
||||
'CONTENT_TYPE': 'multipart/form-data; boundary={}'.format(BOUNDARY),
|
||||
'CONTENT_LENGTH':'558'}
|
||||
for encoding in ['iso-8859-1','utf-8']:
|
||||
fp = BytesIO(POSTDATA_NON_ASCII.encode(encoding))
|
||||
fs = cgi.FieldStorage(fp, environ=env,encoding=encoding)
|
||||
self.assertEqual(len(fs.list), 1)
|
||||
expect = [{'name':'id', 'filename':None, 'value':'\xe7\xf1\x80'}]
|
||||
for x in range(len(fs.list)):
|
||||
for k, exp in expect[x].items():
|
||||
got = getattr(fs.list[x], k)
|
||||
self.assertEqual(got, exp)
|
||||
|
||||
def test_fieldstorage_multipart_maxline(self):
|
||||
# Issue #18167
|
||||
maxline = 1 << 16
|
||||
self.maxDiff = None
|
||||
def check(content):
|
||||
data = """---123
|
||||
Content-Disposition: form-data; name="upload"; filename="fake.txt"
|
||||
Content-Type: text/plain
|
||||
|
||||
%s
|
||||
---123--
|
||||
""".replace('\n', '\r\n') % content
|
||||
environ = {
|
||||
'CONTENT_LENGTH': str(len(data)),
|
||||
'CONTENT_TYPE': 'multipart/form-data; boundary=-123',
|
||||
'REQUEST_METHOD': 'POST',
|
||||
}
|
||||
self.assertEqual(gen_result(data, environ),
|
||||
{'upload': content.encode('latin1')})
|
||||
check('x' * (maxline - 1))
|
||||
check('x' * (maxline - 1) + '\r')
|
||||
check('x' * (maxline - 1) + '\r' + 'y' * (maxline - 1))
|
||||
|
||||
def test_fieldstorage_multipart_w3c(self):
|
||||
# Test basic FieldStorage multipart parsing (W3C sample)
|
||||
env = {
|
||||
'REQUEST_METHOD': 'POST',
|
||||
'CONTENT_TYPE': 'multipart/form-data; boundary={}'.format(BOUNDARY_W3),
|
||||
'CONTENT_LENGTH': str(len(POSTDATA_W3))}
|
||||
fp = BytesIO(POSTDATA_W3.encode('latin-1'))
|
||||
fs = cgi.FieldStorage(fp, environ=env, encoding="latin-1")
|
||||
self.assertEqual(len(fs.list), 2)
|
||||
self.assertEqual(fs.list[0].name, 'submit-name')
|
||||
self.assertEqual(fs.list[0].value, 'Larry')
|
||||
self.assertEqual(fs.list[1].name, 'files')
|
||||
files = fs.list[1].value
|
||||
self.assertEqual(len(files), 2)
|
||||
expect = [{'name': None, 'filename': 'file1.txt', 'value': b'... contents of file1.txt ...'},
|
||||
{'name': None, 'filename': 'file2.gif', 'value': b'...contents of file2.gif...'}]
|
||||
for x in range(len(files)):
|
||||
for k, exp in expect[x].items():
|
||||
got = getattr(files[x], k)
|
||||
self.assertEqual(got, exp)
|
||||
|
||||
def test_fieldstorage_part_content_length(self):
|
||||
BOUNDARY = "JfISa01"
|
||||
POSTDATA = """--JfISa01
|
||||
Content-Disposition: form-data; name="submit-name"
|
||||
Content-Length: 5
|
||||
|
||||
Larry
|
||||
--JfISa01"""
|
||||
env = {
|
||||
'REQUEST_METHOD': 'POST',
|
||||
'CONTENT_TYPE': 'multipart/form-data; boundary={}'.format(BOUNDARY),
|
||||
'CONTENT_LENGTH': str(len(POSTDATA))}
|
||||
fp = BytesIO(POSTDATA.encode('latin-1'))
|
||||
fs = cgi.FieldStorage(fp, environ=env, encoding="latin-1")
|
||||
self.assertEqual(len(fs.list), 1)
|
||||
self.assertEqual(fs.list[0].name, 'submit-name')
|
||||
self.assertEqual(fs.list[0].value, 'Larry')
|
||||
|
||||
def test_field_storage_multipart_no_content_length(self):
|
||||
fp = BytesIO(b"""--MyBoundary
|
||||
Content-Disposition: form-data; name="my-arg"; filename="foo"
|
||||
|
||||
Test
|
||||
|
||||
--MyBoundary--
|
||||
""")
|
||||
env = {
|
||||
"REQUEST_METHOD": "POST",
|
||||
"CONTENT_TYPE": "multipart/form-data; boundary=MyBoundary",
|
||||
"wsgi.input": fp,
|
||||
}
|
||||
fields = cgi.FieldStorage(fp, environ=env)
|
||||
|
||||
self.assertEqual(len(fields["my-arg"].file.read()), 5)
|
||||
|
||||
def test_fieldstorage_as_context_manager(self):
|
||||
fp = BytesIO(b'x' * 10)
|
||||
env = {'REQUEST_METHOD': 'PUT'}
|
||||
with cgi.FieldStorage(fp=fp, environ=env) as fs:
|
||||
content = fs.file.read()
|
||||
self.assertFalse(fs.file.closed)
|
||||
self.assertTrue(fs.file.closed)
|
||||
self.assertEqual(content, 'x' * 10)
|
||||
with self.assertRaisesRegex(ValueError, 'I/O operation on closed file'):
|
||||
fs.file.read()
|
||||
|
||||
_qs_result = {
|
||||
'key1': 'value1',
|
||||
'key2': ['value2x', 'value2y'],
|
||||
'key3': 'value3',
|
||||
'key4': 'value4'
|
||||
}
|
||||
def testQSAndUrlEncode(self):
|
||||
data = "key2=value2x&key3=value3&key4=value4"
|
||||
environ = {
|
||||
'CONTENT_LENGTH': str(len(data)),
|
||||
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
|
||||
'QUERY_STRING': 'key1=value1&key2=value2y',
|
||||
'REQUEST_METHOD': 'POST',
|
||||
}
|
||||
v = gen_result(data, environ)
|
||||
self.assertEqual(self._qs_result, v)
|
||||
|
||||
def test_max_num_fields(self):
|
||||
# For application/x-www-form-urlencoded
|
||||
data = '&'.join(['a=a']*11)
|
||||
environ = {
|
||||
'CONTENT_LENGTH': str(len(data)),
|
||||
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
|
||||
'REQUEST_METHOD': 'POST',
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
cgi.FieldStorage(
|
||||
fp=BytesIO(data.encode()),
|
||||
environ=environ,
|
||||
max_num_fields=10,
|
||||
)
|
||||
|
||||
# For multipart/form-data
|
||||
data = """---123
|
||||
Content-Disposition: form-data; name="a"
|
||||
|
||||
3
|
||||
---123
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
a=4
|
||||
---123
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
a=5
|
||||
---123--
|
||||
"""
|
||||
environ = {
|
||||
'CONTENT_LENGTH': str(len(data)),
|
||||
'CONTENT_TYPE': 'multipart/form-data; boundary=-123',
|
||||
'QUERY_STRING': 'a=1&a=2',
|
||||
'REQUEST_METHOD': 'POST',
|
||||
}
|
||||
|
||||
# 2 GET entities
|
||||
# 1 top level POST entities
|
||||
# 1 entity within the second POST entity
|
||||
# 1 entity within the third POST entity
|
||||
with self.assertRaises(ValueError):
|
||||
cgi.FieldStorage(
|
||||
fp=BytesIO(data.encode()),
|
||||
environ=environ,
|
||||
max_num_fields=4,
|
||||
)
|
||||
cgi.FieldStorage(
|
||||
fp=BytesIO(data.encode()),
|
||||
environ=environ,
|
||||
max_num_fields=5,
|
||||
)
|
||||
|
||||
def testQSAndFormData(self):
|
||||
data = """---123
|
||||
Content-Disposition: form-data; name="key2"
|
||||
|
||||
value2y
|
||||
---123
|
||||
Content-Disposition: form-data; name="key3"
|
||||
|
||||
value3
|
||||
---123
|
||||
Content-Disposition: form-data; name="key4"
|
||||
|
||||
value4
|
||||
---123--
|
||||
"""
|
||||
environ = {
|
||||
'CONTENT_LENGTH': str(len(data)),
|
||||
'CONTENT_TYPE': 'multipart/form-data; boundary=-123',
|
||||
'QUERY_STRING': 'key1=value1&key2=value2x',
|
||||
'REQUEST_METHOD': 'POST',
|
||||
}
|
||||
v = gen_result(data, environ)
|
||||
self.assertEqual(self._qs_result, v)
|
||||
|
||||
def testQSAndFormDataFile(self):
|
||||
data = """---123
|
||||
Content-Disposition: form-data; name="key2"
|
||||
|
||||
value2y
|
||||
---123
|
||||
Content-Disposition: form-data; name="key3"
|
||||
|
||||
value3
|
||||
---123
|
||||
Content-Disposition: form-data; name="key4"
|
||||
|
||||
value4
|
||||
---123
|
||||
Content-Disposition: form-data; name="upload"; filename="fake.txt"
|
||||
Content-Type: text/plain
|
||||
|
||||
this is the content of the fake file
|
||||
|
||||
---123--
|
||||
"""
|
||||
environ = {
|
||||
'CONTENT_LENGTH': str(len(data)),
|
||||
'CONTENT_TYPE': 'multipart/form-data; boundary=-123',
|
||||
'QUERY_STRING': 'key1=value1&key2=value2x',
|
||||
'REQUEST_METHOD': 'POST',
|
||||
}
|
||||
result = self._qs_result.copy()
|
||||
result.update({
|
||||
'upload': b'this is the content of the fake file\n'
|
||||
})
|
||||
v = gen_result(data, environ)
|
||||
self.assertEqual(result, v)
|
||||
|
||||
def test_parse_header(self):
|
||||
self.assertEqual(
|
||||
cgi.parse_header("text/plain"),
|
||||
("text/plain", {}))
|
||||
self.assertEqual(
|
||||
cgi.parse_header("text/vnd.just.made.this.up ; "),
|
||||
("text/vnd.just.made.this.up", {}))
|
||||
self.assertEqual(
|
||||
cgi.parse_header("text/plain;charset=us-ascii"),
|
||||
("text/plain", {"charset": "us-ascii"}))
|
||||
self.assertEqual(
|
||||
cgi.parse_header('text/plain ; charset="us-ascii"'),
|
||||
("text/plain", {"charset": "us-ascii"}))
|
||||
self.assertEqual(
|
||||
cgi.parse_header('text/plain ; charset="us-ascii"; another=opt'),
|
||||
("text/plain", {"charset": "us-ascii", "another": "opt"}))
|
||||
self.assertEqual(
|
||||
cgi.parse_header('attachment; filename="silly.txt"'),
|
||||
("attachment", {"filename": "silly.txt"}))
|
||||
self.assertEqual(
|
||||
cgi.parse_header('attachment; filename="strange;name"'),
|
||||
("attachment", {"filename": "strange;name"}))
|
||||
self.assertEqual(
|
||||
cgi.parse_header('attachment; filename="strange;name";size=123;'),
|
||||
("attachment", {"filename": "strange;name", "size": "123"}))
|
||||
self.assertEqual(
|
||||
cgi.parse_header('form-data; name="files"; filename="fo\\"o;bar"'),
|
||||
("form-data", {"name": "files", "filename": 'fo"o;bar'}))
|
||||
|
||||
def test_all(self):
|
||||
not_exported = {
|
||||
"logfile", "logfp", "initlog", "dolog", "nolog", "closelog", "log",
|
||||
"maxlen", "valid_boundary"}
|
||||
support.check__all__(self, cgi, not_exported=not_exported)
|
||||
|
||||
|
||||
BOUNDARY = "---------------------------721837373350705526688164684"
|
||||
|
||||
POSTDATA = """-----------------------------721837373350705526688164684
|
||||
Content-Disposition: form-data; name="id"
|
||||
|
||||
1234
|
||||
-----------------------------721837373350705526688164684
|
||||
Content-Disposition: form-data; name="title"
|
||||
|
||||
|
||||
-----------------------------721837373350705526688164684
|
||||
Content-Disposition: form-data; name="file"; filename="test.txt"
|
||||
Content-Type: text/plain
|
||||
|
||||
Testing 123.
|
||||
|
||||
-----------------------------721837373350705526688164684
|
||||
Content-Disposition: form-data; name="submit"
|
||||
|
||||
Add\x20
|
||||
-----------------------------721837373350705526688164684--
|
||||
"""
|
||||
|
||||
POSTDATA_NON_ASCII = """-----------------------------721837373350705526688164684
|
||||
Content-Disposition: form-data; name="id"
|
||||
|
||||
\xe7\xf1\x80
|
||||
-----------------------------721837373350705526688164684
|
||||
"""
|
||||
|
||||
# http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4
|
||||
BOUNDARY_W3 = "AaB03x"
|
||||
POSTDATA_W3 = """--AaB03x
|
||||
Content-Disposition: form-data; name="submit-name"
|
||||
|
||||
Larry
|
||||
--AaB03x
|
||||
Content-Disposition: form-data; name="files"
|
||||
Content-Type: multipart/mixed; boundary=BbC04y
|
||||
|
||||
--BbC04y
|
||||
Content-Disposition: file; filename="file1.txt"
|
||||
Content-Type: text/plain
|
||||
|
||||
... contents of file1.txt ...
|
||||
--BbC04y
|
||||
Content-Disposition: file; filename="file2.gif"
|
||||
Content-Type: image/gif
|
||||
Content-Transfer-Encoding: binary
|
||||
|
||||
...contents of file2.gif...
|
||||
--BbC04y--
|
||||
--AaB03x--
|
||||
"""
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -1,71 +0,0 @@
|
|||
from test.support.os_helper import temp_dir
|
||||
from test.support.script_helper import assert_python_failure
|
||||
from test.support.warnings_helper import import_deprecated
|
||||
import unittest
|
||||
import sys
|
||||
cgitb = import_deprecated("cgitb")
|
||||
|
||||
class TestCgitb(unittest.TestCase):
|
||||
|
||||
def test_fonts(self):
|
||||
text = "Hello Robbie!"
|
||||
self.assertEqual(cgitb.small(text), "<small>{}</small>".format(text))
|
||||
self.assertEqual(cgitb.strong(text), "<strong>{}</strong>".format(text))
|
||||
self.assertEqual(cgitb.grey(text),
|
||||
'<font color="#909090">{}</font>'.format(text))
|
||||
|
||||
def test_blanks(self):
|
||||
self.assertEqual(cgitb.small(""), "")
|
||||
self.assertEqual(cgitb.strong(""), "")
|
||||
self.assertEqual(cgitb.grey(""), "")
|
||||
|
||||
def test_html(self):
|
||||
try:
|
||||
raise ValueError("Hello World")
|
||||
except ValueError as err:
|
||||
# If the html was templated we could do a bit more here.
|
||||
# At least check that we get details on what we just raised.
|
||||
html = cgitb.html(sys.exc_info())
|
||||
self.assertIn("ValueError", html)
|
||||
self.assertIn(str(err), html)
|
||||
|
||||
def test_text(self):
|
||||
try:
|
||||
raise ValueError("Hello World")
|
||||
except ValueError:
|
||||
text = cgitb.text(sys.exc_info())
|
||||
self.assertIn("ValueError", text)
|
||||
self.assertIn("Hello World", text)
|
||||
|
||||
def test_syshook_no_logdir_default_format(self):
|
||||
with temp_dir() as tracedir:
|
||||
rc, out, err = assert_python_failure(
|
||||
'-c',
|
||||
('import cgitb; cgitb.enable(logdir=%s); '
|
||||
'raise ValueError("Hello World")') % repr(tracedir),
|
||||
PYTHONIOENCODING='utf-8')
|
||||
out = out.decode()
|
||||
self.assertIn("ValueError", out)
|
||||
self.assertIn("Hello World", out)
|
||||
self.assertIn("<strong><module></strong>", out)
|
||||
# By default we emit HTML markup.
|
||||
self.assertIn('<p>', out)
|
||||
self.assertIn('</p>', out)
|
||||
|
||||
def test_syshook_no_logdir_text_format(self):
|
||||
# Issue 12890: we were emitting the <p> tag in text mode.
|
||||
with temp_dir() as tracedir:
|
||||
rc, out, err = assert_python_failure(
|
||||
'-c',
|
||||
('import cgitb; cgitb.enable(format="text", logdir=%s); '
|
||||
'raise ValueError("Hello World")') % repr(tracedir),
|
||||
PYTHONIOENCODING='utf-8')
|
||||
out = out.decode()
|
||||
self.assertIn("ValueError", out)
|
||||
self.assertIn("Hello World", out)
|
||||
self.assertNotIn('<p>', out)
|
||||
self.assertNotIn('</p>', out)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -219,9 +219,6 @@ class PyclbrTest(TestCase):
|
|||
|
||||
# These were once some of the longest modules.
|
||||
cm('random', ignore=('Random',)) # from _random import Random as CoreGenerator
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore', DeprecationWarning)
|
||||
cm('cgi', ignore=('log',)) # set with = in module
|
||||
cm('pickle', ignore=('partial', 'PickleBuffer'))
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore', DeprecationWarning)
|
||||
|
|
|
@ -3457,7 +3457,7 @@ Patch contributed by Rémi Lapeyre.
|
|||
.. nonce: kG0ub5
|
||||
.. section: Library
|
||||
|
||||
Fixes a bug in :mod:`cgi` module when a multipart/form-data request has no
|
||||
Fixes a bug in :mod:`!cgi` module when a multipart/form-data request has no
|
||||
`Content-Length` header.
|
||||
|
||||
..
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
:pep:`594`: Remove the :mod:`!cgi`` and :mod:`!cgitb` modules, deprecated in
|
||||
Python 3.11. Patch by Victor Stinner.
|
|
@ -107,8 +107,6 @@ static const char* _Py_stdlib_module_names[] = {
|
|||
"bz2",
|
||||
"cProfile",
|
||||
"calendar",
|
||||
"cgi",
|
||||
"cgitb",
|
||||
"chunk",
|
||||
"cmath",
|
||||
"cmd",
|
||||
|
|
|
@ -64,8 +64,6 @@ OMIT_FILES = (
|
|||
# socket.create_connection() raises an exception:
|
||||
# "BlockingIOError: [Errno 26] Operation in progress".
|
||||
OMIT_NETWORKING_FILES = (
|
||||
"cgi.py",
|
||||
"cgitb.py",
|
||||
"email/",
|
||||
"ftplib.py",
|
||||
"http/",
|
||||
|
|
Loading…
Reference in New Issue