<commit_before>/*
 * Copyright (C) 2014 Daniel Nicoletti <dantti12@gmail.com>
 *
 * 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 "headers_p.h"

#include "common.h"

#include <QStringBuilder>
#include <QStringList>

#include <QDebug>

using namespace Cutelyst;

QString Headers::contentEncoding() const
{
    return value(QStringLiteral("content_encoding"));
}

void Headers::setContentEncoding(const QString &encoding)
{
    insert(QStringLiteral("content_encoding"), encoding);
}

QString Headers::contentType() const
{
    QString ct = value(QStringLiteral("content_type"));
    return ct.section(QLatin1Char(';'), 0, 0).toLower();
}

QString Headers::contentTypeCharset() const
{
    QString ct = value(QStringLiteral("content_type"));
    QVector<QStringRef> parts = ct.splitRef(QLatin1Char(';'));
    Q_FOREACH (const QStringRef &part, parts) {
        int pos = part.indexOf(QLatin1String("charset="));
        if (pos != -1) {
            int endPos = part.indexOf(QLatin1Char(';'), pos);
            return part.mid(pos + 8, endPos).trimmed().toString().toUpper();
        }
    }
    return QString();
}

bool Headers::contentIsText() const
{
    return value(QStringLiteral("content_type")).startsWith(QLatin1String("text/"));
}

bool Headers::contentIsHtml() const
{
    QString ct = contentType();
    return ct == QLatin1String("text/html") ||
            ct == QLatin1String("application/xhtml+xml") ||
            ct == QLatin1String("application/vnd.wap.xhtml+xml");
}

bool Headers::contentIsXHtml() const
{
    QString ct = contentType();
    return ct == QLatin1String("application/xhtml+xml") ||
            ct == QLatin1String("application/vnd.wap.xhtml+xml");
}

bool Headers::contentIsXml() const
{
    QString ct = contentType();
    return ct == QLatin1String("text/xml") ||
            ct == QLatin1String("application/xml") ||
            ct.endsWith(QLatin1String("xml"));
}

void Headers::setContentType(const QString &contentType)
{
    insert(QStringLiteral("content_type"), contentType);
}

qint64 Headers::contentLength() const
{
    return value(QStringLiteral("content_length")).toLongLong();
}

void Headers::setContentLength(qint64 value)
{
    insert(QStringLiteral("content_length"), QString::number(value));
}

void Headers::setDateWithDateTime(const QDateTime &date)
{
    // ALL dates must be in GMT timezone http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
    // and follow RFC 822
    QLocale locale(QLocale::C);
    QString dt = locale.toString(date.toTimeSpec(Qt::UTC),
                                 QLatin1String("ddd, dd MMM yyyy hh:mm:ss")) % QLatin1String(" GMT");
    insert(QStringLiteral("date"), dt);
}

QString Headers::ifModifiedSince() const
{
    return header(QStringLiteral("if_modified_since"));
}

QDateTime Headers::ifModifiedSinceDateTime() const
{
    Headers::ConstIterator it = constFind(QStringLiteral("if_modified_since"));
    if (it == constEnd()) {
        return QDateTime();
    }

    const QString &ifModifiedStr = it.value();
    QLocale locale(QLocale::C);

    QDateTime localDT;
    if (ifModifiedStr.endsWith(QLatin1String(" GMT"))) {
        localDT = locale.toDateTime(ifModifiedStr.left(ifModifiedStr.size() - 4),
                                    QStringLiteral("ddd, dd MMM yyyy hh:mm:ss"));
    } else {
        localDT = locale.toDateTime(ifModifiedStr,
                                    QStringLiteral("ddd, dd MMM yyyy hh:mm:ss"));
    }
    return QDateTime(localDT.date(), localDT.time(), Qt::UTC);
}

QString Headers::lastModified() const
{
    return value(QStringLiteral("last_modified"));
}

void Headers::setLastModified(const QString &value)
{
    insert(QStringLiteral("last_modified"), value);
}

void Headers::setLastModified(const QDateTime &lastModified)
{
    // ALL dates must be in GMT timezone http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
    // and follow RFC 822
    QLocale locale(QLocale::C);
    QString dt = locale.toString(lastModified.toTimeSpec(Qt::UTC),
                                 QStringLiteral("ddd, dd MMM yyyy hh:mm:ss")) % QLatin1String(" GMT");
    setLastModified(dt);
}

QString Headers::server() const
{
    return value(QStringLiteral("server"));
}

void Headers::setServer(const QString &value)
{
    insert(QStringLiteral("server"), value);
}

QString Headers::userAgent() const
{
    return value(QStringLiteral("user_agent"));
}

void Headers::setUserAgent(const QString &value)
{
    insert(QStringLiteral("user_agent"), value);
}

QString Headers::referer() const
{
    return value(QStringLiteral("referer"));
}

void Headers::setReferer(const QString &uri)
{
    int fragmentPos = uri.indexOf(QLatin1Char('#'));
    if (fragmentPos != -1) {
        // Strip fragment per RFC 2616, section 14.36.
        insert(QStringLiteral("referer"), uri.mid(0, fragmentPos));
    } else {
        insert(QStringLiteral("referer"), uri);
    }
}

void Headers::setWwwAuthenticate(const QString &value)
{
    insert(QStringLiteral("www_authenticate"), value);
}

void Headers::setProxyAuthenticate(const QString &value)
{
    insert(QStringLiteral("proxy_authenticate"), value);
}

QString Headers::authorization() const
{
    return value(QStringLiteral("authorization"));
}

QString Headers::authorizationBasic() const
{
    return HeadersPrivate::decodeBasicAuth(authorization());
}

QPair<QString, QString> Headers::authorizationBasicPair() const
{
    return HeadersPrivate::decodeBasicAuthPair(authorization());
}

void Headers::setAuthorizationBasic(const QString &username, const QString &password)
{
    if (username.contains(':')) {
        qCWarning(CUTELYST_CORE) << "Headers::Basic authorization user name can't contain ':'";
    }
    QString result = username % QLatin1Char(':') % password;
    insert(QStringLiteral("authorization"), QStringLiteral("Basic ") + result.toLatin1().toBase64());
}

QString Headers::proxyAuthorization() const
{
    return value(QStringLiteral("proxy_authorization"));
}

QString Headers::proxyAuthorizationBasic() const
{
    return HeadersPrivate::decodeBasicAuth(proxyAuthorization());
}

QPair<QString, QString> Headers::proxyAuthorizationBasicPair() const
{
    return HeadersPrivate::decodeBasicAuthPair(proxyAuthorization());
}

QString Headers::header(const QString &field) const
{
    return value(HeadersPrivate::normalizeHeaderKey(field));
}

QString Headers::header(const QString &field, const QString &defaultValue) const
{
    return value(HeadersPrivate::normalizeHeaderKey(field), defaultValue);
}

void Headers::setHeader(const QString &field, const QString &value)
{
    insert(HeadersPrivate::normalizeHeaderKey(field), value);
}

void Headers::setHeader(const QString &field, const QStringList &values)
{
    setHeader(field, values.join(QLatin1String(", ")));
}

void Headers::pushHeader(const QString &field, const QString &value)
{
    const QString &key = HeadersPrivate::normalizeHeaderKey(field);
    const QString &old = Headers::value(key);
    if (old.isEmpty()) {
        insert(key, value);
    } else {
        insert(key, old % QLatin1String(", ") % value);
    }
}

void Headers::pushHeader(const QString &field, const QStringList &values)
{
    pushHeader(field, values.join(QLatin1String(", ")));
}

void Headers::removeHeader(const QString &field)
{
    remove(HeadersPrivate::normalizeHeaderKey(field));
}

static QString cutelyst_header_order(
        // General headers
        "Cache-Control\n"
        "Connection\n"
        "Date\n"
        "Pragma\n"
        "Trailer\n"
        "Transfer-Encoding\n"
        "Upgrade\n"
        "Via\n"
        "Warning\n"
        // Request headers
        "Accept\n"
        "Accept-Charset\n"
        "Accept-Encoding\n"
        "Accept-Language\n"
        "Authorization\n"
        "Expect\n"
        "From\n"
        "Host\n"
        "If-Match\n"
        "If-Modified-Since\n"
        "If-None-Match\n"
        "If-Range\n"
        "If-Unmodified-Since\n"
        "Max-Forwards\n"
        "Proxy-Authorization\n"
        "Range\n"
        "Referer\n"
        "TE\n"
        "User-Agent\n"
        // Response headers
        "Accept-Ranges\n"
        "Age\n"
        "ETag\n"
        "Location\n"
        "Proxy-Authenticate\n"
        "Retry-After\n"
        "Server\n"
        "Vary\n"
        "WWW-Authenticate\n"
        // Entity headers
        "Allow\n"
        "Content-Encoding\n"
        "Content-Language\n"
        "Content-Length\n"
        "Content-Location\n"
        "Content-MD5\n"
        "Content-Range\n"
        "Content-Type\n"
        "Expires\n"
        "Last-Modified"
        );

bool httpGoodPracticeWeightSort(const HeaderValuePair &pair1, const HeaderValuePair &pair2)
{
    int index1 = pair1.weight;
    int index2 = pair2.weight;

    if (index1 != -1 && index2 != -1) {
        // Both items are in the headerOrder list
        return index1 < index2;
    } else if (index1 == -1 && index2 == -1) {
        // Noone of them are int the headerOrder list
        return false;
    }

    // if the pair1 is in the header list it should go first
    return index1 != -1;
}

QList<HeaderValuePair> Headers::headersForResponse() const
{
    QList<HeaderValuePair> ret;

    QHash<QString, QString>::const_iterator it = constBegin();
    while (it != constEnd()) {
        HeaderValuePair pair;
        QString key = it.key();

        // The RFC 2616 and 7230 states keys are not case
        // case sensitive, however several tools fail
        // if the headers are not on camel case form.
        bool lastWasDash = true;
        for (int i = 0 ; i < key.size() ; ++i) {
            QCharRef c = key[i];
            if (c == QLatin1Char('_')) {
                c = QLatin1Char('-');
                lastWasDash = true;
            } else if(lastWasDash) {
                lastWasDash = false;
                c = c.toUpper();
            }
        }

        pair.key = key;
        pair.value = it.value();
        pair.weight = cutelyst_header_order.indexOf(pair.key);

        ret.append(pair);
        ++it;
    }

    // Sort base on the "good practices" of HTTP RCF
    qSort(ret.begin(), ret.end(), &httpGoodPracticeWeightSort);

    return ret;
}

QString HeadersPrivate::normalizeHeaderKey(const QString &field)
{
    QString key = field;
    int i = 0;
    while (i < key.size()) {
        QCharRef c = key[i];
        if (c.isSpace()) {
            key.remove(i, 1);
            continue;
        } else if (c == QLatin1Char('-')) {
            c = QLatin1Char('_');
        } else {
            c = c.toLower();
        }
        ++i;
    }
    return key;
}

QByteArray HeadersPrivate::decodeBasicAuth(const QString &auth)
{
    if (!auth.isEmpty() && auth.startsWith(QLatin1String("Basic "))) {
        int pos = auth.lastIndexOf(' ');
        if (pos != -1) {
            return QByteArray::fromBase64(auth.mid(pos).toLatin1());
        }
    }
    return QByteArray();
}

QPair<QString, QString> HeadersPrivate::decodeBasicAuthPair(const QString &auth)
{
    QPair<QString, QString> ret;
    const QByteArray &authorization = decodeBasicAuth(auth);
    if (!authorization.isEmpty()) {
        int pos = authorization.indexOf(':');
        if (pos == -1) {
            ret.first = QString::fromLatin1(authorization);
        } else {
            ret.first = QString::fromLatin1(authorization.left(pos));
            ret.second = QString::fromLatin1(authorization.mid(pos + 1));
        }
    }
    return ret;
}
<commit_msg>some const & usage<commit_after>/*
 * Copyright (C) 2014 Daniel Nicoletti <dantti12@gmail.com>
 *
 * 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 "headers_p.h"

#include "common.h"

#include <QStringBuilder>
#include <QStringList>

#include <QDebug>

using namespace Cutelyst;

QString Headers::contentEncoding() const
{
    return value(QStringLiteral("content_encoding"));
}

void Headers::setContentEncoding(const QString &encoding)
{
    insert(QStringLiteral("content_encoding"), encoding);
}

QString Headers::contentType() const
{
    const QString &ct = value(QStringLiteral("content_type"));
    return ct.section(QLatin1Char(';'), 0, 0).toLower();
}

QString Headers::contentTypeCharset() const
{
    const QString &ct = value(QStringLiteral("content_type"));
    QVector<QStringRef> parts = ct.splitRef(QLatin1Char(';'));
    Q_FOREACH (const QStringRef &part, parts) {
        int pos = part.indexOf(QLatin1String("charset="));
        if (pos != -1) {
            int endPos = part.indexOf(QLatin1Char(';'), pos);
            return part.mid(pos + 8, endPos).trimmed().toString().toUpper();
        }
    }
    return QString();
}

bool Headers::contentIsText() const
{
    return value(QStringLiteral("content_type")).startsWith(QLatin1String("text/"));
}

bool Headers::contentIsHtml() const
{
    const QString &ct = contentType();
    return ct == QLatin1String("text/html") ||
            ct == QLatin1String("application/xhtml+xml") ||
            ct == QLatin1String("application/vnd.wap.xhtml+xml");
}

bool Headers::contentIsXHtml() const
{
    const QString &ct = contentType();
    return ct == QLatin1String("application/xhtml+xml") ||
            ct == QLatin1String("application/vnd.wap.xhtml+xml");
}

bool Headers::contentIsXml() const
{
    const QString &ct = contentType();
    return ct == QLatin1String("text/xml") ||
            ct == QLatin1String("application/xml") ||
            ct.endsWith(QLatin1String("xml"));
}

void Headers::setContentType(const QString &contentType)
{
    insert(QStringLiteral("content_type"), contentType);
}

qint64 Headers::contentLength() const
{
    return value(QStringLiteral("content_length")).toLongLong();
}

void Headers::setContentLength(qint64 value)
{
    insert(QStringLiteral("content_length"), QString::number(value));
}

void Headers::setDateWithDateTime(const QDateTime &date)
{
    // ALL dates must be in GMT timezone http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
    // and follow RFC 822
    QLocale locale(QLocale::C);
    const QString &dt = locale.toString(date.toTimeSpec(Qt::UTC),
                                        QLatin1String("ddd, dd MMM yyyy hh:mm:ss")) % QLatin1String(" GMT");
    insert(QStringLiteral("date"), dt);
}

QString Headers::ifModifiedSince() const
{
    return header(QStringLiteral("if_modified_since"));
}

QDateTime Headers::ifModifiedSinceDateTime() const
{
    Headers::ConstIterator it = constFind(QStringLiteral("if_modified_since"));
    if (it == constEnd()) {
        return QDateTime();
    }

    const QString &ifModifiedStr = it.value();
    QLocale locale(QLocale::C);

    QDateTime localDT;
    if (ifModifiedStr.endsWith(QLatin1String(" GMT"))) {
        localDT = locale.toDateTime(ifModifiedStr.left(ifModifiedStr.size() - 4),
                                    QStringLiteral("ddd, dd MMM yyyy hh:mm:ss"));
    } else {
        localDT = locale.toDateTime(ifModifiedStr,
                                    QStringLiteral("ddd, dd MMM yyyy hh:mm:ss"));
    }
    return QDateTime(localDT.date(), localDT.time(), Qt::UTC);
}

QString Headers::lastModified() const
{
    return value(QStringLiteral("last_modified"));
}

void Headers::setLastModified(const QString &value)
{
    insert(QStringLiteral("last_modified"), value);
}

void Headers::setLastModified(const QDateTime &lastModified)
{
    // ALL dates must be in GMT timezone http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
    // and follow RFC 822
    QLocale locale(QLocale::C);
    const QString &dt = locale.toString(lastModified.toTimeSpec(Qt::UTC),
                                        QStringLiteral("ddd, dd MMM yyyy hh:mm:ss")) % QLatin1String(" GMT");
    setLastModified(dt);
}

QString Headers::server() const
{
    return value(QStringLiteral("server"));
}

void Headers::setServer(const QString &value)
{
    insert(QStringLiteral("server"), value);
}

QString Headers::userAgent() const
{
    return value(QStringLiteral("user_agent"));
}

void Headers::setUserAgent(const QString &value)
{
    insert(QStringLiteral("user_agent"), value);
}

QString Headers::referer() const
{
    return value(QStringLiteral("referer"));
}

void Headers::setReferer(const QString &uri)
{
    int fragmentPos = uri.indexOf(QLatin1Char('#'));
    if (fragmentPos != -1) {
        // Strip fragment per RFC 2616, section 14.36.
        insert(QStringLiteral("referer"), uri.mid(0, fragmentPos));
    } else {
        insert(QStringLiteral("referer"), uri);
    }
}

void Headers::setWwwAuthenticate(const QString &value)
{
    insert(QStringLiteral("www_authenticate"), value);
}

void Headers::setProxyAuthenticate(const QString &value)
{
    insert(QStringLiteral("proxy_authenticate"), value);
}

QString Headers::authorization() const
{
    return value(QStringLiteral("authorization"));
}

QString Headers::authorizationBasic() const
{
    return HeadersPrivate::decodeBasicAuth(authorization());
}

QPair<QString, QString> Headers::authorizationBasicPair() const
{
    return HeadersPrivate::decodeBasicAuthPair(authorization());
}

void Headers::setAuthorizationBasic(const QString &username, const QString &password)
{
    if (username.contains(':')) {
        qCWarning(CUTELYST_CORE) << "Headers::Basic authorization user name can't contain ':'";
    }
    QString result = username % QLatin1Char(':') % password;
    insert(QStringLiteral("authorization"), QStringLiteral("Basic ") + result.toLatin1().toBase64());
}

QString Headers::proxyAuthorization() const
{
    return value(QStringLiteral("proxy_authorization"));
}

QString Headers::proxyAuthorizationBasic() const
{
    return HeadersPrivate::decodeBasicAuth(proxyAuthorization());
}

QPair<QString, QString> Headers::proxyAuthorizationBasicPair() const
{
    return HeadersPrivate::decodeBasicAuthPair(proxyAuthorization());
}

QString Headers::header(const QString &field) const
{
    return value(HeadersPrivate::normalizeHeaderKey(field));
}

QString Headers::header(const QString &field, const QString &defaultValue) const
{
    return value(HeadersPrivate::normalizeHeaderKey(field), defaultValue);
}

void Headers::setHeader(const QString &field, const QString &value)
{
    insert(HeadersPrivate::normalizeHeaderKey(field), value);
}

void Headers::setHeader(const QString &field, const QStringList &values)
{
    setHeader(field, values.join(QLatin1String(", ")));
}

void Headers::pushHeader(const QString &field, const QString &value)
{
    const QString &key = HeadersPrivate::normalizeHeaderKey(field);
    const QString &old = Headers::value(key);
    if (old.isEmpty()) {
        insert(key, value);
    } else {
        insert(key, old % QLatin1String(", ") % value);
    }
}

void Headers::pushHeader(const QString &field, const QStringList &values)
{
    pushHeader(field, values.join(QLatin1String(", ")));
}

void Headers::removeHeader(const QString &field)
{
    remove(HeadersPrivate::normalizeHeaderKey(field));
}

static QString cutelyst_header_order(
        // General headers
        "Cache-Control\n"
        "Connection\n"
        "Date\n"
        "Pragma\n"
        "Trailer\n"
        "Transfer-Encoding\n"
        "Upgrade\n"
        "Via\n"
        "Warning\n"
        // Request headers
        "Accept\n"
        "Accept-Charset\n"
        "Accept-Encoding\n"
        "Accept-Language\n"
        "Authorization\n"
        "Expect\n"
        "From\n"
        "Host\n"
        "If-Match\n"
        "If-Modified-Since\n"
        "If-None-Match\n"
        "If-Range\n"
        "If-Unmodified-Since\n"
        "Max-Forwards\n"
        "Proxy-Authorization\n"
        "Range\n"
        "Referer\n"
        "TE\n"
        "User-Agent\n"
        // Response headers
        "Accept-Ranges\n"
        "Age\n"
        "ETag\n"
        "Location\n"
        "Proxy-Authenticate\n"
        "Retry-After\n"
        "Server\n"
        "Vary\n"
        "WWW-Authenticate\n"
        // Entity headers
        "Allow\n"
        "Content-Encoding\n"
        "Content-Language\n"
        "Content-Length\n"
        "Content-Location\n"
        "Content-MD5\n"
        "Content-Range\n"
        "Content-Type\n"
        "Expires\n"
        "Last-Modified"
        );

bool httpGoodPracticeWeightSort(const HeaderValuePair &pair1, const HeaderValuePair &pair2)
{
    int index1 = pair1.weight;
    int index2 = pair2.weight;

    if (index1 != -1 && index2 != -1) {
        // Both items are in the headerOrder list
        return index1 < index2;
    } else if (index1 == -1 && index2 == -1) {
        // Noone of them are int the headerOrder list
        return false;
    }

    // if the pair1 is in the header list it should go first
    return index1 != -1;
}

QList<HeaderValuePair> Headers::headersForResponse() const
{
    QList<HeaderValuePair> ret;

    QHash<QString, QString>::const_iterator it = constBegin();
    while (it != constEnd()) {
        HeaderValuePair pair;
        QString key = it.key();

        // The RFC 2616 and 7230 states keys are not case
        // case sensitive, however several tools fail
        // if the headers are not on camel case form.
        bool lastWasDash = true;
        for (int i = 0 ; i < key.size() ; ++i) {
            QCharRef c = key[i];
            if (c == QLatin1Char('_')) {
                c = QLatin1Char('-');
                lastWasDash = true;
            } else if(lastWasDash) {
                lastWasDash = false;
                c = c.toUpper();
            }
        }

        pair.key = key;
        pair.value = it.value();
        pair.weight = cutelyst_header_order.indexOf(pair.key);

        ret.append(pair);
        ++it;
    }

    // Sort base on the "good practices" of HTTP RCF
    qSort(ret.begin(), ret.end(), &httpGoodPracticeWeightSort);

    return ret;
}

QString HeadersPrivate::normalizeHeaderKey(const QString &field)
{
    QString key = field;
    int i = 0;
    while (i < key.size()) {
        QCharRef c = key[i];
        if (c.isSpace()) {
            key.remove(i, 1);
            continue;
        } else if (c == QLatin1Char('-')) {
            c = QLatin1Char('_');
        } else {
            c = c.toLower();
        }
        ++i;
    }
    return key;
}

QByteArray HeadersPrivate::decodeBasicAuth(const QString &auth)
{
    if (!auth.isEmpty() && auth.startsWith(QLatin1String("Basic "))) {
        int pos = auth.lastIndexOf(' ');
        if (pos != -1) {
            return QByteArray::fromBase64(auth.mid(pos).toLatin1());
        }
    }
    return QByteArray();
}

QPair<QString, QString> HeadersPrivate::decodeBasicAuthPair(const QString &auth)
{
    QPair<QString, QString> ret;
    const QByteArray &authorization = decodeBasicAuth(auth);
    if (!authorization.isEmpty()) {
        int pos = authorization.indexOf(':');
        if (pos == -1) {
            ret.first = QString::fromLatin1(authorization);
        } else {
            ret.first = QString::fromLatin1(authorization.left(pos));
            ret.second = QString::fromLatin1(authorization.mid(pos + 1));
        }
    }
    return ret;
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2013-2016 Daniel Nicoletti <dantti12@gmail.com>
 *
 * 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 "request_p.h"
#include "engine.h"
#include "common.h"
#include "multipartformdataparser.h"

#include <QtCore/QJsonDocument>
#include <QtNetwork/QHostInfo>
#include <QtNetwork/QNetworkCookie>

using namespace Cutelyst;

Request::Request(RequestPrivate *prv) :
    d_ptr(prv)
{
}

Request::~Request()
{
    qDeleteAll(d_ptr->uploads);
    delete d_ptr;
}

QHostAddress Request::address() const
{
    Q_D(const Request);
    return d->remoteAddress;
}

QString Request::hostname() const
{
    Q_D(const Request);

    // We have the client hostname
    if (!d->remoteHostname.isEmpty()) {
        return d->remoteHostname;
    } else {
        // We tried to get the client hostname but failed
        if (!d->remoteHostname.isNull()) {
            return QString();
        }
    }

    const QHostInfo ptr = QHostInfo::fromName(d->remoteAddress.toString());
    if (ptr.error() != QHostInfo::NoError) {
        qCDebug(CUTELYST_REQUEST) << "DNS lookup for the client hostname failed" << d->remoteAddress;
        d->remoteHostname = QLatin1String("");
        return QString();
    }

    d->remoteHostname = ptr.hostName();
    return d->remoteHostname;
}

quint16 Request::port() const
{
    Q_D(const Request);
    return d->remotePort;
}

QUrl Request::uri() const
{
    Q_D(const Request);
    if (!d->urlParsed) {
        QUrl uri;

        // This is a hack just in case remote is not set
        if (d->serverAddress.isNull()) {
            uri.setHost(QHostInfo::localHostName());
        } else {
            uri.setAuthority(d->serverAddress);
        }

        uri.setScheme(d->https ? QStringLiteral("https") : QStringLiteral("http"));

        // if the path does not start with a slash it cleans the uri
        uri.setPath(QLatin1Char('/') + d->path);

        if (!d->query.isEmpty()) {
            uri.setQuery(QString::fromLatin1(d->query));
        }

        d->url = uri;
        d->urlParsed = true;
    }
    return d->url;
}

QString Request::base() const
{
    Q_D(const Request);
    if (!d->baseParsed) {
        QString base = d->https ? QStringLiteral("https://") : QStringLiteral("http://");

        // This is a hack just in case remote is not set
        if (d->serverAddress.isNull()) {
            base.append(QHostInfo::localHostName());
        } else {
            base.append(d->serverAddress);
        }

        // base always have a trailing slash
        base.append(QLatin1Char('/'));

        d->base = base;
        d->baseParsed = true;
    }
    return d->base;
}

QString Request::path() const
{
    Q_D(const Request);
    return d->path;
}

QString Request::match() const
{
    Q_D(const Request);
    return d->match;
}

void Request::setMatch(const QString &match)
{
    Q_D(Request);
    d->match = match;
}

QStringList Request::arguments() const
{
    Q_D(const Request);
    return d->args;
}

void Request::setArguments(const QStringList &arguments)
{
    Q_D(Request);
    d->args = arguments;
}

QStringList Request::captures() const
{
    Q_D(const Request);
    return d->captures;
}

void Request::setCaptures(const QStringList &captures)
{
    Q_D(Request);
    d->captures = captures;
}

bool Request::secure() const
{
    Q_D(const Request);
    return d->https;
}

QIODevice *Request::body() const
{
    Q_D(const Request);
    return d->body;
}

QVariant Request::bodyData() const
{
    Q_D(const Request);
    if (!d->bodyParsed) {
        d->parseBody();
    }
    return d->bodyData;
}

QVariantMap Request::bodyParametersVariant() const
{
    return RequestPrivate::paramsMultiMapToVariantMap(bodyParameters());
}

ParamsMultiMap Request::bodyParameters() const
{
    Q_D(const Request);
    if (!d->bodyParsed) {
        d->parseBody();
    }
    return d->bodyParam;
}

QString Request::queryKeywords() const
{
    Q_D(const Request);
    if (!d->queryParamParsed) {
        d->parseUrlQuery();
    }
    return d->queryKeywords;
}

QVariantMap Request::queryParametersVariant() const
{
    return RequestPrivate::paramsMultiMapToVariantMap(queryParameters());
}

ParamsMultiMap Request::queryParameters() const
{
    Q_D(const Request);
    if (!d->queryParamParsed) {
        d->parseUrlQuery();
    }
    return d->queryParam;
}

QVariantMap Request::parametersVariant() const
{
    return RequestPrivate::paramsMultiMapToVariantMap(parameters());
}

ParamsMultiMap Request::parameters() const
{
    Q_D(const Request);
    if (!d->paramParsed) {
        d->param = queryParameters();
        d->param.unite(bodyParameters());
        d->paramParsed = true;
    }
    return d->param;
}

QNetworkCookie Request::cookie(const QString &name) const
{
    Q_D(const Request);
    if (!d->cookiesParsed) {
        d->parseCookies();
    }

    Q_FOREACH (const QNetworkCookie &cookie, d->cookies) {
        if (QString::fromLatin1(cookie.name()) == name) {
            return cookie;
        }
    }
    return QNetworkCookie();
}

QList<QNetworkCookie> Request::cookies() const
{
    Q_D(const Request);
    if (!d->cookiesParsed) {
        d->parseCookies();
    }
    return d->cookies;
}

Headers Request::headers() const
{
    Q_D(const Request);
    return d->headers;
}

QString Request::method() const
{
    Q_D(const Request);
    return d->method;
}

bool Request::isPost() const
{
    Q_D(const Request);
    return d->method == QStringLiteral("POST");
}

bool Request::isGet() const
{
    Q_D(const Request);
    return d->method == QStringLiteral("GET");
}

QString Request::protocol() const
{
    Q_D(const Request);
    return d->protocol;
}

QString Request::remoteUser() const
{
    Q_D(const Request);
    return d->remoteUser;
}

QMap<QString, Cutelyst::Upload *> Request::uploads() const
{
    Q_D(const Request);
    if (!d->bodyParsed) {
        d->parseBody();
    }
    return d->uploads;
}

ParamsMultiMap Request::mangleParams(const ParamsMultiMap &args, bool append) const
{
    ParamsMultiMap ret;
    if (append) {
        ret = args;
        ret.unite(queryParams());
    } else {
        ret = queryParams();
        auto it = args.constBegin();
        while (it != args.constEnd()) {
            ret.insert(it.key(), it.value());
            ++it;
        }
    }

    return ret;
}

QUrl Request::uriWith(const ParamsMultiMap &args, bool append) const
{
    QUrl ret = uri();
    QUrlQuery urlQuery;
    ParamsMultiMap query = mangleParams(args, append);
    auto it = query.constBegin();
    while (it != query.constEnd()) {
        urlQuery.addQueryItem(it.key(), it.value());
        ++it;
    }
    ret.setQuery(urlQuery);

    return ret;
}

Engine *Request::engine() const
{
    Q_D(const Request);
    return d->engine;
}

void *Request::engineData()
{
    Q_D(Request);
    return d->requestPtr;
}

void RequestPrivate::parseUrlQuery() const
{
    // TODO move this to the asignment of query
    if (query.size()) {
        // Check for keywords (no = signs)
        if (query.indexOf('=') < 0) {
            queryKeywords = QUrl::fromPercentEncoding(query);
        } else {
            queryParam = parseUrlEncoded(query);
        }
    }
    queryParamParsed = true;
}

void RequestPrivate::parseBody() const
{
    ParamsMultiMap params;
    QVariant data;

    const QString contentType = headers.contentType();
    if (contentType == QLatin1String("application/x-www-form-urlencoded")) {
        // Parse the query (BODY) of type "application/x-www-form-urlencoded"
        // parameters ie "?foo=bar&bar=baz"
        qint64 posOrig = body->pos();
        body->seek(0);

        params = parseUrlEncoded(body->readLine());
        data = QVariant::fromValue(params);

        body->seek(posOrig);
    } else if (contentType == QLatin1String("multipart/form-data")) {
        Uploads uploadList = MultiPartFormDataParser::parse(body, headers.header(QStringLiteral("content_type")));
        auto it = uploadList.constEnd();
        while (it != uploadList.constBegin()) {
            --it;
            uploads.insertMulti((*it)->name(), *it);
        }
    } else if (contentType == QLatin1String("application/json")) {
        qint64 posOrig = body->pos();
        body->seek(0);

        data = QJsonDocument::fromJson(body->readAll());

        body->seek(posOrig);
    }

    // Asign it here so that we clean it in case no if matched
    bodyParam = params;
    bodyData = data;

    bodyParsed = true;
}

static inline bool isSlit(char c)
{
    return c == ';' || c == ',';
}

int findNextSplit(const QByteArray &text, int from, int length)
{
    while (from < length) {
        if (isSlit(text.at(from))) {
            return from;
        }
        ++from;
    }
    return -1;
}

static inline bool isLWS(char c)
{
    return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}

static int nextNonWhitespace(const QByteArray &text, int from, int length)
{
    // RFC 2616 defines linear whitespace as:
    //  LWS = [CRLF] 1*( SP | HT )
    // We ignore the fact that CRLF must come as a pair at this point
    // It's an invalid HTTP header if that happens.
    while (from < length) {
        if (isLWS(text.at(from)))
            ++from;
        else
            return from;        // non-whitespace
    }

    // reached the end
    return text.length();
}

static QPair<QByteArray, QByteArray> nextField(const QByteArray &text, int &position)
{
    // format is one of:
    //    (1)  token
    //    (2)  token = token
    //    (3)  token = quoted-string
    const int length = text.length();
    position = nextNonWhitespace(text, position, length);

    int semiColonPosition = findNextSplit(text, position, length);
    if (semiColonPosition < 0)
        semiColonPosition = length; //no ';' means take everything to end of string

    int equalsPosition = text.indexOf('=', position);
    if (equalsPosition < 0 || equalsPosition > semiColonPosition) {
        return qMakePair(QByteArray(), QByteArray()); //'=' is required for name-value-pair (RFC6265 section 5.2, rule 2)
    }

    QByteArray first = text.mid(position, equalsPosition - position).trimmed();
    QByteArray second;
    int secondLength = semiColonPosition - equalsPosition - 1;
    if (secondLength > 0)
        second = text.mid(equalsPosition + 1, secondLength).trimmed();

    position = semiColonPosition;
    return qMakePair(first, second);
}

void RequestPrivate::parseCookies() const
{
    QList<QNetworkCookie> ret;
    const QByteArray cookieString = headers.header(QStringLiteral("Cookie")).toLatin1();
    int position = 0;
    const int length = cookieString.length();
    while (position < length) {
        QPair<QByteArray,QByteArray> field = nextField(cookieString, position);
        if (field.first.isEmpty()) {
            // parsing error
            break;
        }

        // Some foreign cookies are not in name=value format, so ignore them.
        if (field.second.isEmpty()) {
            ++position;
            continue;
        }
        ret.append(QNetworkCookie(field.first, field.second));
        ++position;

    }

    cookies = ret;
    cookiesParsed = true;
}

ParamsMultiMap RequestPrivate::parseUrlEncoded(const QByteArray &line)
{
    ParamsMultiMap ret;
    const QList<QByteArray> items = line.split('&');
    for (int i = items.size() - 1; i >= 0; --i) {
        const QByteArray parameter = items.at(i);
        if (parameter.isEmpty()) {
            continue;
        }

        const QList<QByteArray> &parts = parameter.split('=');
        if (parts.size() == 2) {
            QByteArray value = parts.at(1);
            if (value.length()) {
                ret.insertMulti(QUrl::fromPercentEncoding(parts.at(0)),
                                QUrl::fromPercentEncoding(value.replace('+', ' ')));
                continue;
            }
        }
        ret.insertMulti(QUrl::fromPercentEncoding(parts.first()),
                        QString());

    }
    return ret;
}

RequestPrivate::RequestPrivate(Engine *_engine,
                               const QString &_method,
                               const QString &_path,
                               const QByteArray &_query,
                               const QString &_protocol,
                               bool _isSecure,
                               const QString &_serverAddress,
                               const QString &_remoteAddress,
                               quint16 _remotePort,
                               const QString &_remoteUser,
                               const Headers &_headers,
                               quint64 _startOfRequest,
                               QIODevice *_body,
                               void *_requestPtr)
    : engine(_engine)
    , method(_method)
    , path(_path)
    , query(_query)
    , protocol(_protocol)
    , serverAddress(_serverAddress)
    , remoteAddress(_remoteAddress)
    , remoteUser(_remoteUser)
    , headers(_headers)
    , body(_body)
    , startOfRequest(_startOfRequest)
    , requestPtr(_requestPtr)
    , remotePort(_remotePort)
    , https(_isSecure)
{

}

QVariantMap RequestPrivate::paramsMultiMapToVariantMap(const ParamsMultiMap &params)
{
    QVariantMap ret;
    auto begin = params.constBegin();
    auto end = params.constEnd();
    while (begin != end) {
        --end;
        ret.insertMulti(ret.constBegin(), end.key(), end.value());
    }
    return ret;
}

#include "moc_request.cpp"
<commit_msg>Simplify parseBody()<commit_after>/*
 * Copyright (C) 2013-2016 Daniel Nicoletti <dantti12@gmail.com>
 *
 * 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 "request_p.h"
#include "engine.h"
#include "common.h"
#include "multipartformdataparser.h"

#include <QtCore/QJsonDocument>
#include <QtNetwork/QHostInfo>
#include <QtNetwork/QNetworkCookie>

using namespace Cutelyst;

Request::Request(RequestPrivate *prv) :
    d_ptr(prv)
{
}

Request::~Request()
{
    qDeleteAll(d_ptr->uploads);
    delete d_ptr;
}

QHostAddress Request::address() const
{
    Q_D(const Request);
    return d->remoteAddress;
}

QString Request::hostname() const
{
    Q_D(const Request);

    // We have the client hostname
    if (!d->remoteHostname.isEmpty()) {
        return d->remoteHostname;
    } else {
        // We tried to get the client hostname but failed
        if (!d->remoteHostname.isNull()) {
            return QString();
        }
    }

    const QHostInfo ptr = QHostInfo::fromName(d->remoteAddress.toString());
    if (ptr.error() != QHostInfo::NoError) {
        qCDebug(CUTELYST_REQUEST) << "DNS lookup for the client hostname failed" << d->remoteAddress;
        d->remoteHostname = QLatin1String("");
        return QString();
    }

    d->remoteHostname = ptr.hostName();
    return d->remoteHostname;
}

quint16 Request::port() const
{
    Q_D(const Request);
    return d->remotePort;
}

QUrl Request::uri() const
{
    Q_D(const Request);
    if (!d->urlParsed) {
        QUrl uri;

        // This is a hack just in case remote is not set
        if (d->serverAddress.isNull()) {
            uri.setHost(QHostInfo::localHostName());
        } else {
            uri.setAuthority(d->serverAddress);
        }

        uri.setScheme(d->https ? QStringLiteral("https") : QStringLiteral("http"));

        // if the path does not start with a slash it cleans the uri
        uri.setPath(QLatin1Char('/') + d->path);

        if (!d->query.isEmpty()) {
            uri.setQuery(QString::fromLatin1(d->query));
        }

        d->url = uri;
        d->urlParsed = true;
    }
    return d->url;
}

QString Request::base() const
{
    Q_D(const Request);
    if (!d->baseParsed) {
        QString base = d->https ? QStringLiteral("https://") : QStringLiteral("http://");

        // This is a hack just in case remote is not set
        if (d->serverAddress.isNull()) {
            base.append(QHostInfo::localHostName());
        } else {
            base.append(d->serverAddress);
        }

        // base always have a trailing slash
        base.append(QLatin1Char('/'));

        d->base = base;
        d->baseParsed = true;
    }
    return d->base;
}

QString Request::path() const
{
    Q_D(const Request);
    return d->path;
}

QString Request::match() const
{
    Q_D(const Request);
    return d->match;
}

void Request::setMatch(const QString &match)
{
    Q_D(Request);
    d->match = match;
}

QStringList Request::arguments() const
{
    Q_D(const Request);
    return d->args;
}

void Request::setArguments(const QStringList &arguments)
{
    Q_D(Request);
    d->args = arguments;
}

QStringList Request::captures() const
{
    Q_D(const Request);
    return d->captures;
}

void Request::setCaptures(const QStringList &captures)
{
    Q_D(Request);
    d->captures = captures;
}

bool Request::secure() const
{
    Q_D(const Request);
    return d->https;
}

QIODevice *Request::body() const
{
    Q_D(const Request);
    return d->body;
}

QVariant Request::bodyData() const
{
    Q_D(const Request);
    if (!d->bodyParsed) {
        d->parseBody();
    }
    return d->bodyData;
}

QVariantMap Request::bodyParametersVariant() const
{
    return RequestPrivate::paramsMultiMapToVariantMap(bodyParameters());
}

ParamsMultiMap Request::bodyParameters() const
{
    Q_D(const Request);
    if (!d->bodyParsed) {
        d->parseBody();
    }
    return d->bodyParam;
}

QString Request::queryKeywords() const
{
    Q_D(const Request);
    if (!d->queryParamParsed) {
        d->parseUrlQuery();
    }
    return d->queryKeywords;
}

QVariantMap Request::queryParametersVariant() const
{
    return RequestPrivate::paramsMultiMapToVariantMap(queryParameters());
}

ParamsMultiMap Request::queryParameters() const
{
    Q_D(const Request);
    if (!d->queryParamParsed) {
        d->parseUrlQuery();
    }
    return d->queryParam;
}

QVariantMap Request::parametersVariant() const
{
    return RequestPrivate::paramsMultiMapToVariantMap(parameters());
}

ParamsMultiMap Request::parameters() const
{
    Q_D(const Request);
    if (!d->paramParsed) {
        d->param = queryParameters();
        d->param.unite(bodyParameters());
        d->paramParsed = true;
    }
    return d->param;
}

QNetworkCookie Request::cookie(const QString &name) const
{
    Q_D(const Request);
    if (!d->cookiesParsed) {
        d->parseCookies();
    }

    Q_FOREACH (const QNetworkCookie &cookie, d->cookies) {
        if (QString::fromLatin1(cookie.name()) == name) {
            return cookie;
        }
    }
    return QNetworkCookie();
}

QList<QNetworkCookie> Request::cookies() const
{
    Q_D(const Request);
    if (!d->cookiesParsed) {
        d->parseCookies();
    }
    return d->cookies;
}

Headers Request::headers() const
{
    Q_D(const Request);
    return d->headers;
}

QString Request::method() const
{
    Q_D(const Request);
    return d->method;
}

bool Request::isPost() const
{
    Q_D(const Request);
    return d->method == QStringLiteral("POST");
}

bool Request::isGet() const
{
    Q_D(const Request);
    return d->method == QStringLiteral("GET");
}

QString Request::protocol() const
{
    Q_D(const Request);
    return d->protocol;
}

QString Request::remoteUser() const
{
    Q_D(const Request);
    return d->remoteUser;
}

QMap<QString, Cutelyst::Upload *> Request::uploads() const
{
    Q_D(const Request);
    if (!d->bodyParsed) {
        d->parseBody();
    }
    return d->uploads;
}

ParamsMultiMap Request::mangleParams(const ParamsMultiMap &args, bool append) const
{
    ParamsMultiMap ret;
    if (append) {
        ret = args;
        ret.unite(queryParams());
    } else {
        ret = queryParams();
        auto it = args.constBegin();
        while (it != args.constEnd()) {
            ret.insert(it.key(), it.value());
            ++it;
        }
    }

    return ret;
}

QUrl Request::uriWith(const ParamsMultiMap &args, bool append) const
{
    QUrl ret = uri();
    QUrlQuery urlQuery;
    ParamsMultiMap query = mangleParams(args, append);
    auto it = query.constBegin();
    while (it != query.constEnd()) {
        urlQuery.addQueryItem(it.key(), it.value());
        ++it;
    }
    ret.setQuery(urlQuery);

    return ret;
}

Engine *Request::engine() const
{
    Q_D(const Request);
    return d->engine;
}

void *Request::engineData()
{
    Q_D(Request);
    return d->requestPtr;
}

void RequestPrivate::parseUrlQuery() const
{
    // TODO move this to the asignment of query
    if (query.size()) {
        // Check for keywords (no = signs)
        if (query.indexOf('=') < 0) {
            queryKeywords = QUrl::fromPercentEncoding(query);
        } else {
            queryParam = parseUrlEncoded(query);
        }
    }
    queryParamParsed = true;
}

void RequestPrivate::parseBody() const
{
    const QString contentType = headers.contentType();
    if (contentType == QLatin1String("application/x-www-form-urlencoded")) {
        // Parse the query (BODY) of type "application/x-www-form-urlencoded"
        // parameters ie "?foo=bar&bar=baz"
        qint64 posOrig = body->pos();
        body->seek(0);

        bodyParam = parseUrlEncoded(body->readLine());
        bodyData = QVariant::fromValue(bodyParam);

        body->seek(posOrig);
    } else if (contentType == QLatin1String("multipart/form-data")) {
        Uploads uploadList = MultiPartFormDataParser::parse(body, headers.header(QStringLiteral("content_type")));
        auto it = uploadList.constEnd();
        while (it != uploadList.constBegin()) {
            --it;
            uploads.insertMulti((*it)->name(), *it);
        }
        bodyData = QVariant::fromValue(uploads);
    } else if (contentType == QLatin1String("application/json")) {
        qint64 posOrig = body->pos();
        body->seek(0);

        bodyData = QJsonDocument::fromJson(body->readAll());

        body->seek(posOrig);
    }

    bodyParsed = true;
}

static inline bool isSlit(char c)
{
    return c == ';' || c == ',';
}

int findNextSplit(const QByteArray &text, int from, int length)
{
    while (from < length) {
        if (isSlit(text.at(from))) {
            return from;
        }
        ++from;
    }
    return -1;
}

static inline bool isLWS(char c)
{
    return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}

static int nextNonWhitespace(const QByteArray &text, int from, int length)
{
    // RFC 2616 defines linear whitespace as:
    //  LWS = [CRLF] 1*( SP | HT )
    // We ignore the fact that CRLF must come as a pair at this point
    // It's an invalid HTTP header if that happens.
    while (from < length) {
        if (isLWS(text.at(from)))
            ++from;
        else
            return from;        // non-whitespace
    }

    // reached the end
    return text.length();
}

static QPair<QByteArray, QByteArray> nextField(const QByteArray &text, int &position)
{
    // format is one of:
    //    (1)  token
    //    (2)  token = token
    //    (3)  token = quoted-string
    const int length = text.length();
    position = nextNonWhitespace(text, position, length);

    int semiColonPosition = findNextSplit(text, position, length);
    if (semiColonPosition < 0)
        semiColonPosition = length; //no ';' means take everything to end of string

    int equalsPosition = text.indexOf('=', position);
    if (equalsPosition < 0 || equalsPosition > semiColonPosition) {
        return qMakePair(QByteArray(), QByteArray()); //'=' is required for name-value-pair (RFC6265 section 5.2, rule 2)
    }

    QByteArray first = text.mid(position, equalsPosition - position).trimmed();
    QByteArray second;
    int secondLength = semiColonPosition - equalsPosition - 1;
    if (secondLength > 0)
        second = text.mid(equalsPosition + 1, secondLength).trimmed();

    position = semiColonPosition;
    return qMakePair(first, second);
}

void RequestPrivate::parseCookies() const
{
    QList<QNetworkCookie> ret;
    const QByteArray cookieString = headers.header(QStringLiteral("Cookie")).toLatin1();
    int position = 0;
    const int length = cookieString.length();
    while (position < length) {
        QPair<QByteArray,QByteArray> field = nextField(cookieString, position);
        if (field.first.isEmpty()) {
            // parsing error
            break;
        }

        // Some foreign cookies are not in name=value format, so ignore them.
        if (field.second.isEmpty()) {
            ++position;
            continue;
        }
        ret.append(QNetworkCookie(field.first, field.second));
        ++position;

    }

    cookies = ret;
    cookiesParsed = true;
}

ParamsMultiMap RequestPrivate::parseUrlEncoded(const QByteArray &line)
{
    ParamsMultiMap ret;
    const QList<QByteArray> items = line.split('&');
    for (int i = items.size() - 1; i >= 0; --i) {
        const QByteArray parameter = items.at(i);
        if (parameter.isEmpty()) {
            continue;
        }

        const QList<QByteArray> &parts = parameter.split('=');
        if (parts.size() == 2) {
            QByteArray value = parts.at(1);
            if (value.length()) {
                ret.insertMulti(QUrl::fromPercentEncoding(parts.at(0)),
                                QUrl::fromPercentEncoding(value.replace('+', ' ')));
                continue;
            }
        }
        ret.insertMulti(QUrl::fromPercentEncoding(parts.first()),
                        QString());

    }
    return ret;
}

RequestPrivate::RequestPrivate(Engine *_engine,
                               const QString &_method,
                               const QString &_path,
                               const QByteArray &_query,
                               const QString &_protocol,
                               bool _isSecure,
                               const QString &_serverAddress,
                               const QString &_remoteAddress,
                               quint16 _remotePort,
                               const QString &_remoteUser,
                               const Headers &_headers,
                               quint64 _startOfRequest,
                               QIODevice *_body,
                               void *_requestPtr)
    : engine(_engine)
    , method(_method)
    , path(_path)
    , query(_query)
    , protocol(_protocol)
    , serverAddress(_serverAddress)
    , remoteAddress(_remoteAddress)
    , remoteUser(_remoteUser)
    , headers(_headers)
    , body(_body)
    , startOfRequest(_startOfRequest)
    , requestPtr(_requestPtr)
    , remotePort(_remotePort)
    , https(_isSecure)
{

}

QVariantMap RequestPrivate::paramsMultiMapToVariantMap(const ParamsMultiMap &params)
{
    QVariantMap ret;
    auto begin = params.constBegin();
    auto end = params.constEnd();
    while (begin != end) {
        --end;
        ret.insertMulti(ret.constBegin(), end.key(), end.value());
    }
    return ret;
}

#include "moc_request.cpp"
<|endoftext|>


<commit_before>#include "DBAdapter.h"

#include "common/RhoFile.h"
#include "common/RhoFilePath.h"

extern "C" const char* RhoGetRootPath();
extern "C" const char* RhoGetRelativeBlobsPath();

namespace rho{
namespace db{
IMPLEMENT_LOGCLASS(CDBAdapter,"DB");

using namespace rho::common;

static int onDBBusy(void* data,int nTry)
{
    LOGC(ERROR,CDBAdapter::getLogCategory())+"Database BUSY";
    return 0;
}

void SyncBlob_DeleteCallback(sqlite3_context* dbContext, int nArgs, sqlite3_value** ppArgs)
{
    char* type = NULL;
    if ( nArgs < 2 )
        return;

    type = (char*)sqlite3_value_text(*(ppArgs+1));
    if ( type && strcmp(type,"blob.file") == 0 )
    {
        String strFilePath = RhoGetRootPath();
        strFilePath += "apps";
        strFilePath += (char*)sqlite3_value_text(*(ppArgs));
        CRhoFile::deleteFile(strFilePath.c_str());
    }
}

/*static*/ String CDBAdapter::makeBlobFolderName()
{
    String strBlobPath = RhoGetRootPath();
    return strBlobPath + RhoGetRelativeBlobsPath();
}

