/**************************************************************************
 *
 * Copyright (c) 2000-2003 Intel Corporation 
 * All rights reserved. 
 * Copyright (c) 2012 France Telecom 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.
 *
 **************************************************************************/

/*!
 * \file
 */

#include "config.h"

#include "membuffer.h"
#include "unixutil.h"
#include "upnp.h"
#include "upnpdebug.h"
#include "UpnpInet.h"
#include "uri.h"
#include "urlconfig.h"
#include "upnputil.h"
#include "webserver.h"

#include <assert.h>
#include <stdio.h>

#ifdef _WIN32
	#if defined(_MSC_VER) && _MSC_VER < 1900
		#define snprintf _snprintf
	#endif
#else
	#include <sys/types.h>
#endif

#ifdef INCLUDE_DEVICE_APIS
#ifdef INTERNAL_WEB_SERVER

/************************************************************************
*	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.
*		IN size_t ipaddr_port_size ;	ipaddr_port buffer size
*
*	Description : Converts an Internet address to a string and stores it 
*		a buffer.
*
*	Return : int ;
*		UPNP_E_SUCCESS - On Success.
*		UPNP_E_BUFFER_TOO_SMALL - Given buffer doesn't have enough size.
*
*	Note :
************************************************************************/
static UPNP_INLINE int
addrToString( const struct sockaddr *addr,
              char *ipaddr_port,
              size_t ipaddr_port_size )
{
    char buf_ntop[INET6_ADDRSTRLEN];
    int rc = 0;

    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) );
        rc = snprintf( ipaddr_port, ipaddr_port_size, "%s:%d", buf_ntop,
            (int)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) );
        rc = snprintf( ipaddr_port, ipaddr_port_size, "[%s]:%d", buf_ntop,
            (int)ntohs( sa6->sin6_port ) );
    }
	if (rc < 0 || (unsigned int) rc >= ipaddr_port_size)
		return UPNP_E_BUFFER_TOO_SMALL;
	return UPNP_E_SUCCESS;
}

/************************************************************************
*	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(
	const char *alias,
	const char *rootPath,
	char **newAlias)
{
	const char *aliasPtr;
	size_t root_len;
	const 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) + (size_t)1;
	alias_temp = malloc(new_alias_len);
	if (alias_temp == NULL)
		return UPNP_E_OUTOF_MEMORY;
	memset(alias_temp, 0, new_alias_len);
	snprintf(alias_temp, new_alias_len, "%s%s%s", rootPath, temp_str, 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(
	const char *ipPortStr,
	const char *alias,
	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) + (size_t)1;
	if (len > (size_t)LINE_SIZE)
		return UPNP_E_URL_TOO_BIG;
	snprintf(descURL, len, "%s%s%s", http_scheme, ipPortStr, 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(
	IXML_Document *doc,
	const char *ip_str,
	char **root_path_str )
{
	IXML_NodeList *baseList;
	IXML_Element *element = NULL;
	IXML_Node *textNode = NULL;
	IXML_Node *rootNode = NULL;
	IXML_Node *urlbase_node = NULL;
	const 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 */
		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) {
			err_code = UPNP_E_INVALID_DESC;
			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) {
			err_code = UPNP_E_INTERNAL_ERROR;
			goto error_handler;
		}
	} else {
		/* urlbase found */
		urlbase_node = ixmlNodeList_item(baseList, 0lu);
		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, "/", (size_t)1) != 0) {
				goto error_handler;
			}
		}
		err_code = ixmlNode_setNodeValue(textNode, url_str.buf);
		if (err_code != IXML_SUCCESS) {
			err_code = UPNP_E_OUTOF_MEMORY;
			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(element);
	}
	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( IXML_Document * doc,
                   const struct sockaddr *serverAddr,
                   const char *alias,
                   time_t last_modified,
                   char docURL[LINE_SIZE] )
{
    char *root_path = NULL;
    char *new_alias = NULL;
    char *xml_str = NULL;
    int err_code;
    char ipaddr_port[LINE_SIZE];

    /* get IP address and port */
    err_code = addrToString( serverAddr, ipaddr_port, sizeof(ipaddr_port) );
    if ( err_code != UPNP_E_SUCCESS ) {
        goto error_handler;
    }

    /* 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;
}
#endif /* INCLUDE_DEVICE_APIS */
#endif /* INTERNAL_WEB_SERVER */ 
