So far, we’ve stepped through the path the system follows to send new mail. Let’s now see what happens when we try to view incoming POP mail.
If you flip back to the main page in Figure 13-2, you’ll see a View link; pressing it triggers the script in Example 13-6 to run on the server:
Example 13-6. PP2EInternetCgi-WebPyMailCgionRootViewLink.cgi
#!/usr/bin/python ############################################################## # on view link click on main/root html page # this could almost be a html file because there are likely # no input params yet, but I wanted to use standard header/ # footer functions and display the site/user names which must # be fetched; On submission, doesn't send the user along with # password here, and only ever sends both as URL params or # hidden fields after the password has been encrypted by a # user-uploadable encryption module; put html in commonhtml? ############################################################## # page template pswdhtml = """ <form method=post action=%s/onViewPswdSubmit.cgi> <p> Please enter POP account password below, for user "%s" and site "%s". <p><input name=pswd type=password> <input type=submit value="Submit"></form></p> <hr><p><i>Security note</i>: The password you enter above will be transmitted over the Internet to the server machine, but is not displayed, is never transmitted in combination with a username unless it is encrypted, and is never stored anywhere: not on the server (it is only passed along as hidden fields in subsequent pages), and not on the client (no cookies are generated). This is still not totally safe; use your browser's back button to back out of PyMailCgi at any time.</p> """ # generate the password input page import commonhtml # usual parms case: user, pswd, site = commonhtml.getstandardpopfields({}) # from module here, commonhtml.pageheader(kind='POP password input') # from html|url later print pswdhtml % (commonhtml.urlroot, user, site) commonhtml.pagefooter( )
This script is almost all embedded
HTML: the triple-quoted pswdhtml
string is
printed, with string formatting, in a single step. But because we
need to fetch the user and server names to display on the generated
page, this is coded as an executable script, not a static HTML file.
Module commonhtml
either loads user and server
names from script inputs (e.g., appended to the script’s URL),
or imports them from the mailconfig
file; either
way, we don’t want to hardcode them into this script or its
HTML, so an HTML file won’t do.
Since this is a script, we can also make use of the
commonhtml
page header and footer routines to
render the generated reply page with the common look-and-feel; this
is shown in Figure 13-7.
At this page, the user is expected to enter the password for the POP
email account of the user and server displayed. Notice that the
actual password isn’t displayed; the input field’s HTML
specifies type=password
, which works just like a
normal text field, but shows typed input as stars. (See also Example 11-6 for doing this at a console, and Example 11-23 for doing this in a GUI.)
After filling out the last page’s password field and pressing its Submit button, the password is shipped off to the script shown in Example 13-7.
Example 13-7. PP2EInternetCgi-WebPyMailCgionViewPswdSubmit.cgi
#!/usr/bin/python # On submit in pop password input window--make view list import cgi, StringIO, rfc822, string import loadmail, commonhtml from secret import encode # user-defined encoder module MaxHdr = 35 # max length of email hdrs in list # only pswd comes from page here, rest usually in module formdata = cgi.FieldStorage( ) mailuser, mailpswd, mailsite = commonhtml.getstandardpopfields(formdata) try: newmail = loadmail.loadnewmail(mailsite, mailuser, mailpswd) mailnum = 1 maillist = [] for mail in newmail: msginfo = [] hdrs = rfc822.Message(StringIO.StringIO(mail)) for key in ('Subject', 'From', 'Date'): msginfo.append(hdrs.get(key, '?')[:MaxHdr]) msginfo = string.join(msginfo, ' | ') maillist.append((msginfo, commonhtml.urlroot + '/onViewListLink.cgi', {'mnum': mailnum, 'user': mailuser, # data params 'pswd': encode(mailpswd), # pass in url 'site': mailsite})) # not inputs mailnum = mailnum+1 commonhtml.listpage(maillist, 'mail selection list') except: commonhtml.errorpage('Error loading mail index')
This script’s main purpose is to generate a selection list page for the user’s email account, using the password typed into the prior page (or passed in a URL). As usual with encapsulation, most of the details are hidden in other files:
loadmail.loadnewmail
reuses the mail module from Chapter 11 to fetch
email with the POP protocol; we need a message count and mail headers
here to display an index list.
commonhtml.listpage
generates HTML to display a
passed-in list of (text
, URL
,
parameter-dictionary
) tuples as a list of
hyperlinks in the reply page; parameter values show up at the end of
URLs in the response.
The maillist
list built here is used to create the
body of the next page -- a clickable email message selection list.
Each generated hyperlink in the list page references a constructed
URL that contains enough information for the next script to fetch and
display a particular email message.
If all goes well, the mail selection list page HTML generated by this
script is rendered as in Figure 13-8. If you get as
much email as I do, you’ll probably need to scroll down to see
the end of this page. It looks like Figure 13-9, and
follows the common look-and-feel for all PyMailCgi pages, thanks to
commonhtml
.
If the script can’t access your email account (e.g., because
you typed the wrong password), then its try
statement handler instead produces a commonly formatted error page.
Figure 13-10 shows one that gives the Python
exception and details as part of the reply after a genuine exception
is caught.
The central mechanism at work in Example 13-7 is the generation of URLs that embed message numbers and mail account information. Clicking on any of the View links in the selection list triggers another script, which uses information in the link’s URL parameters to fetch and display the selected email. As mentioned in the prior chapter, because the list’s links are effectively programmed to “know” how to load a particular message, it’s not too far-fetched to refer to them as smart links -- URLs that remember what to do next. Figure 13-11 shows part of the HTML generated by this script.
Did you get all that? You may not be able to read generated HTML like this, but your browser can. For the sake of readers afflicted with human parsing limitations, here is what one of those link lines looks like, reformatted with line breaks and spaces to make it easier to understand:
<tr><th><ahref="http://starship.python.net/~lutz/ PyMailCgi/onViewListLink.cgi ?user=lutz& mnum=66& pswd=%8cg%c2P%1e%f3%5b%c5J%1c%f0& site=pop.rmi.net">View</a> 66 <td>test sendurl.py | [email protected] | Mon Jun 5 17:51:11 2000
PyMailCgi generates fully specified URLs (with server and pathname
values imported from a common module). Clicking on the word
“View” in the hyperlink rendered from this HTML code
triggers the onViewListLink
script as usual,
passing it all the parameters embedded at the end of the URL: POP
username, the POP message number of the message associated with this
link, and POP password and site information. These values will be
available in the object returned by
cgi.FieldStorage
in the next script run. Note that
the mnum
POP message number parameter differs in
each link because each opens a different message when clicked, and
that the text after <td>
comes from message
headers extracted with the rfc822
module.
The
commonhtml
module escapes all of the link
parameters with the urllib
module, not
cgi.escape
, because they are part of a URL. This
is obvious only in the pswd
password
parameter -- its value has been encrypted, but
urllib
additionally escapes non-safe characters in
the encrypted string per URL convention (that’s where all those
%xx
come from). It’s okay if the encryptor
yields odd -- even non-printable -- characters, because URL
encoding makes them legible for transmission. When the password
reaches the next script, cgi.FieldStorage
undoes
URL escape sequences, leaving the encrypted password string without
%
escapes.
It’s instructive to see how commonhtml
builds up the smart link parameters. Earlier, we learned how to use
the urllib.quote_plus
call to escape a string for
inclusion in URLs:
>>>import urllib
>>>urllib.quote_plus("There's bugger all down here on Earth")
'There%27s+bugger+all+down+here+on+Earth'
Module
commonhtml
, though, calls the higher-level
urllib.urlencode
function, which translates a
dictionary of name:value
pairs into a
complete URL parameter string, ready to add after a
?
marker in a URL. For instance, here is
urlencode
in action at the interactive prompt:
>>>parmdict = {'user': 'Brian',
...'pswd': '#!/spam',
...'text': 'Say no more, squire!'}
>>>urllib.urlencode(parmdict)
'pswd=%23%21/spam&user=Brian&text=Say+no+more,+squire%21' >>>"%s?%s" % ("http://scriptname.cgi", urllib.urlencode(parmdict))
'http://scriptname.cgi?pswd=%23%21/spam&user=Brian&text=Say+no+more,+squire%21'
Internally, urlencode
passes each name and value
in the dictionary to the built-in str
function (to
make sure they are strings) and then runs each one through
urllib.quote_plus
as they are added to the result.
The CGI script builds up a list of similar dictionaries and passes it
to commonhtml
to be formatted into a selection
list page.[108]
In broader terms, generating URLs with parameters like this is one way to pass state information to the next script (along with databases and hidden form input fields, discussed later). Without such state information, the user would have to re-enter the username, password, and site name on every page they visit along the way. We’ll use this technique again in the next case study, to generate links that “know” how to fetch a particular database record.
Incidentally, the list generated by this script is not radically different in functionality from what we built in the PyMailGui program of Chapter 11. Figure 13-12 shows this strictly client-side GUI’s view on the same email list displayed in Figures Figure 13-8 and Figure 13-9.
However, PyMailGui uses the Tkinter GUI library to build up a user interface instead of sending HTML to a browser. It also runs entirely on the client and downloads mail from the POP server to the client machine over sockets on demand. In contrast, PyMailCgi runs on the server machine and simply displays mail text on the client’s browser -- mail is downloaded from the POP server machine to the starship server, where CGI scripts are run. These architecture differences have some important ramifications, which we’ll discuss in a few moments.
In
onViewPswdSubmit
’s source code (Example 13-7), notice that password inputs are passed to an
encode
function as they are added to the
parameters dictionary, and hence show up encrypted in hyperlink URLs.
They are also URL-encoded for transmission (with %
escapes) and are later decoded and decrypted within other scripts as
needed to access the POP account. The password encryption step,
encode
, is at the heart of PyMailCgi’s
security policy.
Beginning in Python 1.6, the standard socket module will include optional support for OpenSSL, an open source implementation of secure sockets that prevents transmitted data from being intercepted by eavesdroppers on the Net. Unfortunately, this example was developed under Python 1.5.2 so an alternative scheme was devised to minimize the chance that email account information could be stolen off the Net in transit.
Here’s how it works. When this script is invoked by the
password input page’s form, it gets only one input parameter:
the password typed into the form. The username is imported from a
mailconfig
module installed on the server instead
of transmitted together with the unencrypted password (that would be
much too easy for malicious users to intercept).
To pass the POP username and password to the next page as state
information, this script adds them to the end of the mail selection
list URLs, but only after the password has been encrypted by
secret.encode
-- a function in a module that
lives on the server and may vary in every location that PyMailCgi is
installed. In fact, PyMailCgi was written to not have to know about
the password encryptor at all; because the encoder is a separate
module, you can provide any flavor you like. Unless you also publish
your encoder module, the encoded password shipped with the username
won’t be of much help to snoopers.
That upshot is that normally, PyMailGui never sends or receives both
user and password values together in a single transaction unless the
password is encrypted with an encryptor of your choice. This limits
its utility somewhat (since only a single account username can be
installed on the server), but the alternative of popping up two
pages -- one for password entry and one for user -- is even
more unfriendly. In general, if you want to read your mail with the
system as coded, you have to install its files on your server, tweak
its mailconfig.py
to reflect your account
details, and change its secret.py
encryptor as
desired.
One exception: since any CGI script can be invoked with parameters in
an explicit URL instead of form field values, and since
commonhtml
tries to fetch inputs from the form
object before importing them from mailconfig
, it
is possible for any person to use this script to check his or her
mail without installing and configuring a copy of PyMailCgi. For
example, a URL like the following (but without the linebreak used to
make it fit here):
http://starship.python.net/~lutz/PyMailCgi/ onViewPswdSubmit.cgi?user=lutz&pswd=asif&site=pop.rmi.net
will actually load email into a selection list using whatever user,
password, and mail site names are appended. From the selection list,
you may then view, reply, forward, and delete email. Notice that at
this point in the interaction, the password you send in a URL of this
form is not encrypted. Later scripts expect that
the password inputs will be sent encrypted, though, which makes it
more difficult to use them with explicit URLs (you would need to
match the encrypted form produced by the secret
module on the server). Passwords are encrypted as they are added to
links in the reply page’s selection list, and remain encrypted
in URLs and hidden form fields thereafter.
But please don’t use a URL like this, unless you don’t care about exposing your email password. Really. Sending both your unencrypted mail user ID and password strings across the Net in a URL like this is extremely unsafe and wide open to snoopers. In fact, it’s like giving them a loaded gun -- anyone who intercepts this URL will have complete access to your email account. It is made even more treacherous by the fact that this URL format appears in a book that will be widely distributed all around the world.
If
you care about security and want to use PyMailCgi, install it on your
own server and configure mailconfig
and
secret
. That should at least guarantee that your
user and password information will never both be transmitted
unencrypted in a single transaction. This scheme still is not
foolproof, so be careful out there, folks. Without secure sockets,
the Internet is a “use at your own risk” medium.
Back to our page flow. At this point, we are still viewing the message selection list in Figure 13-8. When we click on one of its generated hyperlinks, the smart URL invokes the script in Example 13-8 on the server, sending the selected message number and mail account information (user, password, and site) as parameters on the end of the script’s URL.
Example 13-8. PP2EInternetCgi-WebPyMailCgionViewListLink.cgi
#!/usr/bin/python ############################################################ # On user click of message link in main selection list; # cgi.FieldStorage undoes any urllib escapes in the link's # input parameters (%xx and '+' for spaces already undone); ############################################################ import cgi, rfc822, StringIO import commonhtml, loadmail from secret import decode #commonhtml.dumpstatepage(0) form = cgi.FieldStorage( ) user, pswd, site = commonhtml.getstandardpopfields(form) try: msgnum = form['mnum'].value # from url link newmail = loadmail.loadnewmail(site, user, decode(pswd)) textfile = StringIO.StringIO(newmail[int(msgnum) - 1]) # don't eval! headers = rfc822.Message(textfile) bodytext = textfile.read( ) commonhtml.viewpage(msgnum, headers, bodytext, form) # encoded pswd except: commonhtml.errorpage('Error loading message')
Again, most of the work here happens in
the loadmail
and commonhtml
modules, which are listed later in this section (Example 13-12 and Example 13-14). This script
adds logic to decode the input password (using the configurable
secret
encryption module) and extract the selected
mail’s headers and text using the rfc822
and
StringIO
modules, just as we did in Chapter 11.[109]
If the message can be loaded and parsed successfully, the result page
(shown in Figure 13-13) allows us to view, but not
edit, the mail’s text. The function
commonhtml.viewpage
generates a
“read-only” HTML option for all the text widgets in this
page.
View pages like this have a pull-down action selection list near the bottom; if you want to do more, use this list to pick an action (Reply, Forward, or Delete), and click on the Next button to proceed to the next screen. If you’re just in a browsing frame of mind, click the “Back to root page” link at the bottom to return to the main page, or use your browser’s Back button to return to the selection list page.
What you don’t see on the
view page in Figure 13-13 is just as important as
what you do. We need to refer to Example 13-14 for
details, but there’s something new going on here. The original
message number, as well as the POP user and (still encrypted)
password information sent to this script as part of the smart
link’s URL, wind up being copied into the HTML used to create
this view page, as the values of “hidden” input fields in
the form. The hidden field generation code in
commonhtml
looks like this:
print '<form method=post action="%s/onViewSubmit.cgi">' % urlroot print '<input type=hidden name=mnum value="%s">' % msgnum print '<input type=hidden name=user value="%s">' % user # from page|url print '<input type=hidden name=site value="%s">' % site # for deletes print '<input type=hidden name=pswd value="%s">' % pswd # pswd encoded
Much like parameters in generated hyperlink URLs, hidden fields in a page’s HTML allow us to embed state information inside this web page itself. Unless you view that page’s source, you can’t see this state information, because hidden fields are never displayed. But when this form’s Submit button is clicked, hidden field values are automatically transmitted to the next script along with the visible fields on the form.
Figure 13-14 shows the source code generated for a different message’s view page; the hidden input fields used to pass selected mail state information are embedded near the top.
The net effect is that hidden input fields in HTML, just like parameters at the end of generated URLs, act like temporary storage areas and retain state between pages and user interaction steps. Both are the Web’s equivalent to programming language variables. They come in handy any time your application needs to remember something between pages.
Hidden fields are especially useful if you cannot invoke the next script from a generated URL hyperlink with parameters. For instance, the next action in our script is a form submit button (Next), not a hyperlink, so hidden fields are used to pass state. As before, without these hidden fields, users would need to re-enter POP account details somewhere on the view page if they were needed by the next script (in our example, they are required if the next action is Delete).
Notice that everything you see on the
message view page in Figure 13-13 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
</textarea>
tag, which would 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), the text will be back in its original, non-escaped 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):
<tr><th align=right>Text: <td><textarea name=text cols=80 rows=10 readonly> more stuff --Mark Lutz (http://rmi.net/~lutz) [PyMailCgi 1.0] > -----Original Message----- > From: [email protected] > To: [email protected] > Date: Tue May 2 18:28:41 2000 > > <table><textarea> > </textarea></table> > --Mark Lutz (http://rmi.net/~lutz) [PyMailCgi 1.0] > > > > -----Original Message-----
After this reply is delivered, its text looks as it did before escapes (and exactly as it appeared to the user in the message edit web page):
more stuff --Mark Lutz (http://rmi.net/~lutz) [PyMailCgi 1.0] > -----Original Message----- > From: [email protected] > To: [email protected] > Date: Tue May 2 18:28:41 2000 > > <table><textarea> > </textarea></table> > --Mark Lutz (http://rmi.net/~lutz) [PyMailCgi 1.0] > > > > -----Original Message-----
Did you notice the odd characters in the hidden password field of the generated HTML screen shot (Figure 13-14)? 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 intercepted off the Net.
The password is no longer URL-encoded when put in the hidden field,
though, even though it was when it appeared at the end of the smart
link URL. Depending on your encryption module, the password might now
contain non-printable 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 13-15 shows what the mail message captured in Figure 13-13 looks like when viewed in PyMailGui, the client-side Tkinter-based email tool from Chapter 11. PyMailGui doesn’t need to care about things like passing state in URLs or hidden fields (it saves state in Python variables) or escaping HTML and URL strings (there are no browsers, and no network transmission steps once mail is downloaded). It does require Python to be installed on the client, but we’ll get into that in a few pages.
At this point in our hypothetical PyMailCgi web interaction, we are viewing an email message (Figure 13-13) 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 13-9 on the server to perform a reply, forward, or delete operation for the selected message.
Example 13-9. PP2EInternetCgi-WebPyMaiCgionViewSubmit.cgi
#!/usr/bin/python # On submit in mail view window, action selected=(fwd, reply, delete) import cgi, string import commonhtml, secret from externs import pymail, 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 """ quoted = ' -----Original Message----- ' for hdr in ('From', 'To', 'Date'): quoted = quoted + '%s: %s ' % (hdr, getfield(form, hdr)) quoted = quoted + ' ' + getfield(form, 'text') quoted = ' ' + string.replace(quoted, ' ', ' > ') return quoted form = cgi.FieldStorage( ) # parse form or url data user, pswd, site = commonhtml.getstandardpopfields(form) try: if form['action'].value == 'Reply': headers = {'From': mailconfig.myaddress, '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, 'To': '', 'Cc': mailconfig.myaddress, 'Subject': 'Fwd: ' + getfield(form, 'Subject')} commonhtml.editpage('Forward', headers, quotetext(form)) elif form['action'].value == 'Delete': msgnum = int(form['mnum'].value) # or string.atoi, but not eval( ) commonhtml.runsilent( # mnum field is required here pymail.deletemessages, (site, user, secret.decode(pswd), [msgnum], 0) ) commonhtml.confirmationpage('Delete') else: assert 0, '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, 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 pymail
module from
Chapter 11.
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).
If you select Reply as the next action, the message edit page in Figure 13-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 13-4. If all goes well, we’ll receive the same confirmation page we got earlier when writing new mail from scratch (Figure 13-4).
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 the utility
preformatted header line strings (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 callback function handles
three buttons, but the architecture is similar.
Selecting the Delete action on a
message view page and pressing Next will cause the
onViewSubmit
script to immediately delete the
message being viewed. Deletions are performed by calling a reusable
delete utility function coded in Chapter 11; the
call to the utility is wrapped in a
commonhtml.runsilent
call that prevents
print
statements in the utility from showing up in
the HTML reply stream (they are just status messages, not HTML code).
Figure 13-17 shows a delete operation in action.
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. This process is illustrated here:
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 (*
).
Load index (Client to CGI server to POP server): It is next passed from the client to the CGI server, which sends it on to your POP server in order to load a mail index. The client sends only the password, unencrypted.
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 parameters, encrypted and URL-encoded.
Load message (Client to CGI server to POP server): When an email is selected from the list, the password is sent to the next script within the script’s URL; the CGI script decrypts it and passes it on to the POP server to fetch the selected message.
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 and HTML-escaped.
Delete (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 decrypts it and passes it to the POP server to delete.
Along the way, scripts have passed the password between pages as both
a URL parameter and an HTML hidden input field; either way, they have
always passed its encrypted string, and never passed an unencrypted
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 13-18.
Note that you really should click “Back to root page” after a successful deletion -- don’t use your browser’s Back button to return to the message selection list at this point, because the delete has changed the relative numbers of some messages in the list. PyMilGui worked around this problem by only deleting on exit, but PyMailCgi deletes mail immediately since there is no notion of “on exit.” Clicking on a view link in an old selection list page may not bring up the message you think it should, if it comes after a message that was deleted.
This is a property of POP email in general: incoming mail simply adds to the mail list with higher message numbers, but deletions remove mail from arbitrary locations in the list and hence change message numbers for all mail following the ones deleted. Even PyMailGui may get some message numbers wrong if mail is deleted by another program while the GUI is open (e.g., in a second PyMailGui instance). Alternatively, both mailers could delete all email off the server as soon as it is downloaded, such that deletions wouldn’t impact POP identifiers (Microsoft Outlook uses this scheme, for instance), but this requires additional mechanisms for storing deleted email persistently for later access.
One subtlety: for replies and forwards, the
onViewSubmit
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 to see if you can follow dates from page to page.
[108] Technically, again, you should generally
escape &
separators in generated URL links
like 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 the prior
chapter for more details; they aren’t escaped here because
there are no clashes.
[109] 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
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?).
18.119.167.196