boolean CDBAdapter::checkDbError(int rc)
{
    if ( rc == SQLITE_OK || rc == SQLITE_ROW || rc == SQLITE_DONE )
        return true;

    const char * szErrMsg = sqlite3_errmsg(m_dbHandle);
    int nErrCode = sqlite3_errcode(m_dbHandle);

    LOG(ERROR)+"DB query failed. Error code: " + nErrCode + ";Message: " + szErrMsg;

    return false;
}

void CDBAdapter::open (String strDbPath, String strVer)
{
    if ( strcasecmp(strDbPath.c_str(),m_strDbPath.c_str() ) == 0 )
        return;
    close();

    m_strDbPath = strDbPath;
    m_strDbVer = strVer;

    checkVersion(strVer);

    boolean bExist = CRhoFile::isFileExist(strDbPath.c_str());
    int nRes = sqlite3_open(strDbPath.c_str(),&m_dbHandle);
    if ( !checkDbError(nRes) )
        return;
    //TODO: raise exception if error
    if ( !bExist )
        createSchema();

    sqlite3_create_function( m_dbHandle, "rhoOnDeleteObjectRecord", 3, SQLITE_ANY, 0,
	    SyncBlob_DeleteCallback, 0, 0 );
    sqlite3_busy_handler(m_dbHandle, onDBBusy, 0 );
}

