示例#1
0
文件: message.py 项目: vermuz/httoop
class Message(with_metaclass(HTTPSemantic)):
    u"""A HTTP message

		.. seealso:: :rfc:`2616#section-4`
	"""
    __slots__ = ('__protocol', '__headers', '__body')

    @property
    def protocol(self):
        return self.__protocol

    @protocol.setter
    def protocol(self, protocol):
        self.__protocol.set(protocol)

    @property
    def headers(self):
        return self.__headers

    @headers.setter
    def headers(self, headers):
        self.__headers.set(headers)

    @property
    def body(self):
        return self.__body

    @body.setter
    def body(self, body):
        self.__body.set(body)

    @property
    def trailer(self):
        return Headers((key, self.headers[key])
                       for key in self.headers.values('Trailer')
                       if key in self.headers)

#	@trailer.setter
#	def trailer(self, trailer):
#		self.headers.pop('Trailer', None)
#		if trailer:
#			trailer = Headers(trailer)
#			for key in trailer:
#				self.headers.append('Trailer', key)
#			self.headers.elements('Trailer')  # sanitize
#			self.headers.merge(trailer)

    def __init__(self, protocol=None, headers=None, body=None):
        u"""Initiates a new Message to hold information about the message.

			:param protocol: the requested protocol
			:type  protocol: str|tuple

			:param headers: the request headers
			:type  headers: dict or :class:`Headers`

			:param body: the request body
			:type  body: any
		"""
        self.__protocol = Protocol(protocol or (1, 1))
        self.__headers = Headers(headers or {})
        self.__body = Body(body or b'')

    def parse(self, protocol):
        u"""parses the HTTP protocol version

			:param protocol: the protocol version string
			:type  protocol: bytes
		"""

        self.protocol.parse(protocol)

    def __repr__(self):
        return '<HTTP Message(protocol=%s)>' % (self.protocol, )
示例#2
0
文件: protocol.py 项目: vermuz/httoop
class Protocol(with_metaclass(HTTPSemantic)):
	u"""The HTTP protocol version"""
	__slots__ = ('name', '__protocol')

	@property
	def version(self):
		return tuple(self)

	@property
	def major(self):
		return self[0]

	@property
	def minor(self):
		return self[1]

	PROTOCOL_RE = re.compile(br"^(HTTP)/(\d+).(\d+)\Z")

	def __init__(self, protocol=(1, 1)):
		self.__protocol = protocol
		self.name = b'HTTP'
		self.set(protocol)

	def set(self, protocol):
		if isinstance(protocol, (bytes, Unicode)):
			protocol = self.parse(protocol)
		else:
			self.__protocol = tuple(protocol)

	def parse(self, protocol):
		match = self.PROTOCOL_RE.match(protocol)
		if match is None:
			raise InvalidLine(_(u"Invalid HTTP protocol: %r"), protocol.decode('ISO8859-1'))
		self.__protocol = (int(match.group(2)), int(match.group(3)))
		self.name = match.group(1)

	def compose(self):
		return b'%s/%d.%d' % (self.name, self.major, self.minor)

	def __iter__(self):
		return self.__protocol.__iter__()

	def __getitem__(self, key):
		return self.version[key]

	def __eq__(self, other):
		try:
			other = Protocol(other)
		except (TypeError, InvalidLine):
			if isinstance(other, int):
				return self.major == other
			return False
		return self.version == other.version

	def __lt__(self, other):
		try:
			other = Protocol(other)
		except (TypeError, InvalidLine):
			if isinstance(other, int):
				return self.major < other
			raise  # pragma: no cover
		return self.version < other.version

	def __gt__(self, other):
		try:
			other = Protocol(other)
		except (TypeError, InvalidLine):
			if isinstance(other, int):
				return self.major > other
			raise  # pragma: no cover
		return self.version > other.version
