/**************************************************************************
 *
 * Copyright (c) 2000-2003 Intel Corporation 
 * All rights reserved. 
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met: 
 *
 * - Redistributions of source code must retain the above copyright notice, 
 * this list of conditions and the following disclaimer. 
 * - Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the documentation 
 * and/or other materials provided with the distribution. 
 * - Neither name of Intel Corporation nor the names of its contributors 
 * may be used to endorse or promote products derived from this software 
 * without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 **************************************************************************/


#include "config.h"


#include "upnp.h"
#include "util.h"
#include "webserver.h"
#include "uri.h"
#include "membuffer.h"
#include "urlconfig.h"
#include "unixutil.h"


#include <assert.h>


#ifdef WIN32
	#include <winsock2.h>
#else
	#include <arpa/inet.h>
	#include <netinet/in.h>
	#include <sys/types.h>
	#include <sys/socket.h>
#endif


/************************************************************************
*	Function :	addrToString
*
*	Parameters :
*		IN const struct sockaddr* addr ;	socket address object with 
*					the IP Address and port information
*		OUT char ipaddr_port[] ;	character array which will hold the 
*					IP Address  in a string format.
*
*	Description : Converts an Internet address to a string and stores it 
*		a buffer.
*
*	Return : void ;
*
*	Note :
************************************************************************/
static UPNP_INLINE void
addrToString( IN const struct sockaddr *addr,
              OUT char ipaddr_port[] )
{
    char buf_ntop[64];

    if( addr->sa_family == AF_INET ) {
        struct sockaddr_in* sa4 = (struct sockaddr_in*)addr;
        inet_ntop(AF_INET, &sa4->sin_addr, buf_ntop, sizeof(buf_ntop) );
        sprintf( ipaddr_port, "%s:%d", buf_ntop, ntohs( sa4->sin_port ) );
    } else if( addr->sa_family == AF_INET6 ) {
        struct sockaddr_in6* sa6 = (struct sockaddr_in6*)addr;
        inet_ntop(AF_INET6, &sa6->sin6_addr, buf_ntop, sizeof(buf_ntop) );
        sprintf( ipaddr_port, "[%s]:%d", buf_ntop, ntohs( sa6->sin6_port ) );
    }
}

/************************************************************************
*	Function :	calc_alias
*
*	Parameters :
*		IN const char* alias ;	String containing the alias
*		IN const char* rootPath ;	String containing the root path
*		OUT char** newAlias ;	String pointer to hold the modified new
*					alias
*
*	Description : Determine alias based urlbase's root path.
*
*	Return : int ;
*		UPNP_E_SUCCESS - On Success.
*		UPNP_E_OUTOF_MEMORY - On Failure to allocate memory for new alias
*
*	Note : 'newAlias' should be freed using free()
************************************************************************/
static UPNP_INLINE int
calc_alias( IN const char *alias,
            IN const char *rootPath,
            OUT char **newAlias )
{
    const char *aliasPtr;
    size_t root_len;
    char *temp_str;
    size_t new_alias_len;
    char *alias_temp;

    assert( rootPath );
    assert( alias );

    // add / suffix, if missing
    root_len = strlen( rootPath );
    if( root_len == 0 || rootPath[root_len - 1] != '/' ) {
        temp_str = "/";
    } else {
        temp_str = "";          // suffix already present
    }

    // discard / prefix, if present
    if( alias[0] == '/' ) {
        aliasPtr = alias + 1;
    } else {
        aliasPtr = alias;
    }

    new_alias_len = root_len + strlen( temp_str ) + strlen( aliasPtr );
    alias_temp = ( char * )malloc( new_alias_len + 1 );
    if( alias_temp == NULL ) {
        return UPNP_E_OUTOF_MEMORY;
    }

    strcpy( alias_temp, rootPath );
    strcat( alias_temp, temp_str );
    strcat( alias_temp, aliasPtr );

    *newAlias = alias_temp;
    return UPNP_E_SUCCESS;
}

