paulb@386 | 1 | #!/usr/bin/env python |
paulb@386 | 2 | |
paulb@386 | 3 | """ |
paulb@386 | 4 | Resources for serving static content. |
paulb@403 | 5 | |
paulb@403 | 6 | Copyright (C) 2004, 2005 Paul Boddie <paul@boddie.org.uk> |
paulb@403 | 7 | |
paulb@403 | 8 | This library is free software; you can redistribute it and/or |
paulb@403 | 9 | modify it under the terms of the GNU Lesser General Public |
paulb@403 | 10 | License as published by the Free Software Foundation; either |
paulb@403 | 11 | version 2.1 of the License, or (at your option) any later version. |
paulb@403 | 12 | |
paulb@403 | 13 | This library is distributed in the hope that it will be useful, |
paulb@403 | 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
paulb@403 | 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
paulb@403 | 16 | Lesser General Public License for more details. |
paulb@403 | 17 | |
paulb@403 | 18 | You should have received a copy of the GNU Lesser General Public |
paulb@403 | 19 | License along with this library; if not, write to the Free Software |
paulb@403 | 20 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
paulb@386 | 21 | """ |
paulb@386 | 22 | |
paulb@386 | 23 | from WebStack.Generic import ContentType, EndOfResponse |
paulb@386 | 24 | import os |
paulb@386 | 25 | |
paulb@386 | 26 | class DirectoryResource: |
paulb@386 | 27 | |
paulb@386 | 28 | "A resource serving the contents of a filesystem directory." |
paulb@386 | 29 | |
paulb@386 | 30 | def __init__(self, directory, media_types=None, unrecognised_media_type="application/data"): |
paulb@386 | 31 | |
paulb@386 | 32 | """ |
paulb@386 | 33 | Initialise the resource to serve files from the given 'directory'. |
paulb@386 | 34 | |
paulb@386 | 35 | The optional 'media_types' dictionary can be used to map filename |
paulb@386 | 36 | extensions to media types, where extensions consist of the part of a |
paulb@386 | 37 | name after a "." character (such as "txt", "html"), and where media |
paulb@386 | 38 | types are the usual content descriptions (such as "text/plain" and |
paulb@386 | 39 | "text/html"). |
paulb@386 | 40 | |
paulb@386 | 41 | If 'media_types' contains a mapping from None to a media type, then |
paulb@386 | 42 | this mapping is used when no extension is present on a requested |
paulb@386 | 43 | resource name. |
paulb@386 | 44 | |
paulb@386 | 45 | Where no media type can be found for a resource, a predefined media |
paulb@386 | 46 | type is set which can be overridden by specifying a value for the |
paulb@386 | 47 | optional 'unrecognised_media_type' parameter. |
paulb@386 | 48 | """ |
paulb@386 | 49 | |
paulb@386 | 50 | self.directory = directory |
paulb@386 | 51 | self.media_types = media_types or {} |
paulb@386 | 52 | self.unrecognised_media_type = unrecognised_media_type |
paulb@386 | 53 | |
paulb@386 | 54 | def respond(self, trans): |
paulb@386 | 55 | |
paulb@386 | 56 | "Respond to the given transaction, 'trans', by serving a file." |
paulb@386 | 57 | |
paulb@386 | 58 | parts = trans.get_virtual_path_info().split("/") |
paulb@386 | 59 | filename = parts[1] |
paulb@386 | 60 | out = trans.get_response_stream() |
paulb@386 | 61 | |
paulb@386 | 62 | # Test for the file's existence. |
paulb@386 | 63 | |
paulb@387 | 64 | pathname = os.path.abspath(os.path.join(self.directory, filename)) |
paulb@387 | 65 | if not (pathname.startswith(os.path.join(self.directory, "/")) and os.path.exists(pathname) and os.path.isfile(pathname)): |
paulb@386 | 66 | trans.set_response_code(404) |
paulb@386 | 67 | trans.set_content_type(ContentType("text/plain")) |
paulb@386 | 68 | out.write("Resource '%s' not found." % filename) |
paulb@386 | 69 | raise EndOfResponse |
paulb@386 | 70 | |
paulb@386 | 71 | # Get the extension. |
paulb@386 | 72 | |
paulb@386 | 73 | extension_parts = filename.split(".") |
paulb@386 | 74 | |
paulb@386 | 75 | if len(extension_parts) > 1: |
paulb@386 | 76 | extension = extension_parts[-1] |
paulb@386 | 77 | media_type = self.media_types.get(extension) |
paulb@386 | 78 | else: |
paulb@386 | 79 | media_type = self.media_types.get(None) |
paulb@386 | 80 | |
paulb@386 | 81 | # Set the content type. |
paulb@386 | 82 | |
paulb@386 | 83 | if media_type is not None: |
paulb@386 | 84 | trans.set_content_type(ContentType(media_type)) |
paulb@386 | 85 | else: |
paulb@386 | 86 | trans.set_content_type(ContentType(self.unrecognised_media_type)) |
paulb@386 | 87 | |
paulb@386 | 88 | # Write the file to the client. |
paulb@386 | 89 | |
paulb@386 | 90 | f = open(os.path.join(self.directory, filename), "rb") |
paulb@386 | 91 | out.write(f.read()) |
paulb@386 | 92 | f.close() |
paulb@386 | 93 | |
paulb@386 | 94 | # vim: tabstop=4 expandtab shiftwidth=4 |