示例#3
0
class URI(with_metaclass(URIType)):
	u"""Uniform Resource Identifier"""

	__slots__ = ('scheme', 'username', 'password', 'host', '_port', 'path', 'query_string', 'fragment')

	SCHEMES = {}
	SCHEME = None
	PORT = None
	encoding = 'UTF-8'

	@property
	def query(self):
		return tuple(QueryString.decode(self.query_string, self.encoding))

	@query.setter
	def query(self, query):
		self.query_string = QueryString.encode(query, self.encoding)

	@property
	def path_segments(self):
		return [Unicode.replace(p, u'%2f', u'/') for p in self.path.split(u'/')]

	@path_segments.setter
	def path_segments(self, path):
		self.path = u'/'.join(seq.replace(u'/', u'%2f') for seq in path)

	@property
	def hostname(self):
		host = self.host
		if host.startswith(u'[v') and host.endswith(u']') and u'.' in host and host[2:-1].split(u'.', 1)[0].isdigit():
			return host[2:-1].split(u'.', 1)[1]
		return host.rstrip(u']').lstrip(u'[').lower()

	@property
	def port(self):
		return self._port or self.PORT

	@port.setter
	def port(self, port):
		port = port or self.PORT
		if port:
			try:
				port = int(port)
				if not 0 < int(port) <= 65535:
					raise ValueError
			except ValueError:
				raise InvalidURI(_(u'Invalid port: %r'), port)  # TODO: TypeError
		self._port = port

	def __init__(self, uri=None, *args, **kwargs):
		self.set(kwargs or args or uri or b'')

	def join(self, other=None, *args, **kwargs):
		u"""Join a URI with another absolute or relative URI"""
		relative = URI(other or args or kwargs)
		joined = URI()
		current = URI(self)
		if relative.scheme:
			current = relative
			current.normalize()
			return current
		joined.scheme = current.scheme
		if relative.host:
			current = relative
		joined.username = current.username
		joined.password = current.password
		joined.host = current.host
		joined.port = current.port
		if relative.path:
			current = relative
		joined.path = current.path
		if relative.path and not relative.path.startswith(b'/'):
			joined.path = b'%s%s%s' % (self.path, b'' if self.path.endswith(b'/') else '/../', relative.path)
		if relative.query_string:
			current = relative
		joined.query_string = current.query_string
		if relative.fragment:
			current = relative
		joined.fragment = current.fragment
		joined.normalize()
		return joined

	def normalize(self):
		u"""Normalize the URI to make it compareable.

			.. seealso:: :rfc:`3986#section-6`
		"""
		self.scheme = self.scheme.lower()
		self.host = self.host.lower()

		if not self.port:
			self.port = self.PORT

		self.abspath()
		if not self.path.startswith(u'/') and self.host and self.scheme and self.path:
			self.path = u'/%s' % (self.path,)

	def abspath(self):
		"""Clear out any '..' and excessive slashes from the path

			>>> dangerous = (u'/./', u'/../', u'./', u'/.', u'../', u'/..', u'//')
			>>> uris = (URI(b'/foo/./bar/../baz//blah/.'), )
			>>> _ = [uri.abspath() for uri in uris]
			>>> all(all(d not in uri.path for d in dangerous) for uri in uris)
			True
			>>> u = URI(b'/foo/../bar/.'); u.abspath(); u.path == u'/bar/'
			True
		"""
		path = re.sub(u'\/{2,}', u'/', self.path)  # remove //
		if not path:
			return
		unsplit = []
		directory = False
		for part in path.split(u'/'):
			if part == u'..' and (not unsplit or unsplit.pop() is not None):
				directory = True
			elif part != b'.':
				unsplit.append(part)
				directory = False
			else:
				directory = True

		if directory:
			unsplit.append(u'')
		self.path = u'/'.join(unsplit) or u'/'

	def set(self, uri):
		if isinstance(uri, Unicode):
			uri = uri.encode('UTF-8')  # FIXME

		if isinstance(uri, bytes):
			self.parse(uri)
		elif isinstance(uri, URI):
			self.tuple = uri.tuple
		elif isinstance(uri, tuple):
			self.tuple = uri
		elif isinstance(uri, dict):
			self.dict = uri
		else:
			raise TypeError('URI must be bytes/unicode/tuple/dict not %r' % (type(uri).__name__,))

	@property
	def dict(self):
		slots = (key.lstrip('_') for key in self.__slots__)
		return dict((key, getattr(self, key)) for key in slots)

	@dict.setter
	def dict(self, uri):
		for key in self.__slots__:
			key = key.lstrip('_')
			setattr(self, key, uri.get(key, u''))

	@property
	def tuple(self):
		return tuple(getattr(self, key) for key in self.__slots__)

	@tuple.setter
	def tuple(self, tuple_):
		(self.scheme, self.username, self.password, self.host,
			self.port, self.path, self.query_string, self.fragment) = tuple_

	def parse(self, uri):
		r"""Parses a well formed absolute or relative URI.

			  foo://example.com:8042/over/there?name=ferret#nose
			  \_/   \______________/\_________/ \_________/ \__/
			   |           |            |            |        |
			scheme     authority       path        query   fragment
			   |   _____________________|__
			  / \ /                        \
			  urn:example:animal:ferret:nose

			https://username:password@[::1]:8090/some/path?query#fragment
			<scheme>://<username>:<password>@<host>:<port>/<path>?<query>#<fragment>
			[<scheme>:][//[<username>[:<password>]@][<host>][:<port>]/]<path>[?<query>][#<fragment>]
		"""

		if isinstance(uri, Unicode):
			try:
				uri = uri.encode('ascii')
			except UnicodeEncodeError:
				raise TypeError('URI must be ASCII bytes.')

		if type(self) is URI and b':' in uri:
			self.scheme = uri.split(b':', 1)[0].lower()
			if type(self) is not URI:
				return self.parse(uri)

		if uri and uri.strip(b'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'):
			raise InvalidURI(_(u'Invalid URI: must consist of printable ASCII characters without whitespace.'))

		uri, __, fragment = uri.partition(b'#')
		uri, __, query_string = uri.partition(b'?')
		scheme, authority_exists, uri = uri.rpartition(b'://')
		if not authority_exists and uri.startswith(b'//'):
			uri = uri[2:]
			authority_exists = True
		if not authority_exists and b':' in uri:
			scheme, __, uri = uri.partition(b':')
		authority, path = b'', uri
		if authority_exists:
			authority, __, path = uri.partition(b'/')
			path = b'%s%s' % (__, path)
		userinfo, __, hostport = authority.rpartition(b'@')
		username, __, password = userinfo.partition(b':')
		if b':' in hostport and not hostport.endswith(b']'):
			host, __, port = hostport.rpartition(b':')
		else:
			host, port = hostport, b''

		unquote = self.unquote
		path = u'/'.join([unquote(seq).replace(u'/', u'%2f') for seq in path.split(b'/')])

		try:
			scheme = scheme.decode('ascii').lower()
		except UnicodeDecodeError:
			raise InvalidURI(_(u'Invalid scheme: must be ASCII.'))

		if scheme and scheme.strip(u'abcdefghijklmnopqrstuvwxyz0123456789.-+'):
			raise InvalidURI(_(u'Invalid scheme: must only contain alphanumeric letters or plus, dash, dot.'))

		if query_string:
			query_string = QueryString.encode(QueryString.decode(query_string, self.encoding), self.encoding)

		self.tuple = (
			scheme,
			unquote(username),
			unquote(password),
			self._unquote_host(host),
			port,
			path,
			query_string.decode(self.encoding),
			unquote(fragment)
		)

	def _unquote_host(self, host):
		# IPv6 / IPvFuture
		if host.startswith(b'[') and host.endswith(b']'):
			host = host[1:-1]
			try:
				return u'[%s]' % inet_ntop(AF_INET6, inet_pton(AF_INET6, host)).decode('ascii')
			except SocketError:
				# IPvFuture
				if host.startswith(b'v') and b'.' in host and host[1:].split(b'.', 1)[0].isdigit():
					try:
						return u'[%s]' % host.decode('ascii')
					except UnicodeDecodeError:
						raise InvalidURI(_('Invalid IPvFuture address: must be ASCII.'))
				raise InvalidURI(_('Invalid IP address in URI.'))
		# IPv4
		if all(x.isdigit() for x in host.split(b'.')):
			try:
				return inet_ntop(AF_INET, inet_pton(AF_INET, host)).decode('ascii')
			except SocketError:
				raise InvalidURI(_('Invalid IPv4 address in URI.'))

		if host.strip(Percent.UNRESERVED + Percent.SUB_DELIMS + b'%'):
			raise InvalidURI(_('Invalid URI host.'))

		# DNS hostname
		host = self.unquote(host)
		try:
			return host.encode('ascii').decode('idna').lower()
		except UnicodeError:
			raise InvalidURI(_('Invalid host.'))

	def compose(self):
		return b''.join(self._compose_absolute_iter())

	def _compose_absolute_iter(self):
		u"""composes the whole URI"""
		scheme, username, password, host, port, path, _, fragment = self.tuple
		if scheme:
			yield self.quote(scheme, Percent.SCHEME)
			yield b':'
		authority = b''.join(self._compose_authority_iter())
		if authority:
			yield b'//'
		yield authority
		yield b''.join(self._compose_relative_iter())

	def _compose_authority_iter(self):
		if not self.host:
			return
		username, password, host, port, quote = self.username, self.password, self.host, self.port, self.quote
		if username:
			yield quote(username, Percent.USERINFO)
			if password:
				yield b':'
				yield quote(password, Percent.USERINFO)
			yield b'@'
		try:
			yield host.encode('idna')
		except UnicodeError:  # u'..'.encode('idna')
			raise InvalidURI(_(u'Invalid URI: cannot encode host as IDNA.'))
		if port and int(port) != self.PORT:
			yield b':%d' % int(port)

	def _compose_relative_iter(self):
		u"""Composes the relative URI beginning with the path"""
		scheme, path, query_string, quote, fragment = self.scheme, self.path, self.query_string, self.quote, self.fragment
		PATH = Percent.PATH
		if not scheme and not path.startswith(u'/'):
			PATH = set(PATH) - {b':', b'@'}
		yield b'/'.join(quote(x, PATH) for x in path.split(u'/'))
		if query_string:
			yield b'?'
			yield query_string
		if fragment:
			yield b'#'
			yield quote(fragment, Percent.FRAGMENT)

	def unquote(self, data):
		return Percent.unquote(bytes(data)).decode(self.encoding)

	def quote(self, data, charset):
		return Percent.quote(Unicode(data).encode(self.encoding), charset)

	def __eq__(self, other):
		u"""Compares the URI with another string or URI

			.. seealso:: :rfc:`2616#section-3.2.3`

			.. seealso:: :rfc:`3986#section-6`

			>>> u1 = URI(b'http://abc.com:80/~smith/home.html')
			>>> u2 = b'http://ABC.com/%7Esmith/home.html'
			>>> u3 = URI(b'http://ABC.com:/%7esmith/home.html')
			>>> u1 == u2 == u3
			True
		"""
		cls = type(self)
		self_, other = cls(self), cls(other)
		self_.normalize()
		other.normalize()

		return self_.tuple == other.tuple

	def __setattr__(self, name, value):
		if name.startswith('_'):
			return super(URI, self).__setattr__(name, value)

		if name == 'scheme' and value:
			self.__class__ = self.SCHEMES.get(value, URI)

		if name in self.__slots__:
			if isinstance(value, bytes):
				try:
					value = value.decode('UTF-8')
				except UnicodeDecodeError:
					value = value.decode('ISO8859-1')
			if value is None:
				pass
			elif not isinstance(value, Unicode):
				raise TypeError('%r must be string, not %s' % (name, type(value).__name__))

		super(URI, self).__setattr__(name, value)

	def __repr__(self):
		return '<URI(%s)>' % bytes(self)
