blob: 30de6998c2ecf362e1cf6baec7c330ad3475e824 [file] [log] [blame]
Guido van Rossume7b146f2000-02-04 15:28:42 +00001"""Parse (absolute and relative) URLs.
2
3See RFC 1808: "Relative Uniform Resource Locators", by R. Fielding,
4UC Irvine, June 1995.
5"""
Guido van Rossum23cb2a81994-09-12 10:36:35 +00006
Fred Drakef606e8d2002-10-16 21:21:39 +00007__all__ = ["urlparse", "urlunparse", "urljoin", "urldefrag",
8 "urlsplit", "urlunsplit"]
Skip Montanaro40fc1602001-03-01 04:27:19 +00009
Guido van Rossum23cb2a81994-09-12 10:36:35 +000010# A classification of schemes ('' means apply by default)
Raymond Hettinger156c49a2004-05-07 05:50:35 +000011uses_relative = ['ftp', 'http', 'gopher', 'nntp', 'imap',
Georg Brandl89f35ac2006-01-20 17:24:23 +000012 'wais', 'file', 'https', 'shttp', 'mms',
13 'prospero', 'rtsp', 'rtspu', '', 'sftp']
Raymond Hettinger156c49a2004-05-07 05:50:35 +000014uses_netloc = ['ftp', 'http', 'gopher', 'nntp', 'telnet',
Georg Brandl89f35ac2006-01-20 17:24:23 +000015 'imap', 'wais', 'file', 'mms', 'https', 'shttp',
16 'snews', 'prospero', 'rtsp', 'rtspu', 'rsync', '',
17 'svn', 'svn+ssh', 'sftp']
Raymond Hettinger156c49a2004-05-07 05:50:35 +000018non_hierarchical = ['gopher', 'hdl', 'mailto', 'news',
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000019 'telnet', 'wais', 'imap', 'snews', 'sip', 'sips']
Raymond Hettinger156c49a2004-05-07 05:50:35 +000020uses_params = ['ftp', 'hdl', 'prospero', 'http', 'imap',
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000021 'https', 'shttp', 'rtsp', 'rtspu', 'sip', 'sips',
Georg Brandl89f35ac2006-01-20 17:24:23 +000022 'mms', '', 'sftp']
Raymond Hettinger156c49a2004-05-07 05:50:35 +000023uses_query = ['http', 'wais', 'imap', 'https', 'shttp', 'mms',
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000024 'gopher', 'rtsp', 'rtspu', 'sip', 'sips', '']
Raymond Hettinger156c49a2004-05-07 05:50:35 +000025uses_fragment = ['ftp', 'hdl', 'http', 'gopher', 'news',
Georg Brandl89f35ac2006-01-20 17:24:23 +000026 'nntp', 'wais', 'https', 'shttp', 'snews',
27 'file', 'prospero', '']
Guido van Rossum23cb2a81994-09-12 10:36:35 +000028
29# Characters valid in scheme names
Guido van Rossumfad81f02000-12-19 16:48:13 +000030scheme_chars = ('abcdefghijklmnopqrstuvwxyz'
31 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
32 '0123456789'
33 '+-.')
Guido van Rossum23cb2a81994-09-12 10:36:35 +000034
Guido van Rossum74495401997-07-14 19:08:15 +000035MAX_CACHE_SIZE = 20
Guido van Rossum3fd32ec1996-05-28 23:54:24 +000036_parse_cache = {}
37
38def clear_cache():
Tim Peterse1190062001-01-15 03:34:38 +000039 """Clear the parse cache."""
Christian Heimesa62da1d2008-01-12 19:39:10 +000040 _parse_cache.clear()
Guido van Rossum3fd32ec1996-05-28 23:54:24 +000041
42
Christian Heimesa62da1d2008-01-12 19:39:10 +000043class ResultMixin(object):
44 """Shared methods for the parsed result objects."""
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000045
46 @property
47 def username(self):
48 netloc = self.netloc
49 if "@" in netloc:
Christian Heimesfaf2f632008-01-06 16:59:19 +000050 userinfo = netloc.rsplit("@", 1)[0]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000051 if ":" in userinfo:
52 userinfo = userinfo.split(":", 1)[0]
53 return userinfo
54 return None
55
56 @property
57 def password(self):
58 netloc = self.netloc
59 if "@" in netloc:
Christian Heimesfaf2f632008-01-06 16:59:19 +000060 userinfo = netloc.rsplit("@", 1)[0]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000061 if ":" in userinfo:
62 return userinfo.split(":", 1)[1]
63 return None
64
65 @property
66 def hostname(self):
67 netloc = self.netloc
68 if "@" in netloc:
Christian Heimesfaf2f632008-01-06 16:59:19 +000069 netloc = netloc.rsplit("@", 1)[1]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000070 if ":" in netloc:
71 netloc = netloc.split(":", 1)[0]
72 return netloc.lower() or None
73
74 @property
75 def port(self):
76 netloc = self.netloc
77 if "@" in netloc:
Christian Heimesfaf2f632008-01-06 16:59:19 +000078 netloc = netloc.rsplit("@", 1)[1]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000079 if ":" in netloc:
80 port = netloc.split(":", 1)[1]
81 return int(port, 10)
82 return None
83
Christian Heimesa62da1d2008-01-12 19:39:10 +000084from collections import namedtuple
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000085
Christian Heimesa62da1d2008-01-12 19:39:10 +000086class SplitResult(namedtuple('SplitResult', 'scheme netloc path query fragment'), ResultMixin):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000087
88 __slots__ = ()
89
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000090 def geturl(self):
91 return urlunsplit(self)
92
93
Christian Heimesa62da1d2008-01-12 19:39:10 +000094class ParseResult(namedtuple('ParseResult', 'scheme netloc path params query fragment'), ResultMixin):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000095
96 __slots__ = ()
97
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000098 def geturl(self):
99 return urlunparse(self)
100
101
102def urlparse(url, scheme='', allow_fragments=True):
Tim Peterse1190062001-01-15 03:34:38 +0000103 """Parse a URL into 6 components:
104 <scheme>://<netloc>/<path>;<params>?<query>#<fragment>
105 Return a 6-tuple: (scheme, netloc, path, params, query, fragment).
106 Note that we don't break the components up in smaller bits
107 (e.g. netloc is a single string) and we don't expand % escapes."""
Fred Drake5751a222001-11-16 02:52:57 +0000108 tuple = urlsplit(url, scheme, allow_fragments)
109 scheme, netloc, url, query, fragment = tuple
110 if scheme in uses_params and ';' in url:
111 url, params = _splitparams(url)
112 else:
113 params = ''
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000114 return ParseResult(scheme, netloc, url, params, query, fragment)
Fred Drake5751a222001-11-16 02:52:57 +0000115
116def _splitparams(url):
117 if '/' in url:
118 i = url.find(';', url.rfind('/'))
119 if i < 0:
120 return url, ''
121 else:
122 i = url.find(';')
123 return url[:i], url[i+1:]
124
Johannes Gijsbers41e4faa2005-01-09 15:29:10 +0000125def _splitnetloc(url, start=0):
Christian Heimesfaf2f632008-01-06 16:59:19 +0000126 delim = len(url) # position of end of domain part of url, default is end
127 for c in '/?#': # look for delimiters; the order is NOT important
128 wdelim = url.find(c, start) # find first of this delim
129 if wdelim >= 0: # if found
130 delim = min(delim, wdelim) # use earliest delim position
131 return url[start:delim], url[delim:] # return (domain, rest)
Johannes Gijsbers41e4faa2005-01-09 15:29:10 +0000132
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000133def urlsplit(url, scheme='', allow_fragments=True):
Fred Drake5751a222001-11-16 02:52:57 +0000134 """Parse a URL into 5 components:
135 <scheme>://<netloc>/<path>?<query>#<fragment>
136 Return a 5-tuple: (scheme, netloc, path, query, fragment).
137 Note that we don't break the components up in smaller bits
138 (e.g. netloc is a single string) and we don't expand % escapes."""
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000139 allow_fragments = bool(allow_fragments)
Christian Heimes38053212007-12-14 01:24:44 +0000140 key = url, scheme, allow_fragments, type(url), type(scheme)
Tim Peterse1190062001-01-15 03:34:38 +0000141 cached = _parse_cache.get(key, None)
142 if cached:
143 return cached
144 if len(_parse_cache) >= MAX_CACHE_SIZE: # avoid runaway growth
145 clear_cache()
Fred Drake5751a222001-11-16 02:52:57 +0000146 netloc = query = fragment = ''
Tim Peterse1190062001-01-15 03:34:38 +0000147 i = url.find(':')
148 if i > 0:
149 if url[:i] == 'http': # optimize the common case
150 scheme = url[:i].lower()
151 url = url[i+1:]
152 if url[:2] == '//':
Johannes Gijsbers41e4faa2005-01-09 15:29:10 +0000153 netloc, url = _splitnetloc(url, 2)
Fred Drake5751a222001-11-16 02:52:57 +0000154 if allow_fragments and '#' in url:
155 url, fragment = url.split('#', 1)
156 if '?' in url:
157 url, query = url.split('?', 1)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000158 v = SplitResult(scheme, netloc, url, query, fragment)
159 _parse_cache[key] = v
160 return v
Tim Peterse1190062001-01-15 03:34:38 +0000161 for c in url[:i]:
162 if c not in scheme_chars:
163 break
164 else:
165 scheme, url = url[:i].lower(), url[i+1:]
Johannes Gijsbers41e4faa2005-01-09 15:29:10 +0000166 if scheme in uses_netloc and url[:2] == '//':
167 netloc, url = _splitnetloc(url, 2)
Fred Drake5751a222001-11-16 02:52:57 +0000168 if allow_fragments and scheme in uses_fragment and '#' in url:
169 url, fragment = url.split('#', 1)
170 if scheme in uses_query and '?' in url:
171 url, query = url.split('?', 1)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000172 v = SplitResult(scheme, netloc, url, query, fragment)
173 _parse_cache[key] = v
174 return v
Guido van Rossum23cb2a81994-09-12 10:36:35 +0000175
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000176def urlunparse(components):
Tim Peterse1190062001-01-15 03:34:38 +0000177 """Put a parsed URL back together again. This may result in a
178 slightly different, but equivalent URL, if the URL that was parsed
179 originally had redundant delimiters, e.g. a ? with an empty query
180 (the draft states that these are equivalent)."""
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000181 scheme, netloc, url, params, query, fragment = components
Fred Drake5751a222001-11-16 02:52:57 +0000182 if params:
183 url = "%s;%s" % (url, params)
184 return urlunsplit((scheme, netloc, url, query, fragment))
185
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000186def urlunsplit(components):
187 scheme, netloc, url, query, fragment = components
Guido van Rossumbbc05682002-10-14 19:59:54 +0000188 if netloc or (scheme and scheme in uses_netloc and url[:2] != '//'):
Tim Peterse1190062001-01-15 03:34:38 +0000189 if url and url[:1] != '/': url = '/' + url
190 url = '//' + (netloc or '') + url
191 if scheme:
192 url = scheme + ':' + url
Tim Peterse1190062001-01-15 03:34:38 +0000193 if query:
194 url = url + '?' + query
195 if fragment:
196 url = url + '#' + fragment
197 return url
Guido van Rossum23cb2a81994-09-12 10:36:35 +0000198
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000199def urljoin(base, url, allow_fragments=True):
Tim Peterse1190062001-01-15 03:34:38 +0000200 """Join a base URL and a possibly relative URL to form an absolute
201 interpretation of the latter."""
202 if not base:
203 return url
204 if not url:
205 return base
206 bscheme, bnetloc, bpath, bparams, bquery, bfragment = \
207 urlparse(base, '', allow_fragments)
208 scheme, netloc, path, params, query, fragment = \
209 urlparse(url, bscheme, allow_fragments)
210 if scheme != bscheme or scheme not in uses_relative:
211 return url
212 if scheme in uses_netloc:
213 if netloc:
214 return urlunparse((scheme, netloc, path,
215 params, query, fragment))
216 netloc = bnetloc
217 if path[:1] == '/':
218 return urlunparse((scheme, netloc, path,
219 params, query, fragment))
Brett Cannon8da2a522003-10-12 04:29:10 +0000220 if not (path or params or query):
Tim Peterse1190062001-01-15 03:34:38 +0000221 return urlunparse((scheme, netloc, bpath,
Brett Cannon8da2a522003-10-12 04:29:10 +0000222 bparams, bquery, fragment))
Tim Peterse1190062001-01-15 03:34:38 +0000223 segments = bpath.split('/')[:-1] + path.split('/')
224 # XXX The stuff below is bogus in various ways...
225 if segments[-1] == '.':
226 segments[-1] = ''
227 while '.' in segments:
228 segments.remove('.')
229 while 1:
230 i = 1
231 n = len(segments) - 1
232 while i < n:
233 if (segments[i] == '..'
234 and segments[i-1] not in ('', '..')):
235 del segments[i-1:i+1]
236 break
237 i = i+1
238 else:
239 break
240 if segments == ['', '..']:
241 segments[-1] = ''
242 elif len(segments) >= 2 and segments[-1] == '..':
243 segments[-2:] = ['']
244 return urlunparse((scheme, netloc, '/'.join(segments),
245 params, query, fragment))
Guido van Rossum23cb2a81994-09-12 10:36:35 +0000246
Guido van Rossum3fd32ec1996-05-28 23:54:24 +0000247def urldefrag(url):
Tim Peterse1190062001-01-15 03:34:38 +0000248 """Removes any existing fragment from URL.
Guido van Rossum3fd32ec1996-05-28 23:54:24 +0000249
Tim Peterse1190062001-01-15 03:34:38 +0000250 Returns a tuple of the defragmented URL and the fragment. If
251 the URL contained no fragments, the second element is the
252 empty string.
253 """
Fred Drake5751a222001-11-16 02:52:57 +0000254 if '#' in url:
255 s, n, p, a, q, frag = urlparse(url)
256 defrag = urlunparse((s, n, p, a, q, ''))
257 return defrag, frag
258 else:
259 return url, ''
Guido van Rossum3fd32ec1996-05-28 23:54:24 +0000260
261
Guido van Rossum23cb2a81994-09-12 10:36:35 +0000262test_input = """
263 http://a/b/c/d
264
265 g:h = <URL:g:h>
266 http:g = <URL:http://a/b/c/g>
267 http: = <URL:http://a/b/c/d>
268 g = <URL:http://a/b/c/g>
269 ./g = <URL:http://a/b/c/g>
270 g/ = <URL:http://a/b/c/g/>
271 /g = <URL:http://a/g>
272 //g = <URL:http://g>
273 ?y = <URL:http://a/b/c/d?y>
274 g?y = <URL:http://a/b/c/g?y>
275 g?y/./x = <URL:http://a/b/c/g?y/./x>
276 . = <URL:http://a/b/c/>
277 ./ = <URL:http://a/b/c/>
278 .. = <URL:http://a/b/>
279 ../ = <URL:http://a/b/>
280 ../g = <URL:http://a/b/g>
281 ../.. = <URL:http://a/>
282 ../../g = <URL:http://a/g>
283 ../../../g = <URL:http://a/../g>
284 ./../g = <URL:http://a/b/g>
285 ./g/. = <URL:http://a/b/c/g/>
286 /./g = <URL:http://a/./g>
287 g/./h = <URL:http://a/b/c/g/h>
288 g/../h = <URL:http://a/b/c/h>
289 http:g = <URL:http://a/b/c/g>
290 http: = <URL:http://a/b/c/d>
Andrew M. Kuchling5c355201999-01-06 22:13:09 +0000291 http:?y = <URL:http://a/b/c/d?y>
292 http:g?y = <URL:http://a/b/c/g?y>
293 http:g?y/./x = <URL:http://a/b/c/g?y/./x>
Guido van Rossum23cb2a81994-09-12 10:36:35 +0000294"""
Guido van Rossum23cb2a81994-09-12 10:36:35 +0000295
296def test():
Tim Peterse1190062001-01-15 03:34:38 +0000297 import sys
298 base = ''
299 if sys.argv[1:]:
300 fn = sys.argv[1]
301 if fn == '-':
302 fp = sys.stdin
303 else:
304 fp = open(fn)
305 else:
Guido van Rossum68937b42007-05-18 00:51:22 +0000306 from io import StringIO
Raymond Hettingera6172712004-12-31 19:15:26 +0000307 fp = StringIO(test_input)
Georg Brandl86def6c2008-01-21 20:36:10 +0000308 for line in fp:
Tim Peterse1190062001-01-15 03:34:38 +0000309 words = line.split()
310 if not words:
311 continue
312 url = words[0]
313 parts = urlparse(url)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000314 print('%-10s : %s' % (url, parts))
Tim Peterse1190062001-01-15 03:34:38 +0000315 abs = urljoin(base, url)
316 if not base:
317 base = abs
318 wrapped = '<URL:%s>' % abs
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000319 print('%-10s = %s' % (url, wrapped))
Tim Peterse1190062001-01-15 03:34:38 +0000320 if len(words) == 3 and words[1] == '=':
321 if wrapped != words[2]:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000322 print('EXPECTED', words[2], '!!!!!!!!!!')
Guido van Rossum23cb2a81994-09-12 10:36:35 +0000323
324if __name__ == '__main__':
Tim Peterse1190062001-01-15 03:34:38 +0000325 test()