#!/usr/bin/env python
##
## Copyright (c) 2007 Alex Malinovich <demonbane@the-love-shack.net>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License v3 as published
## by the Free Software Foundation.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##


"""
A module for handling XMPP URI's as specified in RFC 4622. This is intended to
be used in conjunction with a XEP-0147 compliant library or application to
ensure proper handling of XMPP URI's for chat applications.

References
==========

  Internationalized Resource Identifiers (IRIs)
  and Uniform Resource Identifiers (URIs) for
  the Extensible Messaging and Presence Protocol (XMPP)
    - U{http://tools.ietf.org/html/rfc4622}

  XMPP URI Scheme Query Components
    - U{http://www.xmpp.org/extensions/xep-0147.html}

  XMPP URI/IRI Querytypes
    - U{http://www.xmpp.org/registrar/querytypes.html}

"""

__version__ = "0.33" # 2007/12/12 01:59:54 UTC
__author__ = "Alex Malinovich <demonbane@the-love-shack.net>"

class XmppUri:
	"""Class representing the elements of an XMPP URI.

	This is intended to handle all elements of an XMPP URI as defined in RFC 4622,
	Section 3.3. Elements may be accessed as instance variables, using the names
	specified in RFC 4622.

	e.g.

	>>> from xmppuri import XmppUri
	>>> xmppuri = XmppUri("xmpp:foo@jabber.org?message;subject=Foo;body=bar")
	>>> xmppuri.isvaliduri
	True
	>>> xmppuri.host
	'jabber.org'
	>>> xmppuri.nodeid
	'foo'
	>>> xmppuri.pathxmpp
	'foo@jabber.org'
	>>> xmppuri.querytype
	'message'

	"""
	ERRORS = {1:"Malformed URI"} #: dict listing error values

	# Initializations
	isvaliduri = False
	"""specifies whether current object contains a valid URI"""
	hierxmpp = None
	querycomp = None
	fragment = None
	authpath = None
	pathxmpp = None
	nodeid = None
	host = None
	resid = None
	querytype = None
	pairs = {}
	"""dict containing the key value pairs associated with this URI. May be
	empty, or hold any number of key value pairs.

	e.g.

	>>> xmppuri = XmppUri("xmpp:foo@jabber.org?message;subject=Foo;body=bar")
	>>> for k, v in xmppuri.pairs.iteritems():
	...     print "%s = %s" % (k, v)
	... 
	body = bar
	subject = Foo
	"""

	def __init__(self,uri):
		"""create a new instance of a XmppUri class. The URI is parsed
		as given, and the boolean value .isvaliduri is set to True or
		False depending on whether parsing succeeded or not.
		@param uri: string containing the URI to be parsed
		@type uri: string
		@return: XmppUri
		"""
		self.uri = uri

		# TODO: This should have some more intelligent error handling.
		#	See the ERRORS dict above.
		if self.__parseparts() != 0:
			self.isvaliduri = False
		else:
			self.isvaliduri = True
		

	def __parseparts(self):
		"""parse the URI passed to the constructor and populate the
		object's attributes with the information retrieved. Returns
		non-zero on failure.
		@return: integer value. A non-zero value indicates a parsing error.
		"""
		if not self.uri[0:5] == 'xmpp:':
			print 'failed xmpp match'
			return 1
		else:
			shorturi = self.uri[5:]

		if '?' in shorturi:
			(self.hierxmpp, self.querycomp) = shorturi.split('?', 1)
			if '#' in self.querycomp:
				(self.querycomp, self.fragment) = self.querycomp.split('#', 1)
		elif '#' in shorturi:
			(self.hierxmpp, self.fragment) = shorturi.split('#', 1)
		elif len(shorturi) > 0:
			self.hierxmpp = shorturi
		else:
			return 1
		# TODO: Need to add handling for fragment parts

		if self.hierxmpp[0:2] == '//':
			self.authpath = self.hierxmpp
		else:
			self.pathxmpp = self.hierxmpp

		# TODO: Need to handle authpath stuff. Only doing pathxmpp for now.

		if not self.pathxmpp is None:
			if '@' in self.pathxmpp:
				(self.nodeid, self.host) = self.pathxmpp.split('@', 1)
				if '/' in self.host:
					(self.host, self.resid) = self.host.split('/', 1)
			elif '/' in self.pathxmpp:
				(self.host, self.resid) = self.pathxmpp.split('/', 1)
			else:
				self.host = self.pathxmpp

		#TODO: Need to add quite a few more things past this point.
		#	  For now I'm just putting in enough to do what I need with
		#	  the URI parsing for Gajim for the time being.

		if not self.querycomp is None:
			if ';' in self.querycomp:
				(self.querytype, pairs) = self.querycomp.split(';', 1)
			else:
				self.querytype = self.querycomp
				pairs = None

			if not pairs is None:
				if ';' in pairs:
					pairs = pairs.split(';')
				else:
					pairs = [pairs]

				for keyval in pairs:
					(key, val) = keyval.split('=',1)
					self.pairs[key] = val

				# TODO: This needs a LOT more validation. These are all
				#	listed as unreserved/pct-encoded in the RFC. This
				#	is probably going to need to wait for uriparse.py
				#	as I believe (hope) that it will handle this portion
				#	of the validation for us.

		# This is the endpoint of the function. If we haven't returned 
		# elsewhere by now, it means that there's actually useful data
		# in place, so we consider the object created.
		return 0

	# These two getters should probably be moved into a separate XEP-0147
	# library at some point. Right now they're just here as helpers.
	def getjid(self):
		"""gets the host or nodeid@host if available

		The False return value can be used for simple alternative switching
		in code. This is a helper function for XEP-0147.

		e.g.

		>>> myjid = (xmppuri.getjid() or "default")

		@return: string containing the host or nodeid@host if available,
		  otherwise False.
		"""
		if not self.nodeid is None:
			return "%s@%s" % (self.nodeid, self.host)
		elif not self.host is None:
			return self.host
		else:
			return False

	def getaction(self):
		"""return the querytype component of the URI if available

		This should be used in conjunction with isvaliduri because an otherwise
		valid URI may not contain a querytype. This is a helper function for
		XEP-0147.

		e.g.

		>>> if xmppuri.isvaliduri and xmppuri.getaction():
		...	 print "Continue processing."

		@return: string containing the querytype if available, otherwise False.
		"""
		if not self.querytype is None:
			return self.querytype
                else:
			return False