示例#4
0
文件: element.py 项目: vermuz/httoop
class HeaderElement(with_metaclass(HeaderType)):
    u"""An element (with parameters) from an HTTP header's element list."""

    priority = None
    hop_by_hop = False
    list_element = False

    # Regular expression that matches `special' characters in parameters, the
    # existance of which force quoting of the parameter value.
    RE_TSPECIALS = re.compile(b'[ \(\)<>@,;:\\\\"/\[\]\?=]')
    RE_SPLIT = re.compile(b',(?=(?:[^"]*"[^"]*")*[^"]*$)')
    RE_PARAMS = re.compile(b';(?=(?:[^"]*"[^"]*")*[^"]*$)')

    def __init__(self, value, params=None):
        self.value = bytes(value)
        self.params = params or {}
        self.sanitize()

    def sanitize(self):
        pass

    def __lt__(self, other):
        return self.value < getattr(other, 'value', other)

    def __gt__(self, other):
        return self.value > getattr(other, 'value', other)

    def __eq__(self, other):
        return self.value == getattr(other, 'value', other)

    def __ne__(self, other):
        return not self == other

    def __bytes__(self):
        return self.compose()

    def __unicode__(self):
        return self.decode(bytes(self))

    if str is bytes:
        __str__ = __bytes__
    else:  # pragma: no cover
        __str__ = __unicode__

    def compose(self):
        params = [
            b'; %s' % self.formatparam(k, v) for k, v in iteritems(self.params)
        ]
        return b'%s%s' % (self.value, ''.join(params))

    @classmethod
    def parseparams(cls, elementstr):
        """Transform 'token;key=val' to ('token', {'key': 'val'})."""
        # Split the element into a value and parameters. The 'value' may
        # be of the form, "token=token", but we don't split that here.
        assert isinstance(elementstr, bytes)
        atoms = [
            x.strip() for x in cls.RE_PARAMS.split(elementstr) if x.strip()
        ] or [b'']

        value = atoms.pop(0)
        params = (cls.parseparam(atom) for atom in atoms)
        params = cls._rfc2231_and_continuation_params(params)
        # TODO: prefer foo* parameter when both are provided

        return value, dict(params)

    @classmethod
    def parseparam(cls, atom):
        key, __, val = atom.partition(b'=')
        try:
            val, quoted = cls.unescape_param(val.strip())
        except InvalidHeader:
            raise InvalidHeader(
                _(u'Unquoted parameter %r in %r containing TSPECIALS: %r'),
                key, cls.__name__, val)
        return cls.unescape_key(key), val, quoted

    @classmethod
    def unescape_key(cls, key):
        return key.strip().lower()

    @classmethod
    def unescape_param(cls, value):
        quoted = value.startswith(b'"') and value.endswith(b'"')
        if quoted:
            value = re.sub(r'\\(?!\\)', '', value.strip(b'"'))
        else:
            if cls.RE_TSPECIALS.search(value):
                raise InvalidHeader(
                    _(u'Unquoted parameter in %r containing TSPECIALS: %r'),
                    cls.__name__, value)
        return value, quoted

    @classmethod
    def _rfc2231_and_continuation_params(cls, params):  # TODO: complexity
        count = set()
        continuations = dict()
        for key, value, quoted in params:
            if key in count:
                raise InvalidHeader(_(u'Parameter given twice: %r'),
                                    key.decode('ISO8859-1'))
            count.add(key)
            if '*' in key:
                if key.endswith('*') and not quoted:
                    charset, language, value_ = decode_rfc2231(
                        value.encode('ISO8859-1'))
                    if not charset:
                        yield key, value
                        continue
                    encoding = sanitize_encoding(charset)
                    if encoding is None:
                        raise InvalidHeader(_(u'Unknown encoding: %r'),
                                            charset)
                    try:
                        key, value = key[:-1], Percent.unquote(value_).decode(
                            encoding)
                    except UnicodeDecodeError as exc:
                        raise InvalidHeader(_(u'%s') % (exc, ))
                key_, asterisk, num = key.rpartition('*')
                if asterisk:
                    try:
                        if num != '0' and num.startswith('0'):
                            raise ValueError
                        num = int(num)
                    except ValueError:
                        yield key, value
                        continue
                    continuations.setdefault(key_, {})[num] = value
                    continue
            yield key, value

        for key, lines in iteritems(continuations):
            value = b''
            for i in xrange(len(lines)):
                try:
                    value += lines.pop(i)
                except KeyError:
                    break
            if not key:
                raise InvalidHeader(_(u'...'))
            if value:
                yield key, value
            for k, v in iteritems(lines):
                yield '%s*%d' % (key, k), v

    @classmethod
    def parse(cls, elementstr):
        """Construct an instance from a string of the form 'token;key=val'."""
        ival, params = cls.parseparams(elementstr)
        return cls(ival, params)

    @classmethod
    def split(cls, fieldvalue):
        return cls.RE_SPLIT.split(fieldvalue)

    @classmethod
    def join(cls, values):
        return b', '.join(values)

    @classmethod
    def sorted(cls, elements):
        return elements

    @classmethod
    def merge(cls, elements, others):
        return cls.join([bytes(x) for x in cls.sorted(elements + others)])

    @classmethod
    def formatparam(cls, param, value=None, quote=False):
        """Convenience function to format and return a key=value pair.

		This will quote the value if needed or if quote is true.
		"""
        if value:
            value = bytes(value)
            if quote or cls.RE_TSPECIALS.search(value):
                value = value.replace(b'\\', b'\\\\').replace(b'"', br'\"')
                return b'%s="%s"' % (param, value)
            else:
                return b'%s=%s' % (param, value)
        else:
            return param

    @classmethod
    def decode(cls, value):
        if b'=?' in value:
            # FIXME: must not parse encoded_words in unquoted ('Content-Type', 'Content-Disposition') header params
            return u''.join(
                atom.decode(charset or 'ISO8859-1')
                for atom, charset in decode_header(value))
        return value.decode('ISO8859-1')

    @classmethod
    def encode(cls, value):
        try:
            return value.encode('ISO8859-1')
        except UnicodeEncodeError:
            return value.encode(
                'ISO8859-1', 'replace'
            )  # FIXME: if value contains UTF-8 chars encode them in MIME; =?UTF-8?B?…?= (RFC 2047); seealso quopri

    def __repr__(self):
        params = ', %r' % (self.params, ) if self.params else ''
        return '<%s(%r%s)>' % (self.__class__.__name__, self.value, params)