/************************************************************************
*	Function :	calc_descURL
*
*	Parameters :
*		IN const char* ipPortStr ;	String containing the port number
*		IN const char* alias ;		String containing the alias
*		OUT char descURL[LINE_SIZE] ;	buffer to hold the calculated 
*					description URL
*
*	Description : Determines the description URL
*
*	Return : int ;
*		UPNP_E_SUCCESS - On Success
*		UPNP_E_URL_TOO_BIG - length of the URL is determined to be to
*		exceeding the limit.
*
*	Note :
************************************************************************/
static UPNP_INLINE int
calc_descURL( IN const char *ipPortStr,
              IN const char *alias,
              OUT char descURL[LINE_SIZE] )
{
    size_t len;
    const char *http_scheme = "http://";

    assert( ipPortStr != NULL && strlen( ipPortStr ) > 0 );
    assert( alias != NULL && strlen( alias ) > 0 );

    len = strlen( http_scheme ) + strlen( ipPortStr ) + strlen( alias );

    if( len > ( LINE_SIZE - 1 ) ) {
        return UPNP_E_URL_TOO_BIG;
    }
    strcpy( descURL, http_scheme );
    strcat( descURL, ipPortStr );
    strcat( descURL, alias );

    UpnpPrintf( UPNP_INFO, API, __FILE__, __LINE__,
        "desc url: %s\n", descURL );

    return UPNP_E_SUCCESS;
}

/************************************************************************
*	Function :	config_description_doc
*
*	Parameters :
*		INOUT IXML_Document *doc ;IMXL description document to be 
*					configured	
*		IN const char* ip_str ;	string containing the IP port number
*		OUT char** root_path_str ;	buffer to hold the root path
*					of the configured description document
*		INOUT IXML_Document *doc :	Description document
*		IN const char* ip_str :	ipaddress string
*		OUT char** root_path_str :	root path string
*
*	Description : Configure the description document. Add the standard 
*		format and then add information from the root device and any
*		child nodes.
*
*	Return : int ;
*		UPNP_E_SUCCESS - On Success
*		UPNP_E_OUTOF_MEMORY - Default Error
*		UPNP_E_INVALID_DESC - Invalid child node		
*		UPNP_E_INVALID_URL - Invalid node information
*
*	Note :
************************************************************************/
static int
config_description_doc( INOUT IXML_Document * doc,
                        IN const char *ip_str,
                        OUT char **root_path_str )
{
    xboolean addNew = FALSE;
    IXML_NodeList *baseList;
    IXML_Element *element = NULL;
    IXML_Element *newElement = NULL;
    IXML_Node *textNode = NULL;
    IXML_Node *rootNode = NULL;
    IXML_Node *urlbase_node = NULL;
    char *urlBaseStr = "URLBase";
    const DOMString domStr = NULL;
    uri_type uri;
    int err_code;
    int len;
    membuffer url_str;
    membuffer root_path;

    membuffer_init( &url_str );
    membuffer_init( &root_path );

    err_code = UPNP_E_OUTOF_MEMORY; // default error

    baseList = ixmlDocument_getElementsByTagName( doc, urlBaseStr );
    if( baseList == NULL ) {
        // urlbase not found -- create new one
        addNew = TRUE;
        element = ixmlDocument_createElement( doc, urlBaseStr );
        if( element == NULL ) {
            goto error_handler;
        }

        if( membuffer_append_str( &url_str, "http://" ) != 0 ||
            membuffer_append_str( &url_str, ip_str ) != 0 ||
            membuffer_append_str( &url_str, "/" ) != 0 ||
            membuffer_append_str( &root_path, "/" ) != 0 ) {
            goto error_handler;
        }

        rootNode = ixmlNode_getFirstChild( ( IXML_Node * ) doc );
        if( rootNode == NULL ) {
            err_code = UPNP_E_INVALID_DESC;
            goto error_handler;
        }

        err_code =
            ixmlNode_appendChild( rootNode, ( IXML_Node * ) element );
        if( err_code != IXML_SUCCESS ) {
            goto error_handler;
        }

        textNode =
            ixmlDocument_createTextNode( doc, ( char * )url_str.buf );
        if( textNode == NULL ) {
            goto error_handler;
        }

        err_code =
            ixmlNode_appendChild( ( IXML_Node * ) element, textNode );
        if( err_code != IXML_SUCCESS ) {
            goto error_handler;
        }

    } else {
        // urlbase found
        urlbase_node = ixmlNodeList_item( baseList, 0 );
        assert( urlbase_node != NULL );

        textNode = ixmlNode_getFirstChild( urlbase_node );
        if( textNode == NULL ) {
            err_code = UPNP_E_INVALID_DESC;
            goto error_handler;
        }

        domStr = ixmlNode_getNodeValue( textNode );
        if( domStr == NULL ) {
            err_code = UPNP_E_INVALID_URL;
            goto error_handler;
        }

        len = parse_uri( domStr, strlen( domStr ), &uri );
        if( len < 0 || uri.type != ABSOLUTE ) {
            err_code = UPNP_E_INVALID_URL;
            goto error_handler;
        }

        if( membuffer_assign( &url_str, uri.scheme.buff,
                              uri.scheme.size ) != 0 ||
            membuffer_append_str( &url_str, "://" ) != 0 ||
            membuffer_append_str( &url_str, ip_str ) != 0 ) {
            goto error_handler;
        }
        // add leading '/' if missing from relative path
        if( ( uri.pathquery.size > 0 && uri.pathquery.buff[0] != '/' ) ||
            ( uri.pathquery.size == 0 )
             ) {
            if( membuffer_append_str( &url_str, "/" ) != 0 ||
                membuffer_append_str( &root_path, "/" ) != 0 ) {
                goto error_handler;
            }
        }

        if( membuffer_append( &url_str, uri.pathquery.buff,
                              uri.pathquery.size ) != 0 ||
            membuffer_append( &root_path, uri.pathquery.buff,
                              uri.pathquery.size ) != 0 ) {
            goto error_handler;
        }
        // add trailing '/' if missing
        if( url_str.buf[url_str.length - 1] != '/' ) {
            if( membuffer_append( &url_str, "/", 1 ) != 0 ) {
                goto error_handler;
            }
        }

        err_code = ixmlNode_setNodeValue( textNode, url_str.buf );
        if( err_code != IXML_SUCCESS ) {
            goto error_handler;
        }
    }

    *root_path_str = membuffer_detach( &root_path );    // return path
    err_code = UPNP_E_SUCCESS;

  error_handler:
    if( err_code != UPNP_E_SUCCESS ) {
        ixmlElement_free( newElement );
    }

    ixmlNodeList_free( baseList );

    membuffer_destroy( &root_path );
    membuffer_destroy( &url_str );

    return err_code;
}

