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 field_name = decode_value(field_name, encoding) 126 127 if type(field_values) == type([]): 128 fields[field_name] = [] 129 for field_value in field_values: 130 fields[field_name].append(get_body_field_or_file(field_value, encoding)) 131 else: 132 fields[field_name] = [get_body_field_or_file(field_values, encoding)] 133 134 return fields 135 136 def get_body_field_or_file(field_value, encoding): 137 138 """ 139 Returns the appropriate value for the given 'field_value' either for a 140 normal form field (thus employing the given 'encoding') or for a file 141 upload field (returning a plain string). 142 """ 143 144 if hasattr(field_value, "headers") and field_value.headers.has_key("content-type"): 145 146 # Detect stray FileUpload objects (eg. with Zope). 147 148 if hasattr(field_value, "read"): 149 return field_value.read() 150 else: 151 return field_value.value 152 else: 153 return get_body_field(field_value, encoding) 154 155 def get_body_field(field_str, encoding): 156 157 """ 158 Returns the appropriate value for the given 'field_str' string using the 159 given 'encoding'. 160 """ 161 162 # Detect stray FieldStorage objects (eg. with Webware). 163 164 if hasattr(field_str, "value"): 165 return get_body_field(field_str.value, encoding) 166 else: 167 return decode_value(field_str, encoding) 168 169 def decode_value(s, encoding): 170 if encoding is not None: 171 try: 172 return unicode(s, encoding) 173 except UnicodeError: 174 pass 175 # NOTE: Hacks to permit graceful failure. 176 return unicode(s, "iso-8859-1") 177 178 def get_fields_from_query_string(query_string, decoder): 179 180 """ 181 Returns a dictionary mapping field names to lists of values for the data 182 encoded in the given 'query_string'. Use the given 'decoder' function or 183 method to process the URL-encoded values. 184 """ 185 186 fields = {} 187 188 for pair in query_string.split("&"): 189 t = pair.split("=") 190 name = decoder(t[0]) 191 192 if len(t) == 2: 193 value = decoder(t[1]) 194 else: 195 value = "" 196 197 # NOTE: Remove empty names. 198 199 if name: 200 if not fields.has_key(name): 201 fields[name] = [] 202 fields[name].append(value) 203 204 return fields 205 206 def filter_fields(all_fields, fields_from_path): 207 208 """ 209 Taking items from the 'all_fields' dictionary, produce a new dictionary 210 which does not contain items from the 'fields_from_path' dictionary. 211 Return a new dictionary. 212 """ 213 214 fields = {} 215 for field_name, field_values in all_fields.items(): 216 217 # Find the path values for this field (for filtering below). 218 219 if fields_from_path.has_key(field_name): 220 field_from_path_values = fields_from_path[field_name] 221 if type(field_from_path_values) != type([]): 222 field_from_path_values = [field_from_path_values] 223 else: 224 field_from_path_values = [] 225 226 fields[field_name] = [] 227 for field_value in field_values: 228 229 # Filter path values. 230 231 if field_value not in field_from_path_values: 232 fields[field_name].append(field_value) 233 234 # Remove filtered fields. 235 236 if fields[field_name] == []: 237 del fields[field_name] 238 239 return fields 240 241 # vim: tabstop=4 expandtab shiftwidth=4