示例#5
0
文件: headers.py 项目: vermuz/httoop
class Headers(with_metaclass(HTTPSemantic, CaseInsensitiveDict)):

	# disallowed bytes for HTTP header field names
	HEADER_RE = re.compile(br"[\x00-\x1F\x7F()<>@,;:\\\\\"/\[\]?={} \t\x80-\xFF]")

	@staticmethod
	def formatvalue(value):
		return to_unicode(value)  # TODO: using unicode here is not good if processed via HeaderElement

	@classmethod
	def formatkey(cls, key):
		key = CaseInsensitiveDict.formatkey(key)
		if cls.HEADER_RE.search(key):
			raise InvalidHeader(_(u"Invalid header name: %r"), key.decode('ISO8859-1'))
		return key  # TODO: do we want bytes here?

	def elements(self, fieldname):
		u"""Return a sorted list of HeaderElements from
			the given comma-separated header string."""

		fieldvalue = self.get(fieldname)
		if not fieldvalue:
			return []

		Element = HEADER.get(fieldname, HeaderElement)
		return Element.sorted([Element.parse(element) for element in Element.split(fieldvalue.encode('ascii'))])

	def element(self, fieldname, default=None):
		u"""Treat the field as single element"""
		if fieldname in self:
			Element = HEADER.get(fieldname, HeaderElement)
			return Element.parse(self[fieldname])
		return default