sqlite3_stmt* CDBAdapter::createInsertStatement(rho::db::CDBResult& res, const String& tableName, CDBAdapter& db, String& strInsert)
{
    sqlite3_stmt* stInsert = 0;
    int nColCount = sqlite3_data_count(res.getStatement());

  	if ( strInsert.length() == 0 )
    {
	    strInsert = "INSERT INTO ";

	    strInsert += tableName;
	    strInsert += "(";
	    String strQuest = ") VALUES(";
        String strValues = "";
	    for (int nCol = 0; nCol < nColCount; nCol++ )
	    {
            String strColName = sqlite3_column_name(res.getStatement(),nCol);
            if ( strColName == "id")
                continue;

		    if ( strValues.length() > 0 )
		    {
			    strValues += ",";
			    strQuest += ",";
		    }

		    strValues += strColName;
		    strQuest += "?";
	    }

	    strInsert += strValues + strQuest + ")";
    }

    int rc = sqlite3_prepare_v2(db.getDbHandle(), strInsert.c_str(), -1, &stInsert, NULL);
    if ( !checkDbError(rc) )
    	return 0;

    int nBindCol = 1;
	for (int nCol = 0; nCol < nColCount; nCol++ )
	{
		int nColType = sqlite3_column_type(res.getStatement(),nCol);
        String strColName = sqlite3_column_name(res.getStatement(),nCol);
        if ( strColName == "id")
            continue;

		switch(nColType){
			case SQLITE_NULL:
                rc = sqlite3_bind_text(stInsert, nBindCol, null, -1, SQLITE_TRANSIENT);
                break;
            case SQLITE_INTEGER:
            {
                sqlite_int64 nValue = sqlite3_column_int64(res.getStatement(), nCol);
                rc = sqlite3_bind_int64(stInsert, nBindCol, nValue);
                break;
            }
			default:{
                char* szValue = (char *)sqlite3_column_text(res.getStatement(), nCol);
                rc = sqlite3_bind_text(stInsert, nBindCol, szValue, -1, SQLITE_TRANSIENT);
				break;
			}
		}
        nBindCol++;
    }

	return stInsert;
}

