1 #!/usr/bin/env python 2 3 """ 4 Request helper classes. 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 """ 22 23 class MessageBodyStream: 24 25 """ 26 A naive stream class, providing a non-blocking stream for transactions when 27 reading the message body. According to the HTTP standard, the following 28 things decide how long the message is: 29 30 * Use of the Content-Length header field (see 4.4 Message Length). 31 * Use of the Transfer-Coding header field (see 3.6 Transfer Codings), 32 particularly when the "chunked" coding is used. 33 34 NOTE: For now, we don't support the Transfer-Coding business. 35 """ 36 37 def __init__(self, stream, headers): 38 39 """ 40 Initialise the object with the given underlying 'stream'. The supplied 41 'headers' in a dictionary-style object are used to examine the nature of 42 the request. 43 """ 44 45 self.stream = stream 46 self.headers = headers 47 self.length = int(headers.get("Content-Length") or 0) 48 49 def read(self, limit=None): 50 51 "Reads all remaining data from the message body." 52 53 if limit is not None: 54 limit = min(limit, self.length) 55 else: 56 limit = self.length 57 data = self.stream.read(limit) 58 self.length = self.length - len(data) 59 return data 60 61 def readline(self): 62 63 "Reads a single line of data from the message body." 64 65 data = [] 66 while self.length > 0: 67 data.append(self.read(1)) 68 if data[-1] == "\n": 69 break 70 return "".join(data) 71 72 def readlines(self): 73 74 """ 75 Reads all remaining data from the message body, splitting it into lines 76 and returning the data as a list of lines. 77 """ 78 79 lines = self.read().split("\n") 80 for i in range(0, len(lines) - 1): 81 lines[i] = lines[i] + "\n" 82 return lines 83 84 def close(self): 85 86 "Closes the stream." 87 88 self.stream.close() 89 90 class Cookie: 91 92 """ 93 A simple cookie class for frameworks which do not return cookies in 94 structured form. 95 """ 96 97 def __init__(self, name, value): 98 self.name = name 99 self.value = value 100 101 def get_storage_items(storage_body): 102 103 """ 104 Return the items (2-tuples of the form key, values) from the 'storage_body'. 105 This is used in conjunction with FieldStorage objects. 106 """ 107 108 items = [] 109 for key in storage_body.keys(): 110 items.append((key, storage_body[key])) 111 return items 112 113 def get_body_fields(field_items, encoding): 114 115 """ 116 Returns a dictionary mapping field names to lists of field values for all 117 entries in the given 'field_items' (2-tuples of the form key, values) using 118 the given 'encoding'. 119 This is used in conjunction with FieldStorage objects. 120 """ 121 122 fields = {} 123 124 for field_name, field_values in field_items: 125 if type(field_values) == type([]): 126 fields[field_name] = [] 127 for field_value in field_values: 128 fields[field_name].append(get_body_field(field_value.value, encoding)) 129 else: 130 fields[field_name] = [get_body_field(field_values.value, encoding)] 131 132 return fields 133 134 def get_body_field(field_str, encoding): 135 136 """ 137 Returns the appropriate value for the given 'field_str' string using the 138 given 'encoding'. 139 """ 140 141 # Detect stray FieldStorage objects (eg. with Webware) or stray FileUpload 142 # objects (eg. with Zope). 143 144 if hasattr(field_str, "value"): 145 return get_body_field(field_str.value, encoding) 146 elif hasattr(field_str, "read"): 147 return field_str.read() 148 elif encoding is not None: 149 try: 150 return unicode(field_str, encoding) 151 except UnicodeError: 152 # NOTE: Hacks to permit graceful failure. 153 try: 154 return unicode(field_str, "iso-8859-1") 155 except UnicodeError: 156 return u"" 157 else: 158 return field_str 159 160 def get_fields_from_query_string(query_string, decoder): 161 162 """ 163 Returns a dictionary mapping field names to lists of values for the data 164 encoded in the given 'query_string'. Use the given 'decoder' function or 165 method to process the URL-encoded values. 166 """ 167 168 fields = {} 169 170 for pair in query_string.split("&"): 171 t = pair.split("=") 172 name = decoder(t[0]) 173 174 if len(t) == 2: 175 value = decoder(t[1]) 176 else: 177 value = "" 178 179 # NOTE: Remove empty names. 180 181 if name: 182 if not fields.has_key(name): 183 fields[name] = [] 184 fields[name].append(value) 185 186 return fields 187 188 def filter_fields(all_fields, fields_from_path): 189 190 """ 191 Taking items from the 'all_fields' dictionary, produce a new dictionary 192 which does not contain items from the 'fields_from_path' dictionary. 193 Return a new dictionary. 194 """ 195 196 fields = {} 197 for field_name, field_values in all_fields.items(): 198 199 # Find the path values for this field (for filtering below). 200 201 if fields_from_path.has_key(field_name): 202 field_from_path_values = fields_from_path[field_name] 203 if type(field_from_path_values) != type([]): 204 field_from_path_values = [field_from_path_values] 205 else: 206 field_from_path_values = [] 207 208 fields[field_name] = [] 209 for field_value in field_values: 210 211 # Filter path values. 212 213 if field_value not in field_from_path_values: 214 fields[field_name].append(field_value) 215 216 # Remove filtered fields. 217 218 if fields[field_name] == []: 219 del fields[field_name] 220 221 return fields 222 223 # vim: tabstop=4 expandtab shiftwidth=4