Plex plugin to to play various online streams (mostly Latvian).

url.py 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. from __future__ import absolute_import
  2. from collections import namedtuple
  3. from ..exceptions import LocationParseError
  4. url_attrs = ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment']
  5. class Url(namedtuple('Url', url_attrs)):
  6. """
  7. Datastructure for representing an HTTP URL. Used as a return value for
  8. :func:`parse_url`.
  9. """
  10. slots = ()
  11. def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None,
  12. query=None, fragment=None):
  13. if path and not path.startswith('/'):
  14. path = '/' + path
  15. return super(Url, cls).__new__(cls, scheme, auth, host, port, path,
  16. query, fragment)
  17. @property
  18. def hostname(self):
  19. """For backwards-compatibility with urlparse. We're nice like that."""
  20. return self.host
  21. @property
  22. def request_uri(self):
  23. """Absolute path including the query string."""
  24. uri = self.path or '/'
  25. if self.query is not None:
  26. uri += '?' + self.query
  27. return uri
  28. @property
  29. def netloc(self):
  30. """Network location including host and port"""
  31. if self.port:
  32. return '%s:%d' % (self.host, self.port)
  33. return self.host
  34. @property
  35. def url(self):
  36. """
  37. Convert self into a url
  38. This function should more or less round-trip with :func:`.parse_url`. The
  39. returned url may not be exactly the same as the url inputted to
  40. :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls
  41. with a blank port will have : removed).
  42. Example: ::
  43. >>> U = parse_url('http://google.com/mail/')
  44. >>> U.url
  45. 'http://google.com/mail/'
  46. >>> Url('http', 'username:password', 'host.com', 80,
  47. ... '/path', 'query', 'fragment').url
  48. 'http://username:password@host.com:80/path?query#fragment'
  49. """
  50. scheme, auth, host, port, path, query, fragment = self
  51. url = ''
  52. # We use "is not None" we want things to happen with empty strings (or 0 port)
  53. if scheme is not None:
  54. url += scheme + '://'
  55. if auth is not None:
  56. url += auth + '@'
  57. if host is not None:
  58. url += host
  59. if port is not None:
  60. url += ':' + str(port)
  61. if path is not None:
  62. url += path
  63. if query is not None:
  64. url += '?' + query
  65. if fragment is not None:
  66. url += '#' + fragment
  67. return url
  68. def __str__(self):
  69. return self.url
  70. def split_first(s, delims):
  71. """
  72. Given a string and an iterable of delimiters, split on the first found
  73. delimiter. Return two split parts and the matched delimiter.
  74. If not found, then the first part is the full input string.
  75. Example::
  76. >>> split_first('foo/bar?baz', '?/=')
  77. ('foo', 'bar?baz', '/')
  78. >>> split_first('foo/bar?baz', '123')
  79. ('foo/bar?baz', '', None)
  80. Scales linearly with number of delims. Not ideal for large number of delims.
  81. """
  82. min_idx = None
  83. min_delim = None
  84. for d in delims:
  85. idx = s.find(d)
  86. if idx < 0:
  87. continue
  88. if min_idx is None or idx < min_idx:
  89. min_idx = idx
  90. min_delim = d
  91. if min_idx is None or min_idx < 0:
  92. return s, '', None
  93. return s[:min_idx], s[min_idx + 1:], min_delim
  94. def parse_url(url):
  95. """
  96. Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is
  97. performed to parse incomplete urls. Fields not provided will be None.
  98. Partly backwards-compatible with :mod:`urlparse`.
  99. Example::
  100. >>> parse_url('http://google.com/mail/')
  101. Url(scheme='http', host='google.com', port=None, path='/mail/', ...)
  102. >>> parse_url('google.com:80')
  103. Url(scheme=None, host='google.com', port=80, path=None, ...)
  104. >>> parse_url('/foo?bar')
  105. Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...)
  106. """
  107. # While this code has overlap with stdlib's urlparse, it is much
  108. # simplified for our needs and less annoying.
  109. # Additionally, this implementations does silly things to be optimal
  110. # on CPython.
  111. if not url:
  112. # Empty
  113. return Url()
  114. scheme = None
  115. auth = None
  116. host = None
  117. port = None
  118. path = None
  119. fragment = None
  120. query = None
  121. # Scheme
  122. if '://' in url:
  123. scheme, url = url.split('://', 1)
  124. # Find the earliest Authority Terminator
  125. # (http://tools.ietf.org/html/rfc3986#section-3.2)
  126. url, path_, delim = split_first(url, ['/', '?', '#'])
  127. if delim:
  128. # Reassemble the path
  129. path = delim + path_
  130. # Auth
  131. if '@' in url:
  132. # Last '@' denotes end of auth part
  133. auth, url = url.rsplit('@', 1)
  134. # IPv6
  135. if url and url[0] == '[':
  136. host, url = url.split(']', 1)
  137. host += ']'
  138. # Port
  139. if ':' in url:
  140. _host, port = url.split(':', 1)
  141. if not host:
  142. host = _host
  143. if port:
  144. # If given, ports must be integers.
  145. if not port.isdigit():
  146. raise LocationParseError(url)
  147. port = int(port)
  148. else:
  149. # Blank ports are cool, too. (rfc3986#section-3.2.3)
  150. port = None
  151. elif not host and url:
  152. host = url
  153. if not path:
  154. return Url(scheme, auth, host, port, path, query, fragment)
  155. # Fragment
  156. if '#' in path:
  157. path, fragment = path.split('#', 1)
  158. # Query
  159. if '?' in path:
  160. path, query = path.split('?', 1)
  161. return Url(scheme, auth, host, port, path, query, fragment)
  162. def get_host(url):
  163. """
  164. Deprecated. Use :func:`.parse_url` instead.
  165. """
  166. p = parse_url(url)
  167. return p.scheme or 'http', p.hostname, p.port