#!/usr/bin/python -u
# This file is a portion of the Red Hat Network Panel Applet
#
# Copyright (C) 1999-2003 Red Hat, Inc. All Rights Reserved.
# Distributed under GPL version 2.
#
# This implement the protocols support needed for the non-RHN accesses
# like YUM repositories.
#
# Author: Daniel Veillard
#
# $Id: rhn_applet_protocols.py,v 1.2 2004/01/07 17:56:14 veillard Exp $

import os
import time
import string
import urlparse
import rhn.connections
import rhn_utils
import rhn_applet_version
try:
    from ftplib import FTP
    have_ftp = 1
except:
    have_ftp = 0

#
# FTP support requires callbacks
#
_ftp_data = ""
def _grab_ftp_data(data):
    global _ftp_data
    _ftp_data = _ftp_data + data

rhnAppletConnHandler = None

def split_host(hoststring):
    """Handle a full location for an URL and chunk it into the 
       appropriate (host, port, user, passwd) list."""
    l = string.split(hoststring, '@', 1)
    host = None
    port = None
    user = None
    passwd = None

    if len(l) == 2:
        hostport = l[1]
        # userinfo present
        userinfo = string.split(l[0], ':', 1)
        user = userinfo[0]
        if len(userinfo) == 2:
            passwd = userinfo[1]
    else:
        hostport = l[0]

    # Now parse hostport
    arr = string.split(hostport, ':', 1)
    host = arr[0]
    if len(arr) == 2:
        port = arr[1]
        
    return (host, port, user, passwd)

class rhnAppletConn:
    """Class for handling connections requests"""
    def __init__(self, http_proxy_url = None, http_proxy_username = None,
                 http_proxy_password = None):
	self.__http_proxy_url__ = http_proxy_url
	self.__http_proxy_username__ = http_proxy_username
	self.__http_proxy_password__ = http_proxy_password

    def set_http_proxy(self, http_proxy_url, http_proxy_username = None,
                       http_proxy_password = None):
	self.__http_proxy_url__ = http_proxy_url
	self.__http_proxy_username__ = http_proxy_username
	self.__http_proxy_password__ = http_proxy_password

    def __do_file_request(self, scheme, host, port, path, lastmodified, base):
        """Implements direct file access"""
	if host != None and host != "":
	    rhn_utils.log_debug("File access uses host %s: %s " % (host, base))
	    raise rhn_utils.rhnAppletNetworkException(13,
				"File access uses host %s: %s " % (host, base))

        try:
	    tim = time.gmtime(os.stat(path)[8])
	except:
	    rhn_utils.log_debug("Failed to stat file: %s " % (path))
	    raise rhn_utils.rhnAppletNetworkException(13,
				"Failed to stat file: %s " % (path))
	    
	if lastmodified != None:
	    if tim == time.strptime(lastmodified, "%a, %d %b %Y %H:%M:%S GMT"):
	        return None
	    
	lastmodified = time.strftime("%a, %d %b %Y %H:%M:%S GMT", tim)

	try:
	    content = open(path, "r").read()
	except:
	    rhn_utils.log_debug("Failed to read file: %s " % (path))
	    raise rhn_utils.rhnAppletNetworkException(13,
				"Failed to read file: %s " % (path))
	return (content, lastmodified)

    def __do_http_request(self, scheme, host, port, path, lastmodified,
			  base, count):
	"""Implements HTTP and HTTPS protocol request, takes care of
	   modification timestamps, redirection and HTTP proxy if present"""
	if scheme == 'http':
	    if self.__http_proxy_url__ != None and \
	       self.__http_proxy_url__ != '':
		conn = rhn.connections.HTTPProxyConnection(
			 self.__http_proxy_url__, host, port,
			 self.__http_proxy_username__,
			 self.__http_proxy_password__)
	    else:
		conn = rhn.connections.HTTPConnection(host, port)
	elif scheme == 'https':
	    if self.__http_proxy_url__ != None and \
	       self.__http_proxy_url__ != '':
	        #
		# Still have trouble for proxy + SSL at least in my
		# home environment testbed
		#
		conn = rhn.connections.HTTPSProxyConnection(
			 self.__http_proxy_url__, host, port,
			 self.__http_proxy_username__,
			 self.__http_proxy_password__)
	    else:
		conn = rhn.connections.HTTPSConnection(host, port)

	headers = { "User-Agent": "RHN-Applet/" + rhn_applet_version.version }
	if lastmodified == None:
	    conn.request("GET", path, None, headers)
	else:
	    headers["If-Modified-Since"] = lastmodified
	    conn.request("GET", path, None, headers)
        response = conn.getresponse()

	if response.status == 304:
	    return None
	if response.status == 301 or response.status == 302:
	    # Redirect, get the Location
	    try:
	        redir = response.getheader("Location")
	    except:
		rhn_utils.log_debug("Redirect error: %s no Location" % (base))
		raise rhn_utils.rhnAppletNetworkException(13,
	                            "Redirect error: %s no Location" % (base))
	    return self.request(redir, lastmodified, count + 1, base)

	if response.status / 100 != 2:
	    raise rhn_utils.rhnAppletNetworkException(13,
	                        "connection error: %s" % (url))
	try:
	    lastmodified = response.getheader("Last-Modified").strip()
	except:
	    lastmodified = None
	data = response.read()
	try:
	    conn.close()
	except:
	    pass
	return (data, lastmodified)

    def __do_ftp_request(self, scheme, host, port, path, lastmodified, base):
        global _ftp_data
        global _grab_ftp_data

        print "__do_ftp_request: %s, %s, %s, %s, %s, %s" % (
	          scheme, host, port, path, lastmodified, base)
        try:
	    if port == None:
		ftp = FTP(host)
	    else:
	        ftp = FTP(host, port)
	    ftp.login()
	    _ftp_data = ""
	    status = ftp.dir(path, _grab_ftp_data)
	except:
	    rhn_utils.log_debug("FTP error: failed to connect")
	    raise rhn_utils.rhnAppletNetworkException(13,
				"FTP error: failed to connect")
	if lastmodified != None and lastmodified != "" and \
	   _ftp_data == lastmodified:
	    return None # assume unchanged
	lastmodified = _ftp_data
	_ftp_data = ""
	try:
	    res = ftp.retrbinary("RETR %s" % path, _grab_ftp_data)
	    ftp.quit()
	    ftp.close()
	except:
	    rhn_utils.log_debug("FTP error: failed to fetch data")
	    raise rhn_utils.rhnAppletNetworkException(13,
				"FTP error: failed to fetch data")
	if res[0] != '2':
	    rhn_utils.log_debug("FTP error: failed to fetch data")
	    raise rhn_utils.rhnAppletNetworkException(13,
				"FTP error: failed to fetch data")

	data = _ftp_data
	_ftp_data = ""
	return(data, lastmodified)

    def request(self, url, lastmodified = None, count = 0, base = None):
        """Implement a connection requests, on success and if modified
	   it returns a tuple of the content and a new lastmodified value,
	   on if not modified returns None, and generate an
	   rhnAppletNetworkException in case of error"""
        if url == None:
	    rhn_utils.log_debug("URL error: no URL")
	    raise rhn_utils.rhnAppletNetworkException(13,
	                        "URL error: no URL")
        if base == None:
	    base = url
        if count > 5:
	    rhn_utils.log_debug("URL error: %s too many redirections" % (base))
	    raise rhn_utils.rhnAppletNetworkException(13,
	                        "URL error: %s too many redirections" % (base))

	try:
	    (scheme, location, path, query, frag) = urlparse.urlsplit(url)
	    path = path + query;
	except:
	    rhn_utils.log_debug("URL error: %s" % (url))
	    raise rhn_utils.rhnAppletNetworkException(13,
	                        "URL error: %s" % (url))
	if frag != '':
	    rhn_utils.log_debug("Fragment identifier not handled: %s" % (url))
	    raise rhn_utils.rhnAppletNetworkException(13,
	                        "Fragment identifier not handled: %s" % (url))
	(host, port, user, passwd) = split_host(location)
	try:
	    port = int(port)
	except:
	    port = None

	if scheme == 'http' or scheme == 'https':
	    return self.__do_http_request(scheme, host, port, path,
	                                  lastmodified, base, count)
	elif scheme == 'file':
	    return self.__do_file_request(scheme, host, port, path,
	                                  lastmodified, base)
	elif scheme == 'ftp':
	    return self.__do_ftp_request(scheme, host, port, path,
	                                 lastmodified, base)
	else:
	    rhn_utils.log_debug("unsupported protocol: %s" % (url))
	    raise rhn_utils.rhnAppletNetworkException(13,
	                        "unsupported protocol: %s" % (url))

