That makes for a flexible tool, but it’s also potentially dangerous if you are running a
server on a remote machine. What if we don’t want users to be able to view some files
on the server? For example, in the next chapter, we will implement an encryption
module for email account passwords. On our server, it is in fact addressable as
PyMailCgi/cgi-bin/secret.py. Allowing users to view that module’s source code would
make encrypted passwords shipped over the Net much more vulnerable to cracking.
To minimize this potential, the getfile script keeps a list, privates, of restricted file-
names, and uses the os.path.samefile built-in to check whether a requested filename
path points to one of the names on privates. The samefile call checks to see whether
the os.stat built-in returns the same identifying information (device and inode num-
bers) for both file paths. As a result, pathnames that look different syntactically but
reference the same file are treated as identical. For example, on the server used for this
book’s second edition, the following paths to the encryptor module were different
strings, but yielded a true result from os.path.samefile:
../PyMailCgi/secret.py
/home/crew/lutz/public_html/PyMailCgi/secret.py
Unfortunately, the os.path.samefile call is supported on Unix, Linux, and Macs, but
not on Windows. To emulate its behavior in Windows, we expand file paths to be
absolute, convert to a common case, and compare (I shortened paths in the following
with ... for display here):
>>> import os
>>> os.path.samefile
AttributeError: 'module' object has no attribute 'samefile'
>>> os.getcwd()
'C:\\...\\PP4E\\dev\\Examples\\PP4E\\Internet\\Web'
>>>
>>> x = os.path.abspath('../Web/PYMailCgi/cgi-bin/secret.py').lower()
>>> y = os.path.abspath('PyMailCgi/cgi-bin/secret.py').lower()
>>> z = os.path.abspath('./PYMailCGI/cgi-bin/../cgi-bin/SECRET.py').lower()
>>> x
'c:\\...\\pp4e\\dev\\examples\\pp4e\\internet\\web\\pymailcgi\\cgi-bin\\secret.py'
>>> y
'c:\\...\\pp4e\\dev\\examples\\pp4e\\internet\\web\\pymailcgi\\cgi-bin\\secret.py'
>>> z
'c:\\...\\pp4e\\dev\\examples\\pp4e\\internet\\web\\pymailcgi\\cgi-bin\\secret.py'
>>>
>>> x == y, y == z
(True, True)
Accessing any of the three paths expanded here generates an error page like that in
Figure 15-31. Notice how the names of secret files are global data in this module, on
the assumption that they pertain to files viewable across an entire site; though we could
allow for customization per site, changing the script’s globals per site is likely just as
convenient as changing a per-site customization files.
Also notice that bona fide file errors are handled differently. Permission problems and
attempts to access nonexistent files, for example, are trapped by a different exception
1216 | Chapter 15: Server-Side Scripting