#	# TODO: a really nice alternative method would be:
#	def element(self, fieldname, which=None, default=None):
#		for element in self.elements(fieldname):
#			if which is None or element == which:
#				return element
#		return default

	def set_element(self, fieldname, *args, **kwargs):
		self[fieldname] = bytes(self.create_element(fieldname, *args, **kwargs))

	def append_element(self, fieldname, *args, **kwargs):
		self.append(fieldname, bytes(self.create_element(fieldname, *args, **kwargs)))

	def create_element(self, fieldname, *args, **kwargs):
		Element = HEADER.get(fieldname, HeaderElement)
		return Element(*args, **kwargs)

	def values(self, key=None):  # FIXME: overwrites dict.values()
		if key is None:
			return super(Headers, self).values()
		# if key is set return a ordered list of element values
		return [e.value for e in self.elements(key)]

	def append(self, _name, _value, **params):
		if params:
			Element = HEADER.get(_name, HeaderElement)
			parts = [_value or b'']
			for k, v in iteritems(params):
				k = k.replace('_', '-')  # TODO: find out why this is done
				if v is None:
					parts.append(k)
				else:
					parts.append(Element.formatparam(k, v))
			_value = "; ".join(parts)

		if _name not in self or not self[_name]:
			self[_name] = _value
		else:
			Element = HEADER.get(_name, HeaderElement)
			self[_name] = Element.join([self[_name], _value])

	def validate(self):
		u"""validates all header elements

			:raises: InvalidHeader
		"""
		for name in self:
			self.elements(name)

	def merge(self, other):
		other = self.__class__(other)
		for key in other:
			Element = HEADER.get(key, HeaderElement)
			self[key] = Element.merge(self.elements(key), other.elements(key))

	def set(self, headers):
		self.clear()
		self.update(headers)

	def parse(self, data):
		r"""parses HTTP headers

			:param data:
				the header string containing headers separated by "\r\n"
				without trailing "\r\n"
			:type  data: bytes
		"""

		lines = data.split(b'\r\n')

		while lines:
			curr = lines.pop(0)
			name, __, value = curr.partition(b':')
			if __ != b':':
				raise InvalidHeader(_(u"Invalid header line: %r"), curr.decode('ISO8859-1'))

			if self.HEADER_RE.search(name):
				raise InvalidHeader(_(u"Invalid header name: %r"), name.decode('ISO8859-1'))

			name, value = name.strip(), [value.lstrip()]

			# continuation lines
			while lines and lines[0].startswith((b' ', b'\t')):
				value.append(lines.pop(0)[1:])
			value = b''.join(value).rstrip()
			Element = HEADER.get(name, HeaderElement)
			value = Element.decode(value)

			self.append(name, value)

	def compose(self):
		return b'%s\r\n' % b''.join(b'%s: %s\r\n' % (k, v) for k, v in self.__items())

	def __items(self):
		return sorted(self.__encoded_items(), key=lambda x: HEADER.get(x[0], HeaderElement).priority or x[0])

	def __encoded_items(self):
		for key, values in iteritems(self):
			Element = HEADER.get(key, HeaderElement)
			if Element is not HeaderElement:
				key = Element.__name__
			if Element.list_element:
				for value in Element.split(values):
					yield key, Element.encode(value)
			else:
				yield key, Element.encode(values)

	def __repr__(self):
		return "<HTTP Headers(%s)>" % repr(list(self.items()))