def init_connections(http_proxy_url = None, http_proxy_username = None,
                     http_proxy_password = None):
    """Initialize the rhnAppletConnHandler used to serve connections"""
    global rhnAppletConnHandler

    if rhnAppletConnHandler == None:
	rhnAppletConnHandler = rhnAppletConn(http_proxy_url,
	                                     http_proxy_username,
					     http_proxy_password)
    elif http_proxy_url != None or http_proxy_username != None or \
         http_proxy_password != None:
	rhnAppletConnHandler.set_http_proxy(http_proxy_url,
	                                    http_proxy_username,
					    http_proxy_password)

def request(url, lastmodified = None):
    """Implement a connection requests, on success and if modified
       it returns a tuple of the content and a new lastmodified value,
       on if not modified returns None, and generate an
       rhnAppletNetworkException in case of error"""
    global rhnAppletConnHandler

    if rhnAppletConnHandler == None:
        init_connections()
    if rhnAppletConnHandler == None:
        return -1
    return(rhnAppletConnHandler.request(url, lastmodified))
    
if __name__ == "__main__":
    res = request("file:///tmp/tst.xml")
    print res
    if res != None:
        res2 = request("file:///tmp/tst.xml", res[1])
	print res2
    res = request("http://veillard.com/tst2.xml")
    print res
    if res != None:
        res2 = request("http://veillard.com/tst2.xml", res[1])
	print res2
    res = request("http://veillard.com/tst.xml")
    print res
    if res != None:
        res2 = request("http://veillard.com/tst.xml", res[1])
	print res2
