12da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Copyright 2011, Google Inc.
22da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# All rights reserved.
32da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
42da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Redistribution and use in source and binary forms, with or without
52da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# modification, are permitted provided that the following conditions are
62da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# met:
72da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
82da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#     * Redistributions of source code must retain the above copyright
92da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# notice, this list of conditions and the following disclaimer.
102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#     * Redistributions in binary form must reproduce the above
112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# copyright notice, this list of conditions and the following disclaimer
122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# in the documentation and/or other materials provided with the
132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# distribution.
142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#     * Neither the name of Google Inc. nor the names of its
152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# contributors may be used to endorse or promote products derived from
162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# this software without specific prior written permission.
172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis#
182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis"""Utilities for parsing and formatting headers that follow the grammar defined
322da489cd246702bee5938545b18a6f710ed214bcJamie Gennisin HTTP RFC http://www.ietf.org/rfc/rfc2616.txt.
332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis"""
342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
362da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport urlparse
372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis_SEPARATORS = '()<>@,;:\\"/[]?={} \t'
402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
422da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef _is_char(c):
432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns true iff c is in CHAR as specified in HTTP RFC."""
442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return ord(c) <= 127
462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
482da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef _is_ctl(c):
492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Returns true iff c is in CTL as specified in HTTP RFC."""
502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return ord(c) <= 31 or ord(c) == 127
522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
542da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass ParsingState(object):
552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    def __init__(self, data):
572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self.data = data
582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self.head = 0
592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
612da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef peek(state, pos=0):
622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Peeks the character at pos from the head of data."""
632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if state.head + pos >= len(state.data):
652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return None
662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return state.data[state.head + pos]
682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
702da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef consume(state, amount=1):
712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Consumes specified amount of bytes from the head and returns the
722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    consumed bytes. If there's not enough bytes to consume, returns None.
732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if state.head + amount > len(state.data):
762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return None
772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    result = state.data[state.head:state.head + amount]
792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    state.head = state.head + amount
802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return result
812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
832da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef consume_string(state, expected):
842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Given a parsing state and a expected string, consumes the string from
852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    the head. Returns True if consumed successfully. Otherwise, returns
862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    False.
872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    pos = 0
902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    for c in expected:
922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if c != peek(state, pos):
932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            return False
942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        pos += 1
952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    consume(state, pos)
972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return True
982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1002da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef consume_lws(state):
1012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Consumes a LWS from the head. Returns True if any LWS is consumed.
1022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    Otherwise, returns False.
1032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    LWS = [CRLF] 1*( SP | HT )
1052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    original_head = state.head
1082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    consume_string(state, '\r\n')
1102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    pos = 0
1122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    while True:
1142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        c = peek(state, pos)
1152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if c == ' ' or c == '\t':
1162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            pos += 1
1172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        else:
1182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if pos == 0:
1192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                state.head = original_head
1202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                return False
1212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            else:
1222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                consume(state, pos)
1232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                return True
1242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1262da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef consume_lwses(state):
1272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Consumes *LWS from the head."""
1282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    while consume_lws(state):
1302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        pass
1312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1332da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef consume_token(state):
1342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Consumes a token from the head. Returns the token or None if no token
1352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    was found.
1362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    pos = 0
1392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    while True:
1412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        c = peek(state, pos)
1422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if c is None or c in _SEPARATORS or _is_ctl(c) or not _is_char(c):
1432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if pos == 0:
1442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                return None
1452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            return consume(state, pos)
1472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        else:
1482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            pos += 1
1492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1512da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef consume_token_or_quoted_string(state):
1522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Consumes a token or a quoted-string, and returns the token or unquoted
1532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    string. If no token or quoted-string was found, returns None.
1542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """
1552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    original_head = state.head
1572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if not consume_string(state, '"'):
1592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return consume_token(state)
1602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    result = []
1622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    expect_quoted_pair = False
1642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    while True:
1662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if not expect_quoted_pair and consume_lws(state):
1672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            result.append(' ')
1682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            continue
1692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        c = consume(state)
1712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if c is None:
1722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            # quoted-string is not enclosed with double quotation
1732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            state.head = original_head
1742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            return None
1752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        elif expect_quoted_pair:
1762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            expect_quoted_pair = False
1772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if _is_char(c):
1782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                result.append(c)
1792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            else:
1802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                # Non CHAR character found in quoted-pair
1812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                state.head = original_head
1822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                return None
1832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        elif c == '\\':
1842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            expect_quoted_pair = True
1852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        elif c == '"':
1862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            return ''.join(result)
1872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        elif _is_ctl(c):
1882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            # Invalid character %r found in qdtext
1892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            state.head = original_head
1902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            return None
1912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        else:
1922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            result.append(c)
1932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1952da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef quote_if_necessary(s):
1962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Quotes arbitrary string into quoted-string."""
1972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    quote = False
1992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if s == '':
2002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return '""'
2012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    result = []
2032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    for c in s:
2042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if c == '"' or c in _SEPARATORS or _is_ctl(c) or not _is_char(c):
2052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            quote = True
2062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if c == '"' or _is_ctl(c):
2082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            result.append('\\' + c)
2092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        else:
2102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            result.append(c)
2112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if quote:
2132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return '"' + ''.join(result) + '"'
2142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    else:
2152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return ''.join(result)
2162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2182da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef parse_uri(uri):
2192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    """Parse absolute URI then return host, port and resource."""
2202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    parsed = urlparse.urlsplit(uri)
2222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if parsed.scheme != 'wss' and parsed.scheme != 'ws':
2232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # |uri| must be a relative URI.
2242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # TODO(toyoshim): Should validate |uri|.
2252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return None, None, uri
2262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if parsed.hostname is None:
2282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return None, None, None
2292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    port = None
2312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    try:
2322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        port = parsed.port
2332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    except ValueError, e:
2342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # port property cause ValueError on invalid null port description like
2352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        # 'ws://host:/path'.
2362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        return None, None, None
2372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if port is None:
2392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if parsed.scheme == 'ws':
2402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            port = 80
2412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        else:
2422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            port = 443
2432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    path = parsed.path
2452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if not path:
2462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        path += '/'
2472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if parsed.query:
2482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        path += '?' + parsed.query
2492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if parsed.fragment:
2502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        path += '#' + parsed.fragment
2512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return parsed.hostname, port, path
2532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2552da489cd246702bee5938545b18a6f710ed214bcJamie Gennistry:
2562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    urlparse.uses_netloc.index('ws')
2572da489cd246702bee5938545b18a6f710ed214bcJamie Gennisexcept ValueError, e:
2582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    # urlparse in Python2.5.1 doesn't have 'ws' and 'wss' entries.
2592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    urlparse.uses_netloc.append('ws')
2602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    urlparse.uses_netloc.append('wss')
2612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# vi:sts=4 sw=4 et
264