/*
 * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
 * Copyright (C) 2004-2007 Apple Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#include "config.h"

#if ENABLE(WML)
#include "WMLPageState.h"

#include "BackForwardController.h"
#include "BackForwardList.h"
#include "Document.h"
#include "Frame.h"
#include "HistoryItem.h"
#include "KURL.h"
#include "Page.h"
#include <wtf/text/CString.h>

namespace WebCore {

WMLPageState::WMLPageState(Page* page)
    : m_page(page)
    , m_hasAccessControlData(false)
{
}

WMLPageState::~WMLPageState()
{
    m_variables.clear();
}

#ifndef NDEBUG
// Debugging helper for use within gdb
void WMLPageState::dump()
{
    WMLVariableMap::iterator it = m_variables.begin();
    WMLVariableMap::iterator end = m_variables.end();

    fprintf(stderr, "Dumping WMLPageState (this=%p) associated with Page (page=%p)...\n", this, m_page);
    for (; it != end; ++it)
        fprintf(stderr, "\t-> name: '%s'\tvalue: '%s'\n", (*it).first.latin1().data(), (*it).second.latin1().data());
}
#endif

void WMLPageState::reset()
{
    // Remove all the variables
    m_variables.clear();

    // Clear the navigation history state 
    if (m_page)
        m_page->backForward()->client()->clearWMLPageHistory();
}

static inline String normalizedHostName(const String& passedHost)
{
    if (passedHost.contains("127.0.0.1")) {
        String host = passedHost;
        return host.replace("127.0.0.1", "localhost");
    }

    return passedHost;
}

static inline String hostFromURL(const KURL& url)
{
    // Default to "localhost"
    String host = normalizedHostName(url.host());
    return host.isEmpty() ? "localhost" : host;
}

static KURL urlForHistoryItem(Frame* frame, HistoryItem* item)
{
    // For LayoutTests we need to find the corresponding WML frame in the test document
    // to be able to test access-control correctly. Remember that WML is never supposed
    // to be embedded anywhere, so the purpose is to simulate a standalone WML document.
    if (frame->document()->isWMLDocument())
        return item->url();

    const HistoryItemVector& childItems = item->children();
    HistoryItemVector::const_iterator it = childItems.begin();
    const HistoryItemVector::const_iterator end = childItems.end();

    for (; it != end; ++it) {
        const RefPtr<HistoryItem> childItem = *it;
        Frame* childFrame = frame->tree()->child(childItem->target());
        if (!childFrame)
            continue;

        if (Document* childDocument = childFrame->document()) {
            if (childDocument->isWMLDocument())
                return childItem->url();
        }
    }

    return item->url();
}

static bool tryAccessHistoryURLs(Page* page, KURL& previousURL, KURL& currentURL)
{
    if (!page)
        return false;

    Frame* frame = page->mainFrame();
    if (!frame || !frame->document())    
        return false;

    HistoryItem* previousItem = page->backForward()->backItem();
    if (!previousItem)
        return false;

    HistoryItem* currentItem = page->backForward()->currentItem();
    if (!currentItem)
        return false;

    previousURL = urlForHistoryItem(frame, previousItem);
    currentURL = urlForHistoryItem(frame, currentItem);

    return true;
}

bool WMLPageState::processAccessControlData(const String& domain, const String& path)
{
    if (m_hasAccessControlData)
        return false;

    m_hasAccessControlData = true;

    KURL previousURL, currentURL;
    if (!tryAccessHistoryURLs(m_page, previousURL, currentURL))
        return true;

    // Spec: The path attribute defaults to the value "/"
    m_accessPath = path.isEmpty() ? "/" : path;

    // Spec: The domain attribute defaults to the current decks domain.
    String previousHost = hostFromURL(previousURL);
    m_accessDomain = domain.isEmpty() ? previousHost : normalizedHostName(domain);

    // Spec: To simplify the development of applications that may not know the absolute path to the 
    // current deck, the path attribute accepts relative URIs. The user agent converts the relative 
    // path to an absolute path and then performs prefix matching against the PATH attribute.
    Document* document = m_page->mainFrame() ? m_page->mainFrame()->document() : 0;
    if (document && previousHost == m_accessDomain && !m_accessPath.startsWith("/")) {
        String currentPath = currentURL.path();

        size_t index = currentPath.reverseFind('/');
        if (index != WTF::notFound)
            m_accessPath = document->completeURL(currentPath.left(index + 1) + m_accessPath).path();
    }

    return true;
}

void WMLPageState::resetAccessControlData()
{
    m_hasAccessControlData = false;
    m_accessDomain = String();
    m_accessPath = String();
}

bool WMLPageState::canAccessDeck() const
{
    if (!m_hasAccessControlData)
        return true;

    KURL previousURL, currentURL;
    if (!tryAccessHistoryURLs(m_page, previousURL, currentURL))
        return true;

    if (equalIgnoringFragmentIdentifier(previousURL, currentURL))
       return true;

    return hostIsAllowedToAccess(hostFromURL(previousURL)) && pathIsAllowedToAccess(previousURL.path());
}

bool WMLPageState::hostIsAllowedToAccess(const String& host) const
{
    // Spec: The access domain is suffix-matched against the domain name portion of the referring URI
    Vector<String> subdomainsAllowed;
    if (m_accessDomain.contains('.'))
        m_accessDomain.split('.', subdomainsAllowed);
    else
        subdomainsAllowed.append(m_accessDomain);

    Vector<String> subdomainsCheck;
    if (host.contains('.'))
        host.split('.', subdomainsCheck);
    else
        subdomainsCheck.append(host);

    Vector<String>::iterator itAllowed = subdomainsAllowed.end() - 1;
    Vector<String>::iterator beginAllowed = subdomainsAllowed.begin();

    Vector<String>::iterator itCheck = subdomainsCheck.end() - 1;
    Vector<String>::iterator beginCheck = subdomainsCheck.begin();

    bool hostOk = true;
    for (; itAllowed >= beginAllowed && itCheck >= beginCheck; ) {
        if (*itAllowed != *itCheck) {
            hostOk = false;
            break;
        }

        --itAllowed;
        --itCheck;
    }

    return hostOk;
}

bool WMLPageState::pathIsAllowedToAccess(const String& path) const
{
    // Spec: The access path is prefix matched against the path portion of the referring URI
    Vector<String> subpathsAllowed;
    if (m_accessPath.contains('/'))
        m_accessPath.split('/', subpathsAllowed);
    else
        subpathsAllowed.append(m_accessPath);

    Vector<String> subpathsCheck;
    if (path.contains('/'))
        path.split('/', subpathsCheck);
    else
        subpathsCheck.append(path);

    Vector<String>::iterator itAllowed = subpathsAllowed.begin();
    Vector<String>::iterator endAllowed = subpathsAllowed.end();

    Vector<String>::iterator itCheck = subpathsCheck.begin();
    Vector<String>::iterator endCheck = subpathsCheck.end();

    bool pathOk = true;
    for (; itAllowed != endAllowed && itCheck != endCheck; ) {
        if (*itAllowed != *itCheck) {
            pathOk = false;
            break;
        }

        ++itAllowed;
        ++itCheck;
    }

    return pathOk;
}

}

#endif