void CDBAdapter::destroy_table(String strTable)
{
    CFilePath oFilePath(m_strDbPath);
	String dbNewName  = oFilePath.changeBaseName("resetdbtemp.sqlite");

    CRhoFile::deleteFile(dbNewName.c_str());
    CRhoFile::deleteFile((dbNewName+"-journal").c_str());

    CDBAdapter db;
    db.open( dbNewName, m_strDbVer );

    //Copy all tables

    Vector<String> vecTables;
    DBResult( res , executeSQL( "SELECT name FROM sqlite_master WHERE type='table' " ) );
    for ( ; !res.isEnd(); res.next() )
        vecTables.addElement(res.getStringByIdx(0));

    db.startTransaction();
    for ( int i = 0; i < (int)vecTables.size(); i++ )
    {
        String tableName = vecTables.elementAt(i);
        if ( tableName.compare(strTable)==0 )
            continue;

        String strSelect = "SELECT * from " + tableName;
        DBResult( res , executeSQL( strSelect.c_str() ) );
		String strInsert = "";
        int rc = 0;
	    for ( ; !res.isEnd(); res.next() )
	    {
	    	sqlite3_stmt* stInsert = createInsertStatement(res, tableName, db, strInsert);

            if (stInsert)
            {
                rc = sqlite3_step(stInsert);
                checkDbError(rc);
                sqlite3_finalize(stInsert);
            }
	    }
    }

    db.endTransaction();
    db.close();

    String dbOldName = m_strDbPath;
    close();
    CRhoFile::deleteFile(dbOldName.c_str());
    CRhoFile::renameFile(dbNewName.c_str(),dbOldName.c_str());
    open( dbOldName, m_strDbVer );
}

