PageRenderTime 25ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/caldav/lib/url.py

https://bitbucket.org/cyrilrbt/caldav/
Python | 186 lines | 149 code | 10 blank | 27 comment | 12 complexity | 4c8a4a386a473b07156ab083a57fdcb0 MD5 | raw file
Possible License(s): Apache-2.0, GPL-3.0
  1. #!/usr/bin/env python
  2. # -*- encoding: utf-8 -*-
  3. from caldav.lib.python_utilities import isPython3, to_unicode
  4. if isPython3():
  5. from urllib import parse
  6. from urllib.parse import ParseResult, SplitResult, urlparse, unquote
  7. else:
  8. from urlparse import urlparse as parse
  9. from urlparse import ParseResult, SplitResult
  10. from urlparse import urlparse
  11. def uc2utf8(input):
  12. ## argh! this feels wrong, but seems to be needed.
  13. if not isPython3() and type(input) == unicode:
  14. return input.encode('utf-8')
  15. else:
  16. return input
  17. class URL:
  18. """
  19. This class is for wrapping URLs into objects. It's used
  20. internally in the library, end users should not need to know
  21. anything about this class. All methods that accept URLs can be
  22. fed either with an URL object, a string or an urlparse.ParsedURL
  23. object.
  24. Addresses may be one out of three:
  25. 1) a path relative to the DAV-root, i.e. "someuser/calendar" may
  26. refer to
  27. "http://my.davical-server.example.com/caldav.php/someuser/calendar".
  28. 2) an absolute path, i.e. "/caldav.php/someuser/calendar"
  29. 3) a fully qualified URL,
  30. i.e. "http://someuser:somepass@my.davical-server.example.com/caldav.php/someuser/calendar".
  31. Remark that hostname, port, user, pass is typically given when
  32. instantiating the DAVClient object and cannot be overridden later.
  33. As of 2013-11, some methods in the caldav library expected strings
  34. and some expected urlParseResult objects, some expected
  35. fully qualified URLs and most expected absolute paths. The purpose
  36. of this class is to ensure consistency and at the same time
  37. maintaining backward compatibility. Basically, all methods should
  38. accept any kind of URL.
  39. """
  40. def __init__(self, url):
  41. if isinstance(url, ParseResult) or isinstance(url, SplitResult):
  42. self.url_parsed = url
  43. self.url_raw = None
  44. else:
  45. self.url_raw = url
  46. self.url_parsed = None
  47. def __bool__(self):
  48. if self.url_raw or self.url_parsed:
  49. return True
  50. else:
  51. return False
  52. def __ne__(self, other):
  53. return not self == other
  54. def __eq__(self, other):
  55. if str(self) == str(other):
  56. return True
  57. ## The URLs could have insignificant differences
  58. me = self.canonical()
  59. if hasattr(other, 'canonical'):
  60. other = other.canonical()
  61. return str(me) == str(other)
  62. ## TODO: better naming? Will return url if url is already an URL
  63. ## object, else will instantiate a new URL object
  64. @classmethod
  65. def objectify(self, url):
  66. if url is None:
  67. return None
  68. if isinstance(url, URL):
  69. return url
  70. else:
  71. return URL(url)
  72. ## To deal with all kind of methods/properties in the ParseResult
  73. ## class
  74. def __getattr__(self, attr):
  75. if self.url_parsed is None:
  76. self.url_parsed = urlparse(self.url_raw)
  77. if hasattr(self.url_parsed, attr):
  78. return getattr(self.url_parsed, attr)
  79. else:
  80. return getattr(self.__unicode__(), attr)
  81. ## returns the url in text format
  82. def __str__(self):
  83. if isPython3():
  84. return self.__unicode__()
  85. return self.__unicode__().encode('utf-8')
  86. ## returns the url in text format
  87. def __unicode__(self):
  88. if self.url_raw is None:
  89. self.url_raw = self.url_parsed.geturl()
  90. if isinstance(self.url_raw, str):
  91. return to_unicode(self.url_raw)
  92. else:
  93. return to_unicode(str(self.url_raw))
  94. def __repr__(self):
  95. return "URL(%s)" % str(self)
  96. def strip_trailing_slash(self):
  97. if str(self)[-1] == '/':
  98. return URL.objectify(str(self)[:-1])
  99. else:
  100. return self
  101. def is_auth(self):
  102. return self.username is not None
  103. def unauth(self):
  104. if not self.is_auth():
  105. return self
  106. return URL.objectify(ParseResult(
  107. self.scheme, '%s:%s' % (self.hostname, self.port or {'https': 443, 'http': 80}[self.scheme]),
  108. self.path.replace('//', '/'), self.params, self.query, self.fragment))
  109. def canonical(self):
  110. """
  111. a canonical URL ... remove authentication details, make sure there
  112. are no double slashes, and to make sure the URL is always the same,
  113. run it through the urlparser
  114. """
  115. url = self.unauth()
  116. ## this is actually already done in the unauth method ...
  117. if '//' in url.path:
  118. raise NotImplementedError("remove the double slashes")
  119. ## This looks like a noop - but it may have the side effect
  120. ## that urlparser be run (actually not - unauth ensures we
  121. ## have an urlParseResult object)
  122. url.scheme
  123. ## make sure to delete the string version
  124. url.url_raw = None
  125. return url
  126. def join(self, path):
  127. """
  128. assumes this object is the base URL or base path. If the path
  129. is relative, it should be appended to the base. If the path
  130. is absolute, it should be added to the connection details of
  131. self. If the path already contains connection details and the
  132. connection details differ from self, raise an error.
  133. """
  134. pathAsString = str(path)
  135. if not path or not pathAsString:
  136. return self
  137. path = URL.objectify(path)
  138. if (
  139. (path.scheme and self.scheme and path.scheme != self.scheme)
  140. or
  141. (path.hostname and self.hostname and path.hostname != self.hostname)
  142. or
  143. (path.port and self.port and path.port != self.port)
  144. ):
  145. raise ValueError("%s can't be joined with %s" % (self, path))
  146. if path.path[0] == '/':
  147. ret_path = uc2utf8(path.path)
  148. else:
  149. sep = "/"
  150. if self.path.endswith("/"):
  151. sep = ""
  152. ret_path = "%s%s%s" % (self.path, sep, uc2utf8(path.path))
  153. return URL(ParseResult(
  154. self.scheme or path.scheme, self.netloc or path.netloc, ret_path, path.params, path.query, path.fragment))
  155. def make(url):
  156. """Backward compatibility"""
  157. return URL.objectify(url)