示例#6
0
文件: types.py 项目: vermuz/httoop
class StatusException(with_metaclass(StatusType, Status, Exception)):
    u"""This class represents a small HTTP Response message
		for error handling purposes"""
    @property
    def headers(self):
        return self._headers

    @property
    def body(self):
        if not hasattr(self, '_body'):
            from httoop.messages.body import Body
            self._body = Body(mimetype='application/json')
            self._body.data = self.to_dict()
        return self._body

    @body.setter
    def body(self, value):
        self.body
        self._body.set(value)

    header_to_remove = ()
    u"""a tuple of header field names which should be
		removed when responding with this error"""

    description = ''

    @property
    def traceback(self):
        return self._traceback

    @traceback.setter
    def traceback(self, tb):
        if self.server_error:
            self._traceback = tb

    code = 0

    def __init__(self,
                 description=None,
                 reason=None,
                 headers=None,
                 traceback=None):
        u"""
			:param description:
				a description of the error which happened
			:type description: str

			:param reason:
				a additional reason phrase
			:type reason: str

			:param headers:
			:type headers: dict

			:param traceback:
				A Traceback for the error
			:type traceback: str
		"""

        Status.__init__(self, self.__class__.code, reason=reason)

        self._headers = dict()
        self._traceback = None

        if isinstance(headers, dict):
            self._headers.update(headers)

        if description is not None:
            self.description = description

        if traceback:
            self.traceback = traceback

    def __repr__(self):
        return '<HTTP Status %d %r>' % (int(self), self.reason)

    __str__ = __repr__

    def to_dict(self):
        u"""the default body arguments"""
        return dict(status=self.status,
                    reason=self.reason,
                    description=self.description,
                    headers=self.headers)
示例#7
0
class Body(with_metaclass(HTTPSemantic, IFile)):
    u"""A HTTP message body

		This class is capable of handling HTTP Transfer-Encoding
		and Content-Encoding as defined in RFC 2616.

		It provides an interface which makes it possible to use either
		unicode, bytes, bytearray, file, file-like objects (e.g. from the
		codecs module), StringIO, BytesIO, NamedTemporaryFiles or any iterable
		returning bytes/unicode as type for the content.

		The encode and decode methods can also control the automatic en/decoding of
		the content using the codec specified in the MIME media type.
	"""
    __slots__ = ('fd', 'data', '__iter', 'headers', 'trailer', 'content_codec',
                 'transfer_codec')

    MAX_CHUNK_SIZE = 4096

    @property
    def fileable(self):
        u"""Flag whether the set content provides the file interface"""
        return all(
            hasattr(self.fd, method) for method in ('read', 'write', 'close'))

    @property
    def generator(self):
        return isinstance(self.fd, GeneratorType)

    @property
    def encoding(self):
        return self.mimetype.charset or 'UTF-8'

    @encoding.setter
    def encoding(self, charset):
        mimetype = self.mimetype
        mimetype.charset = charset
        self.mimetype = mimetype

    @property
    def mimetype(self):
        u"""Represents the MIME media type of the content"""
        return self.headers.element('Content-Type')

    @mimetype.setter
    def mimetype(self, mimetype):
        self.headers['Content-Type'] = bytes(mimetype)

    @property
    def content_encoding(self):
        return self.headers.element('Content-Encoding')

    @content_encoding.setter
    def content_encoding(self, value):
        if value:
            self.headers['Content-Encoding'] = bytes(value)
            self.content_codec = None  #self.content_encoding.iterdecode()
        else:
            self.headers.pop('Content-Encoding', None)
            self.content_codec = None

    @property
    def transfer_encoding(self):
        return self.headers.element('Transfer-Encoding')

    @transfer_encoding.setter
    def transfer_encoding(self, transfer_encoding):
        if transfer_encoding:
            self.headers['Transfer-Encoding'] = bytes(transfer_encoding)
            self.transfer_codec = None  #self.transfer_encoding.iterdecode()
        else:
            self.headers.pop('Transfer-Encoding', None)
            self.transfer_codec = None

    @property
    def chunked(self):
        if not self.transfer_encoding:
            return False
        return 'chunked' == self.transfer_encoding.value

    @chunked.setter
    def chunked(self, chunked):
        self.transfer_encoding = 'chunked' if chunked else None

    def __init__(self, content=None, mimetype=None):
        self.data = None
        self.__iter = None
        self.fd = BytesIO()
        self.headers = Headers()
        self.trailer = Headers()
        self.transfer_codec = None
        self.content_codec = None

        self.mimetype = mimetype or b'text/plain; charset=UTF-8'
        self.set(content)

    def encode(self, *data):
        u"""Encode the object in :attr:`data` if a codec for the mimetype exists"""
        codec = self.mimetype.codec
        if codec:
            if self.data is None and not data:
                return
            data = data[0] if data else self.data
            value = codec.encode(data, self.encoding, self.mimetype)
            self.set(value)
            self.data = data

    def iterencode(self, *data):
        codec = self.mimetype.codec
        if codec:
            data = data[0] if data else self.data
            value = codec.iterencode(data, self.encoding, self.mimetype)
            self.set(value)
            self.data = data

    def decode(self, *data):
        u"""Decodes the body content if a codec for the mimetype exists.
			Stores the decoded object in :attr:`data`
		"""
        codec = self.mimetype.codec
        if data:
            self.set(data[0])
        if codec:
            self.data = codec.decode(self.__content_bytes(), self.encoding,
                                     self.mimetype)
            return self.data

    def compress(self):
        u"""Applies the Content-Encoding codec to the content"""
        codec = self.content_codec
        if codec:
            self.set(codec.encode(self.__content_bytes()))

    def set(self, content):
        if isinstance(content, Body):
            self.mimetype = content.mimetype
            self.data = content.data
            self.chunked = content.chunked
            self.trailer = content.trailer
            self.fd = content.fd
            return

        self.data = None
        if not content:
            content = BytesIO()
        elif isinstance(content, BytesIO) or (hasattr(content, 'read')
                                              and hasattr(content, 'fileno')
                                              and hasattr(content, 'closed')):
            if content.closed:
                raise ValueError('I/O operation on closed file.')
        elif isinstance(content, Unicode):
            content = BytesIO(content.encode(self.encoding))
        elif isinstance(content, bytes):
            content = BytesIO(content)
        elif isinstance(content, bytearray):
            content = BytesIO(bytes(content))
        elif not hasattr(content, '__iter__'):
            raise TypeError('Content must be iterable.')
        self.fd = content

    def parse(self, data):
        if self.transfer_codec:
            data = self.transfer_codec.decode(data)

        if self.content_codec:
            data = self.content_codec.decode(data)

        self.write(data)

    def compose(self):
        return b''.join(self.__iter__())

    def close(self):
        fileable = self.fileable
        super(Body, self).close()
        if fileable:
            self.set('')

    def __unicode__(self):
        return self.__content_bytes().decode(self.encoding)

    def __iter__(self):
        u"""Iterates over the content applying Content-Encoding and Transfer-Encoding"""
        data = self.__content_iter()
        for codec in (self.content_codec, self.transfer_codec):
            if codec:
                data = (codec.encode(d) for d in data)
        if self.chunked:
            data = self.__compose_chunked_iter(data)
        return data

    def __compose_chunked_iter(self, iterable):
        for data in iterable:
            if not data:
                continue
            yield b"%x\r\n%s\r\n" % (len(data), data)
        if self.trailer:
            yield b"0\r\n%s" % bytes(self.trailer)
        else:
            yield b"0\r\n\r\n"

    def __content_bytes(self):
        return b''.join(self.__content_iter())

    def __content_iter(self):
        iterable = self.__iterable()

        t = self.tell()
        self.seek(0)

        try:
            for data in iterable:
                if data is None:
                    continue
                if isinstance(data, Unicode):
                    data = data.encode(self.encoding)
                elif not isinstance(data, bytes):  # pragma: no cover
                    raise TypeError('Iterable contained non-bytes: %r' %
                                    (type(data).__name__, ))
                yield data
        finally:
            self.seek(t)

    def __iterable(self):
        if self.fileable:
            return self.__iter_fileable()
        elif self.generator:
            return self.__iter_generator()
        return self.fd

    def __iter_fileable(self, chunksize=MAX_CHUNK_SIZE):
        data = self.read(chunksize)
        while data:
            yield data
            data = self.read(chunksize)

    def __iter_generator(self):
        fd = self.fd
        buffer_ = []
        while True:
            try:
                data = next(fd)
            except StopIteration:
                self.set(buffer_)
                raise
            else:
                buffer_.append(data)
                yield data