void CDBAdapter::checkVersion(String& strVer)
{
    //TODO: checkVersion
}

static const char* g_szDbSchema =
    "CREATE TABLE client_info ("
    " client_id VARCHAR(255) PRIMARY KEY,"
    " token VARCHAR(255) default NULL,"
    " token_sent int default 0,"
    " reset int default 0,"
    " port VARCHAR(10) default NULL,"
    " last_sync_success VARCHAR(100) default NULL);"
    "CREATE TABLE object_values ("
    " id INTEGER PRIMARY KEY,"
    " token INTEGER default NULL,"
    " source_id int default NULL,"
    " attrib varchar(255) default NULL,"
    " object varchar(255) default NULL,"
    " value text default NULL,"
    " update_type varchar(255) default NULL,"
    " attrib_type varchar(255) default NULL);"
    "CREATE TABLE sources ("
    " id INTEGER PRIMARY KEY,"
    " token INTEGER default NULL,"
    " source_id int default -1,"
    " source_url VARCHAR(255) default NULL,"
    " name VARCHAR(255) default NULL,"
    " session VARCHAR(255) default NULL,"
    " last_updated int default 0,"
    " last_inserted_size int default 0,"
    " last_deleted_size int default 0,"
    " last_sync_duration int default 0,"
    " last_sync_success int default 0,"
    " source_attribs varchar default NULL);"
    "CREATE INDEX by_attrib_utype on object_values (attrib,update_type);"
    "CREATE INDEX by_src_type ON object_values (source_id, attrib_type, object);"
    "CREATE INDEX by_src_utype on object_values (source_id,update_type);"
    "CREATE INDEX by_type ON object_values (attrib_type);"
    "CREATE TRIGGER rhodeleteTrigger BEFORE DELETE ON object_values FOR EACH ROW "
        "BEGIN "
            "SELECT rhoOnDeleteObjectRecord(OLD.value,OLD.attrib_type,OLD.update_type);"
        "END;"
    ";";

void CDBAdapter::createSchema()
{
    char* errmsg = 0;
    int rc = sqlite3_exec(m_dbHandle, g_szDbSchema,  NULL, NULL, &errmsg);

    if ( rc != SQLITE_OK )
        LOG(ERROR)+"createSchema failed. Error code: " + rc + ";Message: " + (errmsg ? errmsg : "");

    if ( errmsg )
        sqlite3_free(errmsg);
}

void CDBAdapter::close()
{
    for (Hashtable<String,sqlite3_stmt*>::iterator it = m_mapStatements.begin();  it != m_mapStatements.end(); ++it )
        sqlite3_finalize( it->second );

    m_mapStatements.clear();

    if ( m_dbHandle != 0 )
        sqlite3_close(m_dbHandle);

    m_dbHandle = 0;
    m_strDbPath = String();
}