/************************************************************************
*	Function :	configure_urlbase
*
*	Parameters :
*		INOUT IXML_Document *doc ;	IXML Description document
*		IN const struct sockaddr* serverAddr ;	socket address object
*					providing the IP address and port information
*		IN const char* alias ;	string containing the alias
*		IN time_t last_modified ;	time when the XML document was 
*					downloaded
*		OUT char docURL[LINE_SIZE] ;	buffer to hold the URL of the 
*					document.
*		INOUT IXML_Document *doc:dom document whose urlbase is to be modified
*		IN const struct sockaddr* serverAddr : ip address and port of 
*													the miniserver
*		IN const char* alias : a name to be used for the temp; e.g.:"foo.xml"
*		IN time_t last_modified :	time
*		OUT char docURL[LINE_SIZE] :	document URL
*
*	Description : Configure the full URL for the description document.
*		Create the URL document and add alias, description information.
*		The doc is added to the web server to be served using the given 
*		alias.
*
*	Return : int ;
*		UPNP_E_SUCCESS - On Success
*		UPNP_E_OUTOF_MEMORY - Default Error
*
*	Note :
************************************************************************/
int
configure_urlbase( INOUT IXML_Document * doc,
                   IN const struct sockaddr *serverAddr,
                   IN const char *alias,
                   IN time_t last_modified,
                   OUT char docURL[LINE_SIZE] )
{
    char *root_path = NULL;
    char *new_alias = NULL;
    char *xml_str = NULL;
    int err_code;
    char ipaddr_port[LINE_SIZE];

    err_code = UPNP_E_OUTOF_MEMORY; // default error

    // get IP address and port
    addrToString( serverAddr, ipaddr_port );

    // config url-base in 'doc'
    err_code = config_description_doc( doc, ipaddr_port, &root_path );
    if( err_code != UPNP_E_SUCCESS ) {
        goto error_handler;
    }
    // calc alias
    err_code = calc_alias( alias, root_path, &new_alias );
    if( err_code != UPNP_E_SUCCESS ) {
        goto error_handler;
    }
    // calc full url for desc doc
    err_code = calc_descURL( ipaddr_port, new_alias, docURL );
    if( err_code != UPNP_E_SUCCESS ) {
        goto error_handler;
    }
    // xml doc to str
    xml_str = ixmlPrintDocument( doc );
    if( xml_str == NULL ) {
        goto error_handler;
    }

    UpnpPrintf( UPNP_INFO, API, __FILE__, __LINE__,
        "desc url: %s\n", docURL );
    UpnpPrintf( UPNP_INFO, API, __FILE__, __LINE__,
        "doc = %s\n", xml_str );
    // store in web server
    err_code =
        web_server_set_alias( new_alias, xml_str, strlen( xml_str ),
                              last_modified );

error_handler:
    free( root_path );
    free( new_alias );

    if( err_code != UPNP_E_SUCCESS ) {
        ixmlFreeDOMString( xml_str );
    }
    return err_code;
}