#	def __copy__(self):
#		body = self.__class__(self.__content_bytes())
#		body.mimetype = self.mimetype
#		body.data = self.data
#		body.transfer_encoding = self.transfer_encoding
#		body.content_encoding = self.content_encoding
#		return body

    def __bool__(self):
        return bool(len(self))

    def __len__(self):
        body = self.fd

        if isinstance(body, BytesIO):
            return len(body.getvalue())
        if hasattr(body, 'fileno'):
            return fstat(body.fileno()).st_size

        return len(self.__content_bytes())

    def __next__(self):
        if self.__iter is None:
            self.__iter = self.__iter__()
        try:
            return next(self.__iter)
        except StopIteration:
            self.__iter = None
            raise

    next = __next__
示例#8
0
class Date(with_metaclass(HTTPSemantic)):
    u"""A HTTP Date string

		It provides a API to multiple time representations:

		* datetime
		* time struct
		* UNIX timestamp

		Supported HTTP date string formats:

		:example:
			Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
			Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
			Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
	"""
    def __init__(self, timeval=None):
        u"""
			:param timeval:
			:type  timeval:
				either seconds since epoch in float
				or a datetime object
				or a timetuple
		"""

        self.__composed = None
        self.__timestamp = None
        self.__datetime = None
        self.__time_struct = None

        if timeval is None:
            self.__timestamp = time.time()
        elif isinstance(timeval, (float, int)):
            self.__timestamp = float(timeval)
        elif isinstance(timeval, (tuple, time.struct_time)):
            #			self.__timestamp = calendar.timegm(timeval)
            self.__timestamp = time.mktime(timeval) - time.timezone
        elif isinstance(timeval, datetime):
            self.__datetime = timeval
            #			self.__timestamp = calendar.timegm(self.datetime.utctimetuple())
            self.__timestamp = time.mktime(
                self.datetime.utctimetuple()) - time.timezone
        elif isinstance(timeval, (bytes, Unicode)):
            if isinstance(timeval, Unicode):
                timeval = timeval.encode('ascii', 'ignore')
            self.__timestamp = float(Date.parse(timeval))
        else:
            raise TypeError('Date(): got invalid argument')

    @property
    def datetime(self):
        if self.__datetime is None:
            self.__datetime = datetime.utcfromtimestamp(int(self))
        return self.__datetime

    @property
    def gmtime(self):
        if self.__time_struct is None:
            self.__time_struct = time.gmtime(int(self))
        return self.__time_struct

    def compose(self):
        if self.__composed is None:
            self.__composed = self.__compose()
        return self.__composed

    def __compose(self):
        d = self.gmtime
        return b'%s, %02d %s %04d %02d:%02d:%02d GMT' % (
            (b'Mon', b'Tue', b'Wed', b'Thu', b'Fri', b'Sat',
             b'Sun')[d.tm_wday], d.tm_mday,
            (b'Jan', b'Feb', b'Mar', b'Apr', b'May', b'Jun', b'Jul', b'Aug',
             b'Sep', b'Oct', b'Nov',
             b'Dec')[d.tm_mon - 1], d.tm_year, d.tm_hour, d.tm_min, d.tm_sec)

    @classmethod
    def parse(cls, timestr=None):
        u"""parses a HTTP date string and returns a :class:`Date` object

			:param timestr: the time string in one of the http formats
			:type  timestr: str

			:returns: the HTTP Date object
			:rtype  : :class:`Date`

		"""

        # parse the most common HTTP Date format (RFC 2822)
        date = parsedate(timestr)
        if date is not None:
            return cls(date[:9])

        old = locale.getlocale(locale.LC_TIME)
        locale.setlocale(locale.LC_TIME, (None, None))
        try:
            # parse RFC 1036 date format
            try:
                date = time.strptime(timestr, '%A, %d-%b-%y %H:%M:%S GMT')
            except ValueError:
                pass
            else:
                return cls(date)

            # parse C's asctime format
            try:
                date = time.strptime(timestr, '%a %b %d %H:%M:%S %Y')
            except ValueError:
                pass
            else:
                return cls(date)
        finally:
            locale.setlocale(locale.LC_TIME, old)

        raise InvalidDate(_(u'Invalid date: %r'), date)

    def __int__(self):
        return int(float(self))

    def __float__(self):
        return float(self.__timestamp)

    def __eq__(self, other):
        try:
            return int(self) == int(self.__other(other))
        except NotImplementedError:
            return NotImplemented

    def __gt__(self, other):
        try:
            return int(self) > int(self.__other(other))
        except NotImplementedError:
            return NotImplemented

    def __lt__(self, other):
        try:
            return int(self) < int(self.__other(other))
        except NotImplementedError:
            return NotImplemented

    def __other(self, other):
        if other is None:
            raise NotImplementedError
        if isinstance(other, Date):
            return other
        try:
            return Date(other)
        except (InvalidDate, TypeError):
            raise NotImplementedError

    def __repr__(self):
        return '<HTTP Date(%d)>' % (int(self), )