DBResultPtr CDBAdapter::prepareStatement( const char* szSt )
{
    if ( m_dbHandle == null )
        return new CDBResult(m_mxDB);

	DBResultPtr res = new CDBResult(0,m_bInsideTransaction ? m_mxTransDB : m_mxDB);
    sqlite3_stmt* st = m_mapStatements.get(szSt);
    if ( st != null )
	{
		res->setStatement(st);
        return res;
	}

    int rc = sqlite3_prepare_v2(m_dbHandle, szSt, -1, &st, NULL);
    if ( !checkDbError(rc) )
    {
        //TODO: raise exception
        return res;
    }

    res->setStatement(st);
    m_mapStatements.put(szSt, st);

    return res;
}

DBResultPtr CDBAdapter::executeSQL( const char* szSt)
{
    DBResultPtr res = prepareStatement(szSt);
    if ( res->getStatement() == null )
        return res;

    return executeStatement(res);
}

DBResultPtr CDBAdapter::executeStatement(DBResultPtr& res)
{
    int rc = sqlite3_step(res->getStatement());
    if ( rc != SQLITE_ROW )
    {
        res->setStatement(null);
        if ( rc != SQLITE_OK && rc != SQLITE_ROW && rc != SQLITE_DONE )
        {
            checkDbError(rc);
            //TODO: raise exception
            return res;
        }
    }

    return res;
}

void CDBAdapter::startTransaction()
{
    Lock();
	m_bInsideTransaction=true;
    char *zErr = 0;
    int rc = 0;
	if ( m_dbHandle )
    {
		rc = sqlite3_exec(m_dbHandle, "BEGIN IMMEDIATE;",0,0,&zErr);
        checkDbError(rc);
    }
}

void CDBAdapter::endTransaction()
{
    char *zErr = 0;
    int rc = 0;
	if (m_dbHandle)
    {
		rc = sqlite3_exec(m_dbHandle, "END;",0,0,&zErr);
        checkDbError(rc);
    }

	m_bInsideTransaction=false;
    Unlock();
}

void CDBAdapter::rollback()
{
    char *zErr = 0;
    int rc = 0;
	if (m_dbHandle)
    {
		rc = sqlite3_exec(m_dbHandle, "ROLLBACK;",0,0,&zErr);
        checkDbError(rc);
    }

	m_bInsideTransaction=false;
    Unlock();
}

}
}
<commit_msg>* adding unique index to detect collisions<commit_after>#include "DBAdapter.h"

#include "common/RhoFile.h"
#include "common/RhoFilePath.h"

extern "C" const char* RhoGetRootPath();
extern "C" const char* RhoGetRelativeBlobsPath();

namespace rho{
namespace db{
IMPLEMENT_LOGCLASS(CDBAdapter,"DB");

using namespace rho::common;

static int onDBBusy(void* data,int nTry)
{
    LOGC(ERROR,CDBAdapter::getLogCategory())+"Database BUSY";
    return 0;
}

void SyncBlob_DeleteCallback(sqlite3_context* dbContext, int nArgs, sqlite3_value** ppArgs)
{
    char* type = NULL;
    if ( nArgs < 2 )
        return;

    type = (char*)sqlite3_value_text(*(ppArgs+1));
    if ( type && strcmp(type,"blob.file") == 0 )
    {
        String strFilePath = RhoGetRootPath();
        strFilePath += "apps";
        strFilePath += (char*)sqlite3_value_text(*(ppArgs));
        CRhoFile::deleteFile(strFilePath.c_str());
    }
}

/*static*/ String CDBAdapter::makeBlobFolderName()
{
    String strBlobPath = RhoGetRootPath();
    return strBlobPath + RhoGetRelativeBlobsPath();
}

boolean CDBAdapter::checkDbError(int rc)
{
    if ( rc == SQLITE_OK || rc == SQLITE_ROW || rc == SQLITE_DONE )
        return true;

    const char * szErrMsg = sqlite3_errmsg(m_dbHandle);
    int nErrCode = sqlite3_errcode(m_dbHandle);

    LOG(ERROR)+"DB query failed. Error code: " + nErrCode + ";Message: " + szErrMsg;

    return false;
}

void CDBAdapter::open (String strDbPath, String strVer)
{
    if ( strcasecmp(strDbPath.c_str(),m_strDbPath.c_str() ) == 0 )
        return;
    close();

    m_strDbPath = strDbPath;
    m_strDbVer = strVer;

    checkVersion(strVer);

    boolean bExist = CRhoFile::isFileExist(strDbPath.c_str());
    int nRes = sqlite3_open(strDbPath.c_str(),&m_dbHandle);
    if ( !checkDbError(nRes) )
        return;
    //TODO: raise exception if error
    if ( !bExist )
        createSchema();

    sqlite3_create_function( m_dbHandle, "rhoOnDeleteObjectRecord", 3, SQLITE_ANY, 0,
	    SyncBlob_DeleteCallback, 0, 0 );
    sqlite3_busy_handler(m_dbHandle, onDBBusy, 0 );
}

sqlite3_stmt* CDBAdapter::createInsertStatement(rho::db::CDBResult& res, const String& tableName, CDBAdapter& db, String& strInsert)
{
    sqlite3_stmt* stInsert = 0;
    int nColCount = sqlite3_data_count(res.getStatement());

  	if ( strInsert.length() == 0 )
    {
	    strInsert = "INSERT INTO ";

	    strInsert += tableName;
	    strInsert += "(";
	    String strQuest = ") VALUES(";
        String strValues = "";
	    for (int nCol = 0; nCol < nColCount; nCol++ )
	    {
            String strColName = sqlite3_column_name(res.getStatement(),nCol);
            if ( strColName == "id")
                continue;

		    if ( strValues.length() > 0 )
		    {
			    strValues += ",";
			    strQuest += ",";
		    }

		    strValues += strColName;
		    strQuest += "?";
	    }

	    strInsert += strValues + strQuest + ")";
    }

    int rc = sqlite3_prepare_v2(db.getDbHandle(), strInsert.c_str(), -1, &stInsert, NULL);
    if ( !checkDbError(rc) )
    	return 0;

    int nBindCol = 1;
	for (int nCol = 0; nCol < nColCount; nCol++ )
	{
		int nColType = sqlite3_column_type(res.getStatement(),nCol);
        String strColName = sqlite3_column_name(res.getStatement(),nCol);
        if ( strColName == "id")
            continue;

		switch(nColType){
			case SQLITE_NULL:
                rc = sqlite3_bind_text(stInsert, nBindCol, null, -1, SQLITE_TRANSIENT);
                break;
            case SQLITE_INTEGER:
            {
                sqlite_int64 nValue = sqlite3_column_int64(res.getStatement(), nCol);
                rc = sqlite3_bind_int64(stInsert, nBindCol, nValue);
                break;
            }
			default:{
                char* szValue = (char *)sqlite3_column_text(res.getStatement(), nCol);
                rc = sqlite3_bind_text(stInsert, nBindCol, szValue, -1, SQLITE_TRANSIENT);
				break;
			}
		}
        nBindCol++;
    }

	return stInsert;
}

void CDBAdapter::destroy_table(String strTable)
{
    CFilePath oFilePath(m_strDbPath);
	String dbNewName  = oFilePath.changeBaseName("resetdbtemp.sqlite");

    CRhoFile::deleteFile(dbNewName.c_str());
    CRhoFile::deleteFile((dbNewName+"-journal").c_str());

    CDBAdapter db;
    db.open( dbNewName, m_strDbVer );

    //Copy all tables

    Vector<String> vecTables;
    DBResult( res , executeSQL( "SELECT name FROM sqlite_master WHERE type='table' " ) );
    for ( ; !res.isEnd(); res.next() )
        vecTables.addElement(res.getStringByIdx(0));

    db.startTransaction();
    for ( int i = 0; i < (int)vecTables.size(); i++ )
    {
        String tableName = vecTables.elementAt(i);
        if ( tableName.compare(strTable)==0 )
            continue;

        String strSelect = "SELECT * from " + tableName;
        DBResult( res , executeSQL( strSelect.c_str() ) );
		String strInsert = "";
        int rc = 0;
	    for ( ; !res.isEnd(); res.next() )
	    {
	    	sqlite3_stmt* stInsert = createInsertStatement(res, tableName, db, strInsert);

            if (stInsert)
            {
                rc = sqlite3_step(stInsert);
                checkDbError(rc);
                sqlite3_finalize(stInsert);
            }
	    }
    }

    db.endTransaction();
    db.close();

    String dbOldName = m_strDbPath;
    close();
    CRhoFile::deleteFile(dbOldName.c_str());
    CRhoFile::renameFile(dbNewName.c_str(),dbOldName.c_str());
    open( dbOldName, m_strDbVer );
}

void CDBAdapter::checkVersion(String& strVer)
{
    //TODO: checkVersion
}

static const char* g_szDbSchema =
    "CREATE TABLE client_info ("
    " client_id VARCHAR(255) PRIMARY KEY,"
    " token VARCHAR(255) default NULL,"
    " token_sent int default 0,"
    " reset int default 0,"
    " port VARCHAR(10) default NULL,"
    " last_sync_success VARCHAR(100) default NULL);"
    "CREATE TABLE object_values ("
    " id INTEGER PRIMARY KEY,"
    " token INTEGER default NULL,"
    " source_id int default NULL,"
    " attrib varchar(255) default NULL,"
    " object varchar(255) default NULL,"
    " value text default NULL,"
    " update_type varchar(255) default NULL,"
    " attrib_type varchar(255) default NULL);"
    "CREATE TABLE sources ("
    " id INTEGER PRIMARY KEY,"
    " token INTEGER default NULL,"
    " source_id int default -1,"
    " source_url VARCHAR(255) default NULL,"
    " name VARCHAR(255) default NULL,"
    " session VARCHAR(255) default NULL,"
    " last_updated int default 0,"
    " last_inserted_size int default 0,"
    " last_deleted_size int default 0,"
    " last_sync_duration int default 0,"
    " last_sync_success int default 0,"
    " source_attribs varchar default NULL);"
    "CREATE INDEX by_attrib_utype on object_values (attrib,update_type);"
    "CREATE INDEX by_src_type ON object_values (source_id, attrib_type, object);"
    "CREATE INDEX by_src_utype on object_values (source_id,update_type);"
    "CREATE INDEX by_type ON object_values (attrib_type);"
	"CREATE UNIQUE INDEX by_src_object ON object_values (object, attrib, source_id, update_type);"
    "CREATE TRIGGER rhodeleteTrigger BEFORE DELETE ON object_values FOR EACH ROW "
        "BEGIN "
            "SELECT rhoOnDeleteObjectRecord(OLD.value,OLD.attrib_type,OLD.update_type);"
        "END;"
    ";";

void CDBAdapter::createSchema()
{
    char* errmsg = 0;
    int rc = sqlite3_exec(m_dbHandle, g_szDbSchema,  NULL, NULL, &errmsg);

    if ( rc != SQLITE_OK )
        LOG(ERROR)+"createSchema failed. Error code: " + rc + ";Message: " + (errmsg ? errmsg : "");

    if ( errmsg )
        sqlite3_free(errmsg);
}

void CDBAdapter::close()
{
    for (Hashtable<String,sqlite3_stmt*>::iterator it = m_mapStatements.begin();  it != m_mapStatements.end(); ++it )
        sqlite3_finalize( it->second );

    m_mapStatements.clear();

    if ( m_dbHandle != 0 )
        sqlite3_close(m_dbHandle);

    m_dbHandle = 0;
    m_strDbPath = String();
}

DBResultPtr CDBAdapter::prepareStatement( const char* szSt )
{
    if ( m_dbHandle == null )
        return new CDBResult(m_mxDB);

	DBResultPtr res = new CDBResult(0,m_bInsideTransaction ? m_mxTransDB : m_mxDB);
    sqlite3_stmt* st = m_mapStatements.get(szSt);
    if ( st != null )
	{
		res->setStatement(st);
        return res;
	}

    int rc = sqlite3_prepare_v2(m_dbHandle, szSt, -1, &st, NULL);
    if ( !checkDbError(rc) )
    {
        //TODO: raise exception
        return res;
    }

    res->setStatement(st);
    m_mapStatements.put(szSt, st);

    return res;
}

DBResultPtr CDBAdapter::executeSQL( const char* szSt)
{
    DBResultPtr res = prepareStatement(szSt);
    if ( res->getStatement() == null )
        return res;

    return executeStatement(res);
}

DBResultPtr CDBAdapter::executeStatement(DBResultPtr& res)
{
    int rc = sqlite3_step(res->getStatement());
    if ( rc != SQLITE_ROW )
    {
        res->setStatement(null);
        if ( rc != SQLITE_OK && rc != SQLITE_ROW && rc != SQLITE_DONE )
        {
            checkDbError(rc);
            //TODO: raise exception
            return res;
        }
    }

    return res;
}

void CDBAdapter::startTransaction()
{
    Lock();
	m_bInsideTransaction=true;
    char *zErr = 0;
    int rc = 0;
	if ( m_dbHandle )
    {
		rc = sqlite3_exec(m_dbHandle, "BEGIN IMMEDIATE;",0,0,&zErr);
        checkDbError(rc);
    }
}

void CDBAdapter::endTransaction()
{
    char *zErr = 0;
    int rc = 0;
	if (m_dbHandle)
    {
		rc = sqlite3_exec(m_dbHandle, "END;",0,0,&zErr);
        checkDbError(rc);
    }

	m_bInsideTransaction=false;
    Unlock();
}

void CDBAdapter::rollback()
{
    char *zErr = 0;
    int rc = 0;
	if (m_dbHandle)
    {
		rc = sqlite3_exec(m_dbHandle, "ROLLBACK;",0,0,&zErr);
        checkDbError(rc);
    }

	m_bInsideTransaction=false;
    Unlock();
}

}
}
<|endoftext|>


