Read Programming Python Online

Authors: Mark Lutz

Tags: #COMPUTERS / Programming Languages / Python

Programming Python (167 page)

BOOK: Programming Python
13.11Mb size Format: txt, pdf, ePub
ads
Escaping Mail Text and Passwords in HTML

Notice that
everything you see on the message view page’s HTML in
Figure 16-14
is escaped
with
cgi.escape
. Header fields and
the text of the mail itself might contain characters that are special to
HTML and must be translated as usual. For instance, because some mailers
allow you to send messages in HTML format, it’s possible that an email’s
text could contain a

tag, which might throw the
reply page hopelessly out of sync if not escaped.

One subtlety here: HTML escapes are important only when text is
sent to the browser initially by the CGI script. If that text is later
sent out again to another script (e.g., by sending a reply mail), the
text will be back in its original, nonescaped format when received again
on the server. The browser parses out escape codes and does not put them
back again when uploading form data, so we don’t need to undo escapes
later. For example, here is part of the escaped text area sent to a
browser during a Reply transaction (use your browser’s View Source
option to see this live):

Text:

> --Mark Lutz (http://rmi.net/~lutz) [PyMailCgi 2.0]
>
>
> > -----Original Message-----

Beyond the normal text, the password gets special HTML escapes
treatment as well. Though not shown in our examples, the hidden password
field of the generated HTML screenshot (
Figure 16-14
) can look
downright bizarre when encryption is applied. It turns out that the POP
password is still encrypted when placed in hidden fields of the HTML.
For security, they have to be. Values of a page’s hidden fields can be
seen with a browser’s View Source option, and it’s not impossible that
the text of this page could be saved to a file or intercepted off the
Net.

The password is no longer URL encoded when put in the hidden
field, however, even though it was when it appeared as a query parameter
at the end of a stateful URL in the mail list page. Depending on your
encryption module, the password might now contain nonprintable
characters when generated as a hidden field value here; the browser
doesn’t care, as long as the field is run through
cgi.escape
like
everything else added to the HTML reply stream. The
commonhtml
module is careful to route all text
and headers through
cgi.escape
as the
view page is constructed.

As a comparison,
Figure 16-15
shows what the
mail message captured in
Figure 16-12
looks
like when viewed in PyMailGUI, the client-side “desktop” tkinter-based
email tool from
Chapter 14
. In that
program, message parts are listed with the Parts button and are
extracted, saved, and opened with the Split button; we also get
quick-access buttons to parts and attachments just below the message
headers. The net effect is similar from an end user’s
perspective.

Figure 16-15. PyMailGUI viewer, same message as
Figure 16-12

In terms of implementation, though, the model is very different.
PyMailGUI doesn’t need to care about things such as passing state in
URLs or hidden fields (it saves state in Python in-process variables and
memory), and there’s no notion of escaping HTML and URL strings (there
are no browsers, and no network transmission steps once mail is
downloaded). It also doesn’t have to rely on temporary server file links
to give access to message parts—the message is retained in memory
attached to a window object and lives on between interactions. On the
other hand, PyMailGUI does require Python to be installed on the client,
but we’ll return to that in a few
pages.

[
67
]
Technically, again, you should generally escape
&
separators in generated URL links by
running the URL through
cgi.escape
, if any parameter’s name could
be the same as that of an HTML character escape code (e.g.,
&=high
). See
Chapter 15
for more details; they aren’t
escaped here because there are no clashes between URL and
HTML.

[
68
]
Notice that the message number arrives as a string and must be
converted to an integer in order to be used to fetch the message.
But we’re careful not to convert with
eval
here, since this is a string passed
over the Net and could have arrived embedded at the end of an
arbitrary URL (remember that earlier warning?).

Processing Fetched Mail

At this point in our
PyMailCGI web interaction, we are viewing an email message
(
Figure 16-12
) that was chosen from the
selection list page. On the message view page, selecting an action from
the pull-down list and clicking the Next button invokes the script in
Example 16-9
on the server to
perform a reply, forward, or delete operation for the selected message
viewed.

Example 16-9. PP4E\Internet\Web\PyMailCgi\cgi-bin\onViewPageAction.py

#!/usr/bin/python
"""
################################################################################
On submit in mail view window: action selected=(fwd, reply, delete);
in 2.0+, we reuse the mailtools delete logic originally coded for PyMailGUI;
################################################################################
"""
import cgi, commonhtml, secret
from externs import mailtools, mailconfig
from commonhtml import getfield
def quotetext(form):
"""
note that headers come from the prior page's form here,
not from parsing the mail message again; that means that
commonhtml.viewpage must pass along date as a hidden field
"""
parser = mailtools.MailParser()
addrhdrs = ('From', 'To', 'Cc', 'Bcc') # decode name only
quoted = '\n-----Original Message-----\n'
for hdr in ('From', 'To', 'Date'):
rawhdr = getfield(form, hdr)
if hdr not in addrhdrs:
dechdr = parser.decodeHeader(rawhdr) # 3.0: decode for display
else: # encoded on sends
dechdr = parser.decodeAddrHeader(rawhdr) # email names only
quoted += '%s: %s\n' % (hdr, dechdr)
quoted += '\n' + getfield(form, 'text')
quoted = '\n' + quoted.replace('\n', '\n> ')
return quoted
form = cgi.FieldStorage() # parse form or URL data
user, pswd, site = commonhtml.getstandardpopfields(form)
pswd = secret.decode(pswd)
try:
if form['action'].value == 'Reply':
headers = {'From': mailconfig.myaddress, # 3.0: commonhtml decodes
'To': getfield(form, 'From'),
'Cc': mailconfig.myaddress,
'Subject': 'Re: ' + getfield(form, 'Subject')}
commonhtml.editpage('Reply', headers, quotetext(form))
elif form['action'].value == 'Forward':
headers = {'From': mailconfig.myaddress, # 3.0: commonhtml decodes
'To': '',
'Cc': mailconfig.myaddress,
'Subject': 'Fwd: ' + getfield(form, 'Subject')}
commonhtml.editpage('Forward', headers, quotetext(form))
elif form['action'].value == 'Delete': # mnum field is required here
msgnum = int(form['mnum'].value) # but not eval(): may be code
fetcher = mailtools.SilentMailFetcher(site, user, pswd)
fetcher.deleteMessages([msgnum])
commonhtml.confirmationpage('Delete')
else:
assert False, 'Invalid view action requested'
except:
commonhtml.errorpage('Cannot process view action')

This script receives all information about the selected message as
form input field data (some hidden and possibly encrypted, some not) along
with the selected action’s name. The next step in the interaction depends
upon the action selected:

Reply and Forward actions

Generate a message edit page with the original message’s lines
automatically quoted with a leading
>
.

Delete actions

Trigger immediate deletion of the email being viewed, using a
tool imported from the
mailtools
module package from
Chapter 13
.

All these actions use data passed in from the prior page’s form, but
only the Delete action cares about the POP username and password and must
decode the password received (it arrives here from hidden form input
fields generated in
the prior page’s HTML).

Reply and Forward

If you select
Reply as the next action, the message edit page in
Figure 16-16
is generated by the script. Text on
this page is editable, and pressing this page’s Send button again
triggers the send mail script we saw in
Example 16-4
. If all goes well,
we’ll receive the same confirmation page we got earlier when writing new
mail from scratch
(
Figure 16-4
).

Figure 16-16. PyMailCGI reply page

Forward operations are virtually the same, except for a few email
header differences. All of this busy-ness comes “for free,” because
Reply and Forward pages are generated by calling
commonhtml.editpage
, the same utility used to
create a new mail composition page. Here, we simply pass preformatted
header line strings to the utility (e.g., replies add “Re:” to the
subject text). We applied the same sort of reuse trick in PyMailGUI, but
in a different context. In PyMailCGI, one script handles three pages; in
PyMailGUI, one superclass and callback method handles three buttons, but
the architecture is similar in spirit.

Delete

Selecting the
Delete action on a message view page and pressing Next
will cause the
onViewPageAction
script to immediately delete the message being viewed. Deletions are
performed by calling a reusable delete utility function coded in
Chapter 13
’s
mailtools
package. In a prior version, the
call to the utility was wrapped in a
commonhtml.runsilent
call that prevents
print
call statements in the utility
from showing up in the HTML reply stream (they are just status messages,
not HTML code). In this version, we get the same capability from the
“Silent” classes in
mailtools
.
Figure 16-17
shows a Delete
operation in action.

Figure 16-17. PyMailCGI view page, Delete selected

By the way, notice the varied type of attachment parts on the
mail’s page in
Figure 16-17
. In version 3.0 we
can send only text attachments due to the Python 3.1 CGI uploads parsing
regression described earlier, but we can still
view
arbitrary attachment types in fetched mails received from other senders.
This includes images and PDFs. Such attachments open according to your
browser’s conventions;
Figure 16-18
shows how Chrome
handles a click on the
monkeys.jpg
link at the bottom of the PyMailCGI page in
Figure 16-17
—it’s the same
image we sent by FTP in
Chapter 13
and via
PyMailGUI in
Chapter 14
, but here it has
been extracted by a PyMailCGI CGI script and is being returned by a
locally running web server.

Figure 16-18. Image attachment part selected in PyMailCGI

Back to our pending deletion. As mentioned, Delete is the only
action that uses the POP account information (user, password, and site)
that was passed in from hidden fields on the prior message view page. By
contrast, the Reply and Forward actions format an edit page, which
ultimately sends a message to the SMTP server; no POP information is
needed or passed.

But at this point in the interaction, the POP password has racked
up more than a few frequent flyer miles. In fact, it may have crossed
phone lines, satellite links, and continents on its journey from machine
to machine. Let’s trace through the voyage:

  1. Input (client): The password starts life by being typed into
    the login page on the client (or being embedded in an explicit URL),
    unencrypted. If typed into the input form in a web browser, each
    character is displayed as a star (
    *
    ).

  2. Fetch index (client to CGI server to POP server): It is next
    passed from the client to the CGI script on the server, which sends
    it on to your POP server in order to load a mail index. The client
    sends only the password, unencrypted.

  3. List page URLs (CGI server to client): To direct the next
    script’s behavior, the password is embedded in the mail selection
    list web page itself as hyperlink URL query parameters, encrypted
    (or otherwise obfuscated) and URL encoded.

  4. Fetch message (client to CGI server to POP server): When an
    email is selected from the list, the password is sent to the next
    script named within the link’s URL; the CGI script decodes it and
    passes it on to the POP server to fetch the selected
    message
    .

  5. View page fields (CGI server to client): To direct the next
    script’s behavior, the password is embedded in the view page itself
    as HTML hidden input fields, encrypted or obfuscated, and HTML
    escaped.

  6. Delete message (client to CGI server to POP server): Finally,
    the password is again passed from client to CGI server, this time as
    hidden form field values; the CGI script decodes it and passes it to
    the POP server to delete.

Along the way, scripts have passed the password between pages as
both a URL query parameter and an HTML hidden input field; either way,
they have always passed its encrypted or obfuscated string and have
never passed an unencoded password and username together in any
transaction. Upon a Delete request, the password must be decoded here
using the
secret
module before
passing it to the POP server. If the script can access the POP server
again and delete the selected message, another confirmation page
appears, as shown in
Figure 16-19
(there is currently no verification for the delete, so be
careful).

Figure 16-19. PyMailCGI delete confirmation

One subtlety for replies and forwards: the
onViewPageAction
mail action script builds up
a
>
-quoted representation of the
original message, with original “From:”, “To:”, and “Date:” header lines
prepended to the mail’s original text. Notice, though, that the original
message’s headers are fetched from the CGI form input, not by reparsing
the original mail (the mail is not readily available at this point). In
other words, the script gets mail header values from the form input
fields of the view page. Because there is no “Date” field on the view
page, the original message’s date is also passed along to the action
script as a hidden input field to avoid reloading the message. Try
tracing through the code in this chapter’s listings ahead to see whether
you can follow dates from page to
page.

BOOK: Programming Python
13.11Mb size Format: txt, pdf, ePub
ads

Other books

Operation Prince Charming by Phyllis Bourne
Push the Envelope by Rochelle Paige
Leon Uris by A God in Ruins
La dama del lago by Andrzej Sapkowski
Enigma Black by Furlong-Burr, Sara
Iron Orchid by Stuart Woods