1 #!/usr/bin/env python 2 3 """ 4 Resources for serving static content. 5 6 Copyright (C) 2004, 2005, 2006 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, 31 unrecognised_media_type="application/data", content_types=None, 32 unrecognised_content_type=None, default_encoding=None, 33 urlencoding="utf-8"): 34 35 """ 36 Initialise the resource to serve files from the given 'directory'. 37 38 The optional 'content_types' dictionary can be used to map filename 39 extensions to content types, where extensions consist of the part of a 40 name after a "." character (such as "txt", "html"), and where content 41 types are typically WebStack.Generic.ContentType objects. 42 43 The optional 'media_types' dictionary can be used to map filename 44 extensions to media types, where extensions consist of the part of a 45 name after a "." character (such as "txt", "html"), and where media 46 types are the usual content descriptions (such as "text/plain" and 47 "text/html"). 48 49 If 'content_types' or 'media_types' contain a mapping from None to a 50 content or media type, then this mapping is used when no extension is 51 present on a requested resource name. 52 53 Where no content or media type can be found for a resource, a 54 predefined media type is set which can be overridden by specifying a 55 value for the optional 'unrecognised_media_type' or for the 56 'unrecognised_content_type' parameter - the latter overriding the former 57 if specified. 58 59 The optional 'default_encoding' is used to specify the character 60 encoding used in any content type produced from a media type (or for 61 the unrecognised media type). If set to None (as is the default), no 62 encoding declaration is produced for file content associated with media 63 types. 64 65 The optional 'urlencoding' is used to decode "URL encoded" character 66 values in the request path, and overrides the default encoding wherever 67 possible. 68 """ 69 70 self.directory = directory 71 self.content_types = content_types or {} 72 self.media_types = media_types or {} 73 self.unrecognised_media_type = unrecognised_media_type 74 self.unrecognised_content_type = unrecognised_content_type 75 self.default_encoding = default_encoding 76 self.urlencoding = urlencoding 77 78 def respond(self, trans): 79 80 "Respond to the given transaction, 'trans', by serving a file." 81 82 parts = trans.get_virtual_path_info(self.urlencoding).split("/") 83 filename = parts[1] 84 out = trans.get_response_stream() 85 86 # Test for the file's existence. 87 88 pathname = os.path.abspath(os.path.join(self.directory, filename)) 89 if not (pathname.startswith(os.path.join(self.directory, "/")) and \ 90 os.path.exists(pathname) and os.path.isfile(pathname)): 91 92 self.not_found(trans, filename) 93 94 # Get the extension. 95 96 extension_parts = filename.split(".") 97 98 if len(extension_parts) > 1: 99 extension = extension_parts[-1] 100 content_type = self.content_types.get(extension) 101 media_type = self.media_types.get(extension) 102 else: 103 content_type = self.content_types.get(None) 104 media_type = self.media_types.get(None) 105 106 # Set the content type. 107 108 if content_type is not None: 109 trans.set_content_type(content_type) 110 elif media_type is not None: 111 trans.set_content_type(ContentType(media_type, self.default_encoding)) 112 elif self.unrecognised_content_type is not None: 113 trans.set_content_type(self.unrecognised_content_type) 114 else: 115 trans.set_content_type( 116 ContentType(self.unrecognised_media_type, self.default_encoding)) 117 118 # Write the file to the client. 119 120 f = open(os.path.join(self.directory, filename), "rb") 121 out.write(f.read()) 122 f.close() 123 124 def not_found(self, trans, filename): 125 126 """ 127 Send the "not found" response using the given transaction, 'trans', and 128 specifying the given 'filename' (if appropriate). 129 """ 130 131 trans.set_response_code(404) 132 trans.set_content_type(ContentType("text/plain")) 133 out = trans.get_response_stream() 134 out.write("Resource '%s' not found." % filename) 135 raise EndOfResponse 136 137 class FileResource: 138 139 "A file serving resource." 140 141 def __init__(self, filename, content_type): 142 self.filename = filename 143 self.content_type = content_type 144 145 def respond(self, trans): 146 trans.set_content_type(self.content_type) 147 f = open(self.filename, "rb") 148 trans.get_response_stream().write(f.read()) 149 f.close() 150 151 # vim: tabstop=4 expandtab shiftwidth=4