<commit_before>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/command_line.h"
#include "base/message_loop.h"
#include "base/scoped_ptr.h"
#include "chrome/browser/automation/ui_controls.h"
#include "chrome/browser/browser_thread.h"
#include "chrome/browser/browser_window.h"
#include "chrome/browser/chromeos/cros/cros_in_process_browser_test.h"
#include "chrome/browser/chromeos/cros/mock_input_method_library.h"
#include "chrome/browser/chromeos/cros/mock_screen_lock_library.h"
#include "chrome/browser/chromeos/login/mock_authenticator.h"
#include "chrome/browser/chromeos/login/screen_locker.h"
#include "chrome/browser/chromeos/login/screen_locker_tester.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/views/browser_dialogs.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/notification_type.h"
#include "chrome/test/ui_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "views/controls/textfield/textfield.h"
#include "views/window/window_gtk.h"

namespace {

// An object that wait for lock state and fullscreen state.
class Waiter : public NotificationObserver {
 public:
  explicit Waiter(Browser* browser)
      : browser_(browser) {
    registrar_.Add(this,
                   NotificationType::SCREEN_LOCK_STATE_CHANGED,
                   NotificationService::AllSources());
    handler_id_ = g_signal_connect(
        G_OBJECT(browser_->window()->GetNativeHandle()),
        "window-state-event",
        G_CALLBACK(OnWindowStateEventThunk),
        this);
  }

  ~Waiter() {
    g_signal_handler_disconnect(
        G_OBJECT(browser_->window()->GetNativeHandle()),
        handler_id_);
  }

  virtual void Observe(NotificationType type,
                       const NotificationSource& source,
                       const NotificationDetails& details) {
    DCHECK(type == NotificationType::SCREEN_LOCK_STATE_CHANGED);
    MessageLoop::current()->Quit();
  }

  // Wait until the two conditions are met.
  void Wait(bool locker_state, bool fullscreen) {
    scoped_ptr<chromeos::test::ScreenLockerTester>
        tester(chromeos::ScreenLocker::GetTester());
    while (tester->IsLocked() != locker_state ||
           browser_->window()->IsFullscreen() != fullscreen) {
      ui_test_utils::RunMessageLoop();
    }
    // Make sure all pending tasks are executed.
    ui_test_utils::RunAllPendingInMessageLoop();
  }

  CHROMEGTK_CALLBACK_1(Waiter, gboolean, OnWindowStateEvent,
                       GdkEventWindowState*);

 private:
  Browser* browser_;
  gulong handler_id_;
  NotificationRegistrar registrar_;

  DISALLOW_COPY_AND_ASSIGN(Waiter);
};

gboolean Waiter::OnWindowStateEvent(GtkWidget* widget,
                                    GdkEventWindowState* event) {
  MessageLoop::current()->Quit();
  return false;
}

}  // namespace

namespace chromeos {

class ScreenLockerTest : public CrosInProcessBrowserTest {
 public:
  ScreenLockerTest() : mock_screen_lock_library_(NULL),
                       mock_input_method_library_(NULL) {
  }

 protected:
  MockScreenLockLibrary *mock_screen_lock_library_;
  MockInputMethodLibrary *mock_input_method_library_;

  // Test the no password mode with different unlock scheme given by
  // |unlock| function.
  void TestNoPassword(void (unlock)(views::Widget*)) {
    EXPECT_CALL(*mock_screen_lock_library_, NotifyScreenUnlockRequested())
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*mock_screen_lock_library_, NotifyScreenLockCompleted())
        .Times(1)
        .RetiresOnSaturation();
    UserManager::Get()->OffTheRecordUserLoggedIn();
    ScreenLocker::Show();
    scoped_ptr<test::ScreenLockerTester> tester(ScreenLocker::GetTester());
    tester->EmulateWindowManagerReady();
    ui_test_utils::WaitForNotification(
        NotificationType::SCREEN_LOCK_STATE_CHANGED);
    EXPECT_TRUE(tester->IsLocked());
    tester->InjectMockAuthenticator("", "");

    unlock(tester->GetWidget());

    ui_test_utils::RunAllPendingInMessageLoop();
    EXPECT_TRUE(tester->IsLocked());

    // Emulate LockScreen request from PowerManager (via SessionManager).
    ScreenLocker::Hide();
    ui_test_utils::RunAllPendingInMessageLoop();
    EXPECT_FALSE(tester->IsLocked());
  }

 private:
  virtual void SetUpInProcessBrowserTestFixture() {
    cros_mock_->InitStatusAreaMocks();
    cros_mock_->InitMockScreenLockLibrary();
    mock_screen_lock_library_ = cros_mock_->mock_screen_lock_library();
    mock_input_method_library_ = cros_mock_->mock_input_method_library();
    EXPECT_CALL(*mock_screen_lock_library_, AddObserver(testing::_))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*mock_screen_lock_library_, NotifyScreenUnlockCompleted())
        .Times(1)
        .RetiresOnSaturation();
    // Expectations for the status are on the screen lock window.
    cros_mock_->SetStatusAreaMocksExpectations();
    // Expectations for the status area on the browser window.
    cros_mock_->SetStatusAreaMocksExpectations();
  }

  virtual void SetUpCommandLine(CommandLine* command_line) {
    command_line->AppendSwitchASCII(switches::kLoginProfile, "user");
    command_line->AppendSwitch(switches::kNoFirstRun);
  }

  DISALLOW_COPY_AND_ASSIGN(ScreenLockerTest);
};

IN_PROC_BROWSER_TEST_F(ScreenLockerTest, TestBasic) {
  EXPECT_CALL(*mock_input_method_library_, GetNumActiveInputMethods())
      .Times(1)
      .WillRepeatedly((testing::Return(0)))
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_screen_lock_library_, NotifyScreenUnlockRequested())
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_screen_lock_library_, NotifyScreenLockCompleted())
      .Times(1)
      .RetiresOnSaturation();
  UserManager::Get()->UserLoggedIn("user");
  ScreenLocker::Show();
  scoped_ptr<test::ScreenLockerTester> tester(ScreenLocker::GetTester());
  tester->EmulateWindowManagerReady();
  ui_test_utils::WaitForNotification(
      NotificationType::SCREEN_LOCK_STATE_CHANGED);

  // Test to make sure that the widget is actually appearing and is of
  // reasonable size, preventing a regression of
  // http://code.google.com/p/chromium-os/issues/detail?id=5987
  gfx::Rect lock_bounds;
  tester->GetChildWidget()->GetBounds(&lock_bounds, true);
  EXPECT_GT(lock_bounds.width(), 10);
  EXPECT_GT(lock_bounds.height(), 10);

  tester->InjectMockAuthenticator("user", "pass");
  EXPECT_TRUE(tester->IsLocked());
  tester->EnterPassword("fail");
  ui_test_utils::RunAllPendingInMessageLoop();
  EXPECT_TRUE(tester->IsLocked());
  tester->EnterPassword("pass");
  ui_test_utils::RunAllPendingInMessageLoop();
  // Successful authentication simply send a unlock request to PowerManager.
  EXPECT_TRUE(tester->IsLocked());

  // Emulate LockScreen request from PowerManager (via SessionManager).
  // TODO(oshima): Find out better way to handle this in mock.
  ScreenLocker::Hide();
  ui_test_utils::RunAllPendingInMessageLoop();
  EXPECT_FALSE(tester->IsLocked());
}

IN_PROC_BROWSER_TEST_F(ScreenLockerTest, TestFullscreenExit) {
  EXPECT_CALL(*mock_screen_lock_library_, NotifyScreenUnlockRequested())
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_screen_lock_library_, NotifyScreenLockCompleted())
      .Times(1)
      .RetiresOnSaturation();
  scoped_ptr<test::ScreenLockerTester> tester(ScreenLocker::GetTester());
  {
    Waiter waiter(browser());
    browser()->ToggleFullscreenMode();
    waiter.Wait(false /* not locked */, true /* full screen */);
    EXPECT_TRUE(browser()->window()->IsFullscreen());
    EXPECT_FALSE(tester->IsLocked());
  }
  {
    Waiter waiter(browser());
    UserManager::Get()->UserLoggedIn("user");
    ScreenLocker::Show();
    tester->EmulateWindowManagerReady();
    waiter.Wait(true /* locked */, false /* full screen */);
    EXPECT_FALSE(browser()->window()->IsFullscreen());
    EXPECT_TRUE(tester->IsLocked());
  }
  tester->InjectMockAuthenticator("user", "pass");
  tester->EnterPassword("pass");
  ui_test_utils::RunAllPendingInMessageLoop();
  ScreenLocker::Hide();
  ui_test_utils::RunAllPendingInMessageLoop();
  EXPECT_FALSE(tester->IsLocked());
}

void MouseMove(views::Widget* widget) {
  ui_controls::SendMouseMove(10, 10);
}

IN_PROC_BROWSER_TEST_F(ScreenLockerTest, TestNoPasswordWithMouseMove) {
  TestNoPassword(MouseMove);
}

void MouseClick(views::Widget* widget) {
  ui_controls::SendMouseClick(ui_controls::RIGHT);
}

IN_PROC_BROWSER_TEST_F(ScreenLockerTest, TestNoPasswordWithMouseClick) {
  TestNoPassword(MouseClick);
}

void KeyPress(views::Widget* widget) {
  ui_controls::SendKeyPress(GTK_WINDOW(widget->GetNativeView()),
                            app::VKEY_SPACE, false, false, false, false);
}

IN_PROC_BROWSER_TEST_F(ScreenLockerTest, TestNoPasswordWithKeyPress) {
  TestNoPassword(KeyPress);
}

