/*
 * Worldvisions Weaver Software:
 *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
 * 
 * WvUrl is a simple URL-parsing class with built-in (though still somewhat
 * inconvenient) DNS resolution.
 * 
 * See wvurl.h.
 */ 
#include "wvurl.h"
#include "strutils.h"

// A static list of the default ports for each protocol.
struct DefaultPort
{
    const char *proto;
    int port;
    bool uses_slashes;
};

// The protocols must be arranged from longest to shortest because they're
// compared with strncmp, so "https://" will also match http.
static DefaultPort portmap[] = {
    { "exchangeits", 7070, false },
    { "exchangeit", 6969, false },
    { "https", 443, true },
    { "http", 80, true },
    { "file", 0, true },
    { "sip", 5060, false },
    { "ftp", 21, true },
    { "ldaps", 636, false },
    { "ldap", 389, false },
    { NULL, 0 }
};

// Look up the protocol and return the default port.
static int get_default_port(WvString proto)
{
    DefaultPort *p = portmap;
    for (p = portmap; p->proto != NULL; p++)
    {
        if (strncmp(p->proto, proto, strlen(p->proto)) == 0)
            return p->port;
    }
    return -1;
}

// Look up the protocol and decide whether it uses slashes (http) or not (sip)
// A check of rfc2396 shows that the URI standard actually distinguishes
// these: 'hierarchical' vs. 'opaque'.
static bool protocol_uses_slashes(WvString proto)
{
    DefaultPort *p = portmap;
    for (p = portmap; p->proto != NULL; p++)
    {
        if (strncmp(p->proto, proto, strlen(p->proto)) == 0)
            return p->uses_slashes;
    }
    return false;
}

// Split up the URL into a hostname, a port, and the rest of it.
WvUrl::WvUrl(WvStringParm url) : err("No error")
{
    WvString work(url);
    char *cptr, *wptr = work.edit();
    
    port = 0; // error condition by default
    addr = NULL;
    resolving = true;
    
    // deal with extra whitespace.
    wptr = trim_string(wptr);
    cptr = wptr + strcspn(wptr, " \t\r\n");
    *cptr = 0;

    // if it's not one of these easy prefixes, give up.  Our URL parser is
    // pretty dumb.
    if (get_default_port(wptr) < 0)
    {
	err = "WvUrl cannot handle the given protocol.";
	return;
    }

    cptr = strchr(wptr, ':');
    if (!cptr)
    {
        err = "No colon after the protocol.";
        return;
    }
    *cptr = 0;
    proto = wptr;

    bool use_slashes = protocol_uses_slashes(proto);
    wptr = cptr + (use_slashes ? 3 : 1);

    cptr = strchr(wptr, '@');
    if (!cptr) // no user given
    {
        user = "";
	password = "";
    }
    else
    {
        *cptr = 0;
	char *cptr2 = strchr(wptr, ':');
	if (cptr2 && (*(cptr2+1) != 0))
	{
	    *cptr2 = 0;
	    password = cptr2 + 1;
	}
	else
	    password = ""; 
        user = wptr;
        wptr = cptr + 1;
    }
    
    cptr = strchr(wptr, '/');
    if (!cptr) // no path given
	file = use_slashes ? "/" : "";
    else
    {
	file = cptr;
	*cptr = 0;
    }
    
    cptr = strchr(wptr, ':');
    if (!cptr)
	port = get_default_port(proto);
    else
    {
	port = atoi(cptr+1);
	*cptr = 0;
    }

    hostname = wptr;

    resolve();
}


WvUrl::WvUrl(const WvUrl &url) : err("No error")
{
    addr = NULL;
    resolving = true;
    
    proto = url.proto;
    user = url.user;
    password = url.password;
    hostname = url.hostname;
    file = url.file;
    port = url.port;

    resolve();
}


WvUrl::~WvUrl()
{
    if (addr) delete addr;
}


bool WvUrl::resolve()
{
    const WvIPAddr *ip;
    int numaddrs;
    
    numaddrs = dns.findaddr(0, hostname, &ip);
    if (!numaddrs) // error condition
    {
	err = WvString("Host '%s' could not be found.", hostname);
	resolving = false;
	return false;
    }
    else if (numaddrs < 0) // still waiting
    {
	resolving = true;
	return false;
    }
    else // got at least one address
    {
	resolving = false;
	if (addr) delete addr;
	addr = new WvIPPortAddr(*ip, port);
	return true;
    }
}


// Print out the URL, using the port name (if it's not 80), and either the 
// hostname (if we know it) or the address (if we know that instead.)
WvUrl::operator WvString () const
{
    if (!isok())
	return WvString("(Invalid URL: %s)", err);

    WvString protostr;
    if (protocol_uses_slashes(proto))
        protostr = WvString("%s://", proto);
    else
        protostr = WvString("%s:", proto);
    WvString userstr("");
    if (user && user.len() != 0)
    {
        userstr = WvString("%s", user);
	if (password && password.len() != 0)
	    userstr.append(WvString(":%s@", password));
	else
	    userstr.append("@");
    }
    WvString portstr("");
    if (port && port != get_default_port(proto))
	portstr = WvString(":%s", port);
    if (hostname)
	return WvString("%s%s%s%s%s", protostr, userstr, hostname, portstr, file);
    else if (addr)
	return WvString("%s%s%s%s%s", protostr, userstr, *addr, portstr, file);
    else
    {
	assert(0);
	return WvString("(Invalid URL)");
    }
}


