1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/testing/mochitest/pywebsocket/mod_pywebsocket/http_header_util.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,263 @@ 1.4 +# Copyright 2011, Google Inc. 1.5 +# All rights reserved. 1.6 +# 1.7 +# Redistribution and use in source and binary forms, with or without 1.8 +# modification, are permitted provided that the following conditions are 1.9 +# met: 1.10 +# 1.11 +# * Redistributions of source code must retain the above copyright 1.12 +# notice, this list of conditions and the following disclaimer. 1.13 +# * Redistributions in binary form must reproduce the above 1.14 +# copyright notice, this list of conditions and the following disclaimer 1.15 +# in the documentation and/or other materials provided with the 1.16 +# distribution. 1.17 +# * Neither the name of Google Inc. nor the names of its 1.18 +# contributors may be used to endorse or promote products derived from 1.19 +# this software without specific prior written permission. 1.20 +# 1.21 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 1.22 +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 1.23 +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 1.24 +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 1.25 +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 1.26 +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 1.27 +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 1.28 +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 1.29 +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 1.30 +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 1.31 +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 1.32 + 1.33 + 1.34 +"""Utilities for parsing and formatting headers that follow the grammar defined 1.35 +in HTTP RFC http://www.ietf.org/rfc/rfc2616.txt. 1.36 +""" 1.37 + 1.38 + 1.39 +import urlparse 1.40 + 1.41 + 1.42 +_SEPARATORS = '()<>@,;:\\"/[]?={} \t' 1.43 + 1.44 + 1.45 +def _is_char(c): 1.46 + """Returns true iff c is in CHAR as specified in HTTP RFC.""" 1.47 + 1.48 + return ord(c) <= 127 1.49 + 1.50 + 1.51 +def _is_ctl(c): 1.52 + """Returns true iff c is in CTL as specified in HTTP RFC.""" 1.53 + 1.54 + return ord(c) <= 31 or ord(c) == 127 1.55 + 1.56 + 1.57 +class ParsingState(object): 1.58 + 1.59 + def __init__(self, data): 1.60 + self.data = data 1.61 + self.head = 0 1.62 + 1.63 + 1.64 +def peek(state, pos=0): 1.65 + """Peeks the character at pos from the head of data.""" 1.66 + 1.67 + if state.head + pos >= len(state.data): 1.68 + return None 1.69 + 1.70 + return state.data[state.head + pos] 1.71 + 1.72 + 1.73 +def consume(state, amount=1): 1.74 + """Consumes specified amount of bytes from the head and returns the 1.75 + consumed bytes. If there's not enough bytes to consume, returns None. 1.76 + """ 1.77 + 1.78 + if state.head + amount > len(state.data): 1.79 + return None 1.80 + 1.81 + result = state.data[state.head:state.head + amount] 1.82 + state.head = state.head + amount 1.83 + return result 1.84 + 1.85 + 1.86 +def consume_string(state, expected): 1.87 + """Given a parsing state and a expected string, consumes the string from 1.88 + the head. Returns True if consumed successfully. Otherwise, returns 1.89 + False. 1.90 + """ 1.91 + 1.92 + pos = 0 1.93 + 1.94 + for c in expected: 1.95 + if c != peek(state, pos): 1.96 + return False 1.97 + pos += 1 1.98 + 1.99 + consume(state, pos) 1.100 + return True 1.101 + 1.102 + 1.103 +def consume_lws(state): 1.104 + """Consumes a LWS from the head. Returns True if any LWS is consumed. 1.105 + Otherwise, returns False. 1.106 + 1.107 + LWS = [CRLF] 1*( SP | HT ) 1.108 + """ 1.109 + 1.110 + original_head = state.head 1.111 + 1.112 + consume_string(state, '\r\n') 1.113 + 1.114 + pos = 0 1.115 + 1.116 + while True: 1.117 + c = peek(state, pos) 1.118 + if c == ' ' or c == '\t': 1.119 + pos += 1 1.120 + else: 1.121 + if pos == 0: 1.122 + state.head = original_head 1.123 + return False 1.124 + else: 1.125 + consume(state, pos) 1.126 + return True 1.127 + 1.128 + 1.129 +def consume_lwses(state): 1.130 + """Consumes *LWS from the head.""" 1.131 + 1.132 + while consume_lws(state): 1.133 + pass 1.134 + 1.135 + 1.136 +def consume_token(state): 1.137 + """Consumes a token from the head. Returns the token or None if no token 1.138 + was found. 1.139 + """ 1.140 + 1.141 + pos = 0 1.142 + 1.143 + while True: 1.144 + c = peek(state, pos) 1.145 + if c is None or c in _SEPARATORS or _is_ctl(c) or not _is_char(c): 1.146 + if pos == 0: 1.147 + return None 1.148 + 1.149 + return consume(state, pos) 1.150 + else: 1.151 + pos += 1 1.152 + 1.153 + 1.154 +def consume_token_or_quoted_string(state): 1.155 + """Consumes a token or a quoted-string, and returns the token or unquoted 1.156 + string. If no token or quoted-string was found, returns None. 1.157 + """ 1.158 + 1.159 + original_head = state.head 1.160 + 1.161 + if not consume_string(state, '"'): 1.162 + return consume_token(state) 1.163 + 1.164 + result = [] 1.165 + 1.166 + expect_quoted_pair = False 1.167 + 1.168 + while True: 1.169 + if not expect_quoted_pair and consume_lws(state): 1.170 + result.append(' ') 1.171 + continue 1.172 + 1.173 + c = consume(state) 1.174 + if c is None: 1.175 + # quoted-string is not enclosed with double quotation 1.176 + state.head = original_head 1.177 + return None 1.178 + elif expect_quoted_pair: 1.179 + expect_quoted_pair = False 1.180 + if _is_char(c): 1.181 + result.append(c) 1.182 + else: 1.183 + # Non CHAR character found in quoted-pair 1.184 + state.head = original_head 1.185 + return None 1.186 + elif c == '\\': 1.187 + expect_quoted_pair = True 1.188 + elif c == '"': 1.189 + return ''.join(result) 1.190 + elif _is_ctl(c): 1.191 + # Invalid character %r found in qdtext 1.192 + state.head = original_head 1.193 + return None 1.194 + else: 1.195 + result.append(c) 1.196 + 1.197 + 1.198 +def quote_if_necessary(s): 1.199 + """Quotes arbitrary string into quoted-string.""" 1.200 + 1.201 + quote = False 1.202 + if s == '': 1.203 + return '""' 1.204 + 1.205 + result = [] 1.206 + for c in s: 1.207 + if c == '"' or c in _SEPARATORS or _is_ctl(c) or not _is_char(c): 1.208 + quote = True 1.209 + 1.210 + if c == '"' or _is_ctl(c): 1.211 + result.append('\\' + c) 1.212 + else: 1.213 + result.append(c) 1.214 + 1.215 + if quote: 1.216 + return '"' + ''.join(result) + '"' 1.217 + else: 1.218 + return ''.join(result) 1.219 + 1.220 + 1.221 +def parse_uri(uri): 1.222 + """Parse absolute URI then return host, port and resource.""" 1.223 + 1.224 + parsed = urlparse.urlsplit(uri) 1.225 + if parsed.scheme != 'wss' and parsed.scheme != 'ws': 1.226 + # |uri| must be a relative URI. 1.227 + # TODO(toyoshim): Should validate |uri|. 1.228 + return None, None, uri 1.229 + 1.230 + if parsed.hostname is None: 1.231 + return None, None, None 1.232 + 1.233 + port = None 1.234 + try: 1.235 + port = parsed.port 1.236 + except ValueError, e: 1.237 + # port property cause ValueError on invalid null port description like 1.238 + # 'ws://host:/path'. 1.239 + return None, None, None 1.240 + 1.241 + if port is None: 1.242 + if parsed.scheme == 'ws': 1.243 + port = 80 1.244 + else: 1.245 + port = 443 1.246 + 1.247 + path = parsed.path 1.248 + if not path: 1.249 + path += '/' 1.250 + if parsed.query: 1.251 + path += '?' + parsed.query 1.252 + if parsed.fragment: 1.253 + path += '#' + parsed.fragment 1.254 + 1.255 + return parsed.hostname, port, path 1.256 + 1.257 + 1.258 +try: 1.259 + urlparse.uses_netloc.index('ws') 1.260 +except ValueError, e: 1.261 + # urlparse in Python2.5.1 doesn't have 'ws' and 'wss' entries. 1.262 + urlparse.uses_netloc.append('ws') 1.263 + urlparse.uses_netloc.append('wss') 1.264 + 1.265 + 1.266 +# vi:sts=4 sw=4 et