IN_PROC_BROWSER_TEST_F(ScreenLockerTest, TestShowTwice) {
  EXPECT_CALL(*mock_screen_lock_library_, NotifyScreenLockCompleted())
      .Times(2)
      .RetiresOnSaturation();

  UserManager::Get()->UserLoggedIn("user");
  ScreenLocker::Show();
  scoped_ptr<test::ScreenLockerTester> tester(ScreenLocker::GetTester());
  tester->EmulateWindowManagerReady();
  ui_test_utils::WaitForNotification(
      NotificationType::SCREEN_LOCK_STATE_CHANGED);
  EXPECT_TRUE(tester->IsLocked());

  // Calling Show again simply send LockCompleted signal.
  ScreenLocker::Show();
  EXPECT_TRUE(tester->IsLocked());

  // Close the locker to match expectations.
  ScreenLocker::Hide();
  ui_test_utils::RunAllPendingInMessageLoop();
  EXPECT_FALSE(tester->IsLocked());
}

}  // namespace chromeos
<commit_msg>chromeos: Fix screen locker tests.<commit_after>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/command_line.h"
#include "base/message_loop.h"
#include "base/scoped_ptr.h"
#include "chrome/browser/automation/ui_controls.h"
#include "chrome/browser/browser_thread.h"
#include "chrome/browser/browser_window.h"
#include "chrome/browser/chromeos/cros/cros_in_process_browser_test.h"
#include "chrome/browser/chromeos/cros/mock_input_method_library.h"
#include "chrome/browser/chromeos/cros/mock_screen_lock_library.h"
#include "chrome/browser/chromeos/login/mock_authenticator.h"
#include "chrome/browser/chromeos/login/screen_locker.h"
#include "chrome/browser/chromeos/login/screen_locker_tester.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/views/browser_dialogs.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/notification_type.h"
#include "chrome/test/ui_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "views/controls/textfield/textfield.h"
#include "views/window/window_gtk.h"

namespace {

// An object that wait for lock state and fullscreen state.
class Waiter : public NotificationObserver {
 public:
  explicit Waiter(Browser* browser)
      : browser_(browser),
        running_(false) {
    registrar_.Add(this,
                   NotificationType::SCREEN_LOCK_STATE_CHANGED,
                   NotificationService::AllSources());
    handler_id_ = g_signal_connect(
        G_OBJECT(browser_->window()->GetNativeHandle()),
        "window-state-event",
        G_CALLBACK(OnWindowStateEventThunk),
        this);
  }

  ~Waiter() {
    g_signal_handler_disconnect(
        G_OBJECT(browser_->window()->GetNativeHandle()),
        handler_id_);
  }

  virtual void Observe(NotificationType type,
                       const NotificationSource& source,
                       const NotificationDetails& details) {
    DCHECK(type == NotificationType::SCREEN_LOCK_STATE_CHANGED);
    if (running_)
      MessageLoop::current()->Quit();
  }

  // Wait until the two conditions are met.
  void Wait(bool locker_state, bool fullscreen) {
    running_ = true;
    scoped_ptr<chromeos::test::ScreenLockerTester>
        tester(chromeos::ScreenLocker::GetTester());
    while (tester->IsLocked() != locker_state ||
           browser_->window()->IsFullscreen() != fullscreen) {
      ui_test_utils::RunMessageLoop();
    }
    // Make sure all pending tasks are executed.
    ui_test_utils::RunAllPendingInMessageLoop();
    running_ = false;
  }

  CHROMEGTK_CALLBACK_1(Waiter, gboolean, OnWindowStateEvent,
                       GdkEventWindowState*);

 private:
  Browser* browser_;
  gulong handler_id_;
  NotificationRegistrar registrar_;

  // Are we currently running the message loop?
  bool running_;

  DISALLOW_COPY_AND_ASSIGN(Waiter);
};

gboolean Waiter::OnWindowStateEvent(GtkWidget* widget,
                                    GdkEventWindowState* event) {
  MessageLoop::current()->Quit();
  return false;
}

}  // namespace

namespace chromeos {

class ScreenLockerTest : public CrosInProcessBrowserTest {
 public:
  ScreenLockerTest() : mock_screen_lock_library_(NULL),
                       mock_input_method_library_(NULL) {
  }

 protected:
  MockScreenLockLibrary *mock_screen_lock_library_;
  MockInputMethodLibrary *mock_input_method_library_;

  // Test the no password mode with different unlock scheme given by
  // |unlock| function.
  void TestNoPassword(void (unlock)(views::Widget*)) {
    EXPECT_CALL(*mock_screen_lock_library_, NotifyScreenUnlockRequested())
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*mock_screen_lock_library_, NotifyScreenLockCompleted())
        .Times(1)
        .RetiresOnSaturation();
    UserManager::Get()->OffTheRecordUserLoggedIn();
    ScreenLocker::Show();
    scoped_ptr<test::ScreenLockerTester> tester(ScreenLocker::GetTester());
    tester->EmulateWindowManagerReady();
    if (!chromeos::ScreenLocker::GetTester()->IsLocked())
      ui_test_utils::WaitForNotification(
          NotificationType::SCREEN_LOCK_STATE_CHANGED);
    EXPECT_TRUE(tester->IsLocked());
    tester->InjectMockAuthenticator("", "");

    unlock(tester->GetWidget());

    ui_test_utils::RunAllPendingInMessageLoop();
    EXPECT_TRUE(tester->IsLocked());

    // Emulate LockScreen request from PowerManager (via SessionManager).
    ScreenLocker::Hide();
    ui_test_utils::RunAllPendingInMessageLoop();
    EXPECT_FALSE(tester->IsLocked());
  }

 private:
  virtual void SetUpInProcessBrowserTestFixture() {
    cros_mock_->InitStatusAreaMocks();
    cros_mock_->InitMockScreenLockLibrary();
    mock_screen_lock_library_ = cros_mock_->mock_screen_lock_library();
    mock_input_method_library_ = cros_mock_->mock_input_method_library();
    EXPECT_CALL(*mock_screen_lock_library_, AddObserver(testing::_))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*mock_screen_lock_library_, NotifyScreenUnlockCompleted())
        .Times(1)
        .RetiresOnSaturation();
    // Expectations for the status are on the screen lock window.
    cros_mock_->SetStatusAreaMocksExpectations();
    // Expectations for the status area on the browser window.
    cros_mock_->SetStatusAreaMocksExpectations();
  }

  virtual void SetUpCommandLine(CommandLine* command_line) {
    command_line->AppendSwitchASCII(switches::kLoginProfile, "user");
    command_line->AppendSwitch(switches::kNoFirstRun);
  }

  DISALLOW_COPY_AND_ASSIGN(ScreenLockerTest);
};

IN_PROC_BROWSER_TEST_F(ScreenLockerTest, TestBasic) {
  EXPECT_CALL(*mock_input_method_library_, GetNumActiveInputMethods())
      .Times(1)
      .WillRepeatedly((testing::Return(0)))
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_screen_lock_library_, NotifyScreenUnlockRequested())
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_screen_lock_library_, NotifyScreenLockCompleted())
      .Times(1)
      .RetiresOnSaturation();
  UserManager::Get()->UserLoggedIn("user");
  ScreenLocker::Show();
  scoped_ptr<test::ScreenLockerTester> tester(ScreenLocker::GetTester());
  tester->EmulateWindowManagerReady();
  if (!chromeos::ScreenLocker::GetTester()->IsLocked())
    ui_test_utils::WaitForNotification(
        NotificationType::SCREEN_LOCK_STATE_CHANGED);

  // Test to make sure that the widget is actually appearing and is of
  // reasonable size, preventing a regression of
  // http://code.google.com/p/chromium-os/issues/detail?id=5987
  gfx::Rect lock_bounds;
  tester->GetChildWidget()->GetBounds(&lock_bounds, true);
  EXPECT_GT(lock_bounds.width(), 10);
  EXPECT_GT(lock_bounds.height(), 10);

  tester->InjectMockAuthenticator("user", "pass");
  EXPECT_TRUE(tester->IsLocked());
  tester->EnterPassword("fail");
  ui_test_utils::RunAllPendingInMessageLoop();
  EXPECT_TRUE(tester->IsLocked());
  tester->EnterPassword("pass");
  ui_test_utils::RunAllPendingInMessageLoop();
  // Successful authentication simply send a unlock request to PowerManager.
  EXPECT_TRUE(tester->IsLocked());

  // Emulate LockScreen request from PowerManager (via SessionManager).
  // TODO(oshima): Find out better way to handle this in mock.
  ScreenLocker::Hide();
  ui_test_utils::RunAllPendingInMessageLoop();
  EXPECT_FALSE(tester->IsLocked());
}

IN_PROC_BROWSER_TEST_F(ScreenLockerTest, TestFullscreenExit) {
  EXPECT_CALL(*mock_screen_lock_library_, NotifyScreenUnlockRequested())
      .Times(1)
      .RetiresOnSaturation();
  EXPECT_CALL(*mock_screen_lock_library_, NotifyScreenLockCompleted())
      .Times(1)
      .RetiresOnSaturation();
  scoped_ptr<test::ScreenLockerTester> tester(ScreenLocker::GetTester());
  {
    Waiter waiter(browser());
    browser()->ToggleFullscreenMode();
    waiter.Wait(false /* not locked */, true /* full screen */);
    EXPECT_TRUE(browser()->window()->IsFullscreen());
    EXPECT_FALSE(tester->IsLocked());
  }
  {
    Waiter waiter(browser());
    UserManager::Get()->UserLoggedIn("user");
    ScreenLocker::Show();
    tester->EmulateWindowManagerReady();
    waiter.Wait(true /* locked */, false /* full screen */);
    EXPECT_FALSE(browser()->window()->IsFullscreen());
    EXPECT_TRUE(tester->IsLocked());
  }
  tester->InjectMockAuthenticator("user", "pass");
  tester->EnterPassword("pass");
  ui_test_utils::RunAllPendingInMessageLoop();
  ScreenLocker::Hide();
  ui_test_utils::RunAllPendingInMessageLoop();
  EXPECT_FALSE(tester->IsLocked());
}

void MouseMove(views::Widget* widget) {
  ui_controls::SendMouseMove(10, 10);
}

IN_PROC_BROWSER_TEST_F(ScreenLockerTest, TestNoPasswordWithMouseMove) {
  TestNoPassword(MouseMove);
}

void MouseClick(views::Widget* widget) {
  ui_controls::SendMouseClick(ui_controls::RIGHT);
}

IN_PROC_BROWSER_TEST_F(ScreenLockerTest, TestNoPasswordWithMouseClick) {
  TestNoPassword(MouseClick);
}

void KeyPress(views::Widget* widget) {
  ui_controls::SendKeyPress(GTK_WINDOW(widget->GetNativeView()),
                            app::VKEY_SPACE, false, false, false, false);
}

IN_PROC_BROWSER_TEST_F(ScreenLockerTest, TestNoPasswordWithKeyPress) {
  TestNoPassword(KeyPress);
}

IN_PROC_BROWSER_TEST_F(ScreenLockerTest, TestShowTwice) {
  EXPECT_CALL(*mock_screen_lock_library_, NotifyScreenLockCompleted())
      .Times(2)
      .RetiresOnSaturation();

  UserManager::Get()->UserLoggedIn("user");
  ScreenLocker::Show();
  scoped_ptr<test::ScreenLockerTester> tester(ScreenLocker::GetTester());
  tester->EmulateWindowManagerReady();
  if (!chromeos::ScreenLocker::GetTester()->IsLocked())
    ui_test_utils::WaitForNotification(
        NotificationType::SCREEN_LOCK_STATE_CHANGED);
  EXPECT_TRUE(tester->IsLocked());

  // Calling Show again simply send LockCompleted signal.
  ScreenLocker::Show();
  EXPECT_TRUE(tester->IsLocked());

  // Close the locker to match expectations.
  ScreenLocker::Hide();
  ui_test_utils::RunAllPendingInMessageLoop();
  EXPECT_FALSE(tester->IsLocked());
}

}  // namespace chromeos
<|endoftext|>