| # |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| # |
| |
| from __future__ import absolute_import |
| |
| import socket |
| |
| from ._compat import urlparse, urlunparse, quote, unquote |
| |
| |
| class Url(object): |
| """ |
| **DEPRECATED** Simple URL parser/constructor. |
| |
| .. deprecated:: 0.27 |
| Use a ``str`` containing the URL instead. |
| |
| Handles URLs of the form: |
| |
| ``<scheme>://<user>:<password>@<host>:<port>/<path>`` |
| |
| All components can be ``None`` if not specified in the URL string. |
| |
| The port can be specified as a service name, e.g. 'amqp' in the |
| URL string but :class:`Url.Port` always gives the integer value. |
| |
| .. warning:: The placement of user and password in URLs is not |
| recommended. It can result in credentials leaking out in program |
| logs. Use connection configuration attributes instead. |
| |
| :ivar scheme: Url scheme e.g. 'amqp' or 'amqps' |
| :ivar username: Username |
| :ivar ~.password: Password |
| :ivar ~.host: Host name, ipv6 literal or ipv4 dotted quad. |
| :ivar ~.port: Integer port. |
| :ivar host_port: Returns host:port |
| |
| :param url: URL string to parse. |
| :type url: ``str`` |
| :param defaults: If ``True``, fill in missing default values in the URL. |
| If ``False``, you can fill them in later by calling self.defaults() |
| :type defaults: ``bool`` |
| :param kwargs: scheme, user, password, host, port, path. |
| If specified, replaces corresponding part in url string. |
| """ |
| |
| AMQPS = "amqps" |
| """URL scheme for the AMQP protocol secured with SSL.""" |
| |
| AMQP = "amqp" |
| """URL scheme for the AMQP protocol.""" |
| |
| |
| class Port(int): |
| """An integer port number that can be constructed from a service name string""" |
| |
| def __new__(cls, value): |
| """ |
| :param value: integer port number or string service name. |
| """ |
| port = super(Url.Port, cls).__new__(cls, cls._port_int(value)) |
| setattr(port, 'name', str(value)) |
| return port |
| |
| def __eq__(self, x): |
| return str(self) == x or int(self) == x |
| |
| def __ne__(self, x): |
| return not self == x |
| |
| def __str__(self): |
| return str(self.name) |
| |
| @staticmethod |
| def _port_int(value): |
| """Convert service, an integer or a service name, into an integer port number.""" |
| try: |
| return int(value) |
| except ValueError: |
| try: |
| return socket.getservbyname(value) |
| except socket.error: |
| # Not every system has amqp/amqps defined as a service |
| if value == Url.AMQPS: |
| return 5671 |
| elif value == Url.AMQP: |
| return 5672 |
| else: |
| raise ValueError("Not a valid port number or service name: '%s'" % value) |
| |
| def __init__(self, url=None, defaults=True, **kwargs): |
| if isinstance(url, Url): |
| self.scheme = url.scheme |
| self.username = url.username |
| self.password = url.password |
| self._host = url._host |
| self._port = url._port |
| self._path = url._path |
| self._params = url._params |
| self._query = url._query |
| self._fragment = url._fragment |
| elif url: |
| if not url.startswith('//'): |
| p = url.partition(':') |
| if '/' in p[0] or not p[2].startswith('//'): |
| url = '//' + url |
| u = urlparse(url) |
| if not u: raise ValueError("Invalid URL '%s'" % url) |
| self.scheme = None if not u.scheme else u.scheme |
| self.username = u.username and unquote(u.username) |
| self.password = u.password and unquote(u.password) |
| (self._host, self._port) = self._parse_host_port(u.netloc) |
| self._path = None if not u.path else u.path |
| self._params = u.params |
| self._query = u.query |
| self._fragment = u.fragment |
| else: |
| self.scheme = None |
| self.username = None |
| self.password = None |
| self._host = None |
| self._port = None |
| self._path = None |
| self._params = None |
| self._query = None |
| self._fragment = None |
| for k in kwargs: # Let kwargs override values parsed from url |
| getattr(self, k) # Check for invalid kwargs |
| setattr(self, k, kwargs[k]) |
| if defaults: self.defaults() |
| |
| @staticmethod |
| def _parse_host_port(nl): |
| hostport = nl.split('@')[-1] |
| hostportsplit = hostport.split(']') |
| beforebrace = hostportsplit[0] |
| afterbrace = hostportsplit[-1] |
| |
| if len(hostportsplit)==1: |
| beforebrace = '' |
| else: |
| beforebrace += ']' |
| if ':' in afterbrace: |
| afterbracesplit = afterbrace.split(':') |
| port = afterbracesplit[1] |
| host = (beforebrace+afterbracesplit[0]).lower() |
| if not port: |
| port = None |
| else: |
| host = (beforebrace+afterbrace).lower() |
| port = None |
| if not host: |
| host = None |
| return (host, port) |
| |
| @property |
| def path(self): |
| """ |
| The path segment of a URL |
| |
| :type: ``str`` |
| """ |
| return self._path if not self._path or self._path[0] != '/' else self._path[1:] |
| |
| @path.setter |
| def path(self, p): |
| self._path = p if p[0] == '/' else '/' + p |
| |
| @staticmethod |
| def _ipv6literal(s): |
| return s.startswith('[') and s.endswith(']') |
| |
| @property |
| def host(self): |
| """ |
| The host segment of a URL |
| |
| :type: ``str`` |
| """ |
| if self._host and self._ipv6literal(self._host): |
| return self._host[1:-1] |
| else: |
| return self._host |
| |
| @host.setter |
| def host(self, h): |
| if ':' in h and not self._ipv6literal(h): |
| self._host = '[' + h + ']' |
| else: |
| self._host = h |
| |
| @property |
| def port(self): |
| """ |
| The port number segment of a URL. |
| |
| :type: :class:`Url.Port` |
| """ |
| return self._port and Url.Port(self._port) |
| |
| @port.setter |
| def port(self, p): |
| self._port = p |
| |
| @property |
| def _netloc(self): |
| hostport = '' |
| if self._host: |
| hostport = self._host |
| if self._port: |
| hostport += ':' |
| hostport += str(self._port) |
| userpart = '' |
| if self.username: |
| userpart += quote(self.username) |
| if self.password: |
| userpart += ':' |
| userpart += quote(self.password) |
| if self.username or self.password: |
| userpart += '@' |
| return userpart + hostport |
| |
| def __str__(self): |
| if self.scheme \ |
| and not self._netloc and not self._path \ |
| and not self._params and not self._query and not self._fragment: |
| return self.scheme + '://' |
| return urlunparse((self.scheme or '', self._netloc or '', self._path or '', |
| self._params or '', self._query or '', self._fragment or '')) |
| |
| def __repr__(self): |
| return "Url('%s')" % self |
| |
| def __eq__(self, x): |
| return str(self) == str(x) |
| |
| def __ne__(self, x): |
| return not self == x |
| |
| def defaults(self): |
| """ |
| Fill in missing values (scheme, host or port) with defaults |
| :return: self |
| """ |
| self.scheme = self.scheme or self.AMQP |
| self._host = self._host or '0.0.0.0' |
| self._port = self._port or self.Port(self.scheme) |
| return self |