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