示例#9
0
文件: status.py 项目: vermuz/httoop
class Status(with_metaclass(HTTPSemantic)):
    u"""A HTTP Status

		:rfc:`2616#section-6.2`
	"""

    #	__slots__ = ('__code', '__reason')

    @property
    def informational(self):
        return 99 < self.__code < 200

    @property
    def successful(self):
        return 199 < self.__code < 300

    @property
    def redirection(self):
        return 299 < self.__code < 400

    @property
    def client_error(self):
        return 399 < self.__code < 500

    @property
    def server_error(self):
        return 499 < self.__code < 600

    # aliases
    @property
    def status(self):
        return self.__code

    @property
    def reason_phrase(self):
        return self.__reason

    reason = None

    @property
    def code(self):
        return self.__code

    @code.setter
    def code(self, code):
        self.set((code, self.__reason))

    @property
    def reason(self):
        return self.__reason

    @reason.setter
    def reason(self, reason):
        self.set((self.__code, reason))

    STATUS_RE = re.compile(br"^([1-5]\d{2})(?:\s+([\s\w]*))\Z")

    def __init__(self, code=None, reason=None):
        """
			:param code:
				the HTTP Statuscode
			:type  code: int

			:param reason:
				the HTTP Reason-Phrase
			:type  reason: unicode
		"""
        self.__code = 0
        reason = reason or u''
        reason = reason or reason or REASONS.get(code, ('', ''))[0]
        if code:
            self.set((
                code,
                reason,
            ))

    def parse(self, status):
        """parse a Statuscode and Reason-Phrase

			:param status: the code and reason
			:type  status: bytes
		"""
        match = self.STATUS_RE.match(status)
        if match is None:
            raise InvalidLine(_(u"Invalid status %r"),
                              status.decode('ISO8859-1'))

        self.set((
            int(match.group(1)),
            match.group(2).decode('ascii'),
        ))

    def compose(self):
        return b'%d %s' % (self.__code, self.__reason.encode('ascii'))

    def __unicode__(self):
        return self.compose().decode('ascii')

    def __int__(self):
        u"""Returns this status as number"""
        return self.__code

    def __eq__(self, other):
        u"""Compares a status with another :class:`Status` or :class:`int`"""
        if isinstance(other, int):
            return self.__code == other
        if isinstance(other, Status):
            return self.__code == other.code
        return super(Status, self).__eq__(other)

    def __lt__(self, other):
        return self.__code < other

    def __gt__(self, other):
        return self.__code > other

    def set(self, status):
        u"""sets reason and status

			:param status:
				A HTTP Status, e.g.: 200, (200, 'OK'), '200 OK'
			:type  status:
				int or tuple or bytes or Status
		"""
        if isinstance(status, int) and 99 < status < 600:
            self.__code, self.__reason = status, REASONS.get(
                status, (u'', u''))[0]
        elif isinstance(status, tuple):
            code, reason = status
            if isinstance(reason, bytes):
                reason = reason.decode('ascii')
            self.__code, self.__reason = int(code), reason
        elif isinstance(status, (bytes, Unicode)):
            code, reason = status.split(None, 1)
            if isinstance(reason, bytes):
                reason = reason.decode('ascii')
            self.__code, self.__reason = int(code), reason
        elif isinstance(status, Status):
            self.__code, self.__reason = status.code, status.reason
        else:
            raise TypeError('invalid status')

    def __repr__(self):
        return '<HTTP Status (code=%d, reason=%r)>' % (self.__code,
                                                       self.__reason)