/*
 * Copyright (c) 2004 Nokia. 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 theb above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the
 * distribution.
 *
 * Neither the name of Nokia 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 THE
 * COPYRIGHT OWNER 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 "Cookie.h"
#include "CookieStorage.h"
#include <assert.h>
#include <string.h> // for strcmp

/** Class storing all cookies from one domain. */
class CookieJar
{
public:
    CookieJar(const GURI * host);
    ~CookieJar();
    
    gchar * cookiesForPath(const GURI * path);
    
    void addCookie(Cookie * cookie);
private:
    GURI * host;
    GHashTable * cookies;
};


/* Cookie jar functions */
static void cookie_jar_cookie_destroy(gpointer data, gpointer user_data)
{
    assert(data);
    Cookie * c = static_cast<Cookie *>(data);
    delete c;
}

static void cookie_jar_value_destroy(gpointer item)
{
    assert(item);
    GList * list = static_cast<GList *>(item);
    g_list_foreach(list, cookie_jar_cookie_destroy, NULL);
    g_list_free(list);
}

static void concatenate_cookies(gpointer data, gpointer user_data) {
    assert(data);
    assert(user_data);
    Cookie * cookie = static_cast<Cookie *>(data);
    GString * buffer = static_cast<GString *>(user_data);
    
    g_string_append_printf(buffer, "; %s", cookie->cookieString());
}

static void cookies_key_destroy(gpointer item)
{
    assert(item);
    g_free(static_cast<gchar *>(item));
}

static void cookiesByURL_value_destroy(gpointer item)
{
    assert(item);
    CookieJar * jar = static_cast<CookieJar *>(item);
    delete jar;
}   

CookieJar::CookieJar(const GURI * ahost) 
    : host(gnet_uri_clone(ahost))
{
    cookies = g_hash_table_new_full(g_str_hash, 
                                    g_str_equal, 
                                    cookies_key_destroy,
                                    cookie_jar_value_destroy);
}

CookieJar::~CookieJar() 
{
    g_hash_table_destroy(cookies);
    gnet_uri_delete(host);
}
    
void CookieJar::addCookie(Cookie * cookie)
{
    assert(cookie);
     
    const gchar * path = cookie->path() ? cookie->path() : "/";
    gpointer key = 0L, value = 0L;
    gchar * keyString;
    GList * cookieList = 0L;
    
    // stealing is necessary in order to avoid list being freed in g_hash_table_insert.
    // Therefore we also need to use original key to avoid memory being leaked
    // on stolen keys. -- psalmi
    if (g_hash_table_lookup_extended(cookies, path, &key, &value)) {
        keyString = static_cast<gchar *>(key);
        cookieList = static_cast<GList *>(value);
    } else {
        keyString = g_strdup(path);
    }
    g_hash_table_steal(cookies, keyString);
    cookieList = g_list_append(cookieList, cookie);

    g_hash_table_insert(cookies, keyString, cookieList);
}

/** Returned memory must be free'd. */
gchar * CookieJar::cookiesForPath(const GURI * uriPath)
{
    assert(uriPath);
    assert(cookies);
    if (g_hash_table_size(cookies) == 0) {
        return 0L;
    }
    
    GString* buffer = NULL;  
    buffer = g_string_sized_new (32);
    
    g_string_append_printf (buffer, "$Version=1");

    GURI * uri = gnet_uri_clone(uriPath);
    GURI * parent = 0L;
    GList * cookieList = 0L;
    bool haveParent = true;
    void * item;    
    const gchar * path = 0L;
    
    while (haveParent) {
        if (parent) {
            gnet_uri_delete(uri);
            uri = parent;
        }
        path = uri->path ? uri->path : "/";
        item = g_hash_table_lookup(cookies, path);
        if (item) {
            cookieList = static_cast<GList *>(item);
            if (cookieList) g_list_foreach(cookieList, concatenate_cookies, buffer);
        }
        haveParent = (parent = (uri ? gnet_uri_parent(uri) : 0L));
    }
    gnet_uri_delete(uri);
    gchar * str = buffer->str;
    g_string_free (buffer, FALSE); 
    return str;
}



/**
 * Dummy implementation for cookie storage
 * Doesn't implement any sort of policy management. 
 * Stores cookie according to url,
 * Is not persistent
 */
CookieStorage::CookieStorage()
    : enabled(true)
    , m_cookieString(0)
{
    cookiesByURL = g_hash_table_new_full(g_str_hash, 
					 g_str_equal, 
					 cookies_key_destroy, 
					 cookiesByURL_value_destroy);
}

CookieStorage::~CookieStorage()
{
    g_hash_table_destroy(cookiesByURL);
    if (m_cookieString) g_free(m_cookieString);
    m_cookieString = 0L;
}


bool CookieStorage::cookiesEnabled()
{
    return enabled;
}

const gchar * CookieStorage::cookiesForURL(const gchar *URL)
{    
    if (!enabled || !URL) return 0L;

    if (m_cookieString) {
        g_free(m_cookieString);
        m_cookieString = 0L;
    }
    
    GURI * uri = gnet_uri_new(URL);
    if (!uri->hostname) {
        return 0L;
    }

    CookieJar * cj = (CookieJar *) g_hash_table_lookup(cookiesByURL, uri->hostname);

    if (cj) m_cookieString = cj->cookiesForPath(uri);
    gnet_uri_delete(uri);

    return m_cookieString;
}

void CookieStorage::setCookiesForURL(const gchar *cookies, 
				     const gchar *URL, 
				     const gchar *policyBaseURL)
{
    if (!enabled) return;
    
    GURI * uri = gnet_uri_new(URL);
    if (!cookies) {
	g_hash_table_remove(cookiesByURL, uri->hostname);
    } else {
        CookieJar * cj = static_cast<CookieJar *>(g_hash_table_lookup(cookiesByURL, uri->hostname));
        
        if (!cj) {
            cj = new CookieJar(uri);
            g_hash_table_replace(cookiesByURL, g_strdup(uri->hostname), cj);
        }

        cj->addCookie(new Cookie(cookies, URL));
    }

    gnet_uri_delete(uri);
}

void CookieStorage::useAsSharedAdapter()
{
    WebCoreCookieAdapter::setSharedAdapter(sharedAdapter());
}

CookieStorage* CookieStorage::sharedAdapter()
{
    static CookieStorage singleton;
    return &singleton;
}

