/* * Copyright (C) 2012 Jason A. Donenfeld . All Rights Reserved. * * See COPYING for license information. * */ #include "JsonScgiPeer.h" #include "JsonScgiServer.h" #include "QtJson.h" #include #include #include using namespace QtJson; JsonScgiPeer::JsonScgiPeer(QTcpSocket *connection, JsonScgiServer *parent) : QObject(parent), m_connection(connection), m_server(parent), m_jsonOffset(0), m_jsonLength(0), m_https(false) { if (!connection->isOpen()) QTimer::singleShot(0, this, SLOT(disconnected())); if (connection->bytesAvailable() > 0) QTimer::singleShot(0, this, SLOT(readyRead())); connect(connection, SIGNAL(disconnected()), this, SLOT(disconnected())); connect(connection, SIGNAL(readyRead()), this, SLOT(readyRead())); } JsonScgiPeer::~JsonScgiPeer() { m_connection->deleteLater(); } void JsonScgiPeer::sendResponse(const QVariantMap &response) { m_outputBuffer = "Status: 200 OK\r\nContent-Type: application/json\r\n" "Set-Cookie: JSONSCGIQTSESSION=" + m_sessionCookie.toAscii() + "; path=/; expires=" + QLocale("C").toString(m_server->cookieExpiration(m_sessionCookie), "ddd, dd MMM yyyy hh:mm:ss").toAscii() + " GMT; HttpOnly" + (m_https ? "; secure" : "") + "\r\n\r\n" + Json::serialize(response) + "\n"; QTimer::singleShot(0, this, SLOT(readyWrite())); } void JsonScgiPeer::die() { m_connection->close(); } void JsonScgiPeer::notFound() { m_outputBuffer = "Status: 404 Not Found\r\nContent-Type: text/plain\r\n\r\nThese aren't the droids you're looking for.\n"; QTimer::singleShot(0, this, SLOT(readyWrite())); } // Implements http://python.ca/scgi/protocol.txt void JsonScgiPeer::parseScgiHeader() { int i = m_inputBuffer.indexOf(':'); if (i == -1) return; bool ok; m_jsonOffset = m_inputBuffer.left(i).toUInt(&ok) + 2 + i; if (!ok) return; if (static_cast(m_inputBuffer.size()) < m_jsonOffset) return; ++i; // Sure, hate me. But sometimes C strings are just simpler, // especially when you're dealing with a null-terminated spec. // Besides, Qt ensures that data() will be null terminated so // that we don't risk any overruns. const char *key, *value; const char *data = m_inputBuffer.constData(); while (static_cast(i) < m_jsonOffset) { if (data[i] == ',') break; key = data + i; i += strlen(key) + 1; if (static_cast(i) >= m_jsonOffset) break; value = data + i; i += strlen(value) + 1; if (!strcmp(key, "CONTENT_LENGTH")) m_jsonLength = QString(value).toUInt(); else if (!strcmp(key, "REQUEST_URI")) m_url.setUrl(value); else if (!strcmp(key, "HTTP_COOKIE")) { QString cookieList(value); QStringList cookies = cookieList.split(';', QString::SkipEmptyParts); foreach (const QString &cookie, cookies) { QStringList cookiePair = cookie.split('=', QString::SkipEmptyParts); if (cookiePair.size() != 2) continue; if (cookiePair.at(0).trimmed() == "JSONSCGIQTSESSION") { m_sessionCookie = cookiePair.at(1); break; } } } else if (!strcmp(key, "HTTPS")) m_https = true; } if (!m_jsonLength) m_server->newWebRequest(m_url, m_sessionCookie, QVariantMap(), *this); else if (static_cast(m_inputBuffer.size()) >= m_jsonOffset + m_jsonLength) parseJsonBody(); } void JsonScgiPeer::parseJsonBody() { if (static_cast(m_inputBuffer.size()) < m_jsonOffset + m_jsonLength) return; bool ok; const QVariantMap result = Json::parse(m_inputBuffer.data() + m_jsonOffset, ok).toMap(); if (!ok) { notFound(); return; } m_server->newWebRequest(m_url, m_sessionCookie, result, *this); } void JsonScgiPeer::disconnected() { deleteLater(); } void JsonScgiPeer::readyRead() { if (m_inputBuffer.size() + m_connection->bytesAvailable() > 100 * 1024 * 1024 /* 100 megs */) { die(); return; } m_inputBuffer.append(m_connection->readAll()); if (m_connection->bytesAvailable()) QTimer::singleShot(0, this, SLOT(readyRead())); if (m_jsonOffset && m_jsonLength && static_cast(m_inputBuffer.size()) >= m_jsonLength + m_jsonOffset) parseJsonBody(); else parseScgiHeader(); } void JsonScgiPeer::readyWrite() { int writen = m_connection->write(m_outputBuffer); if (writen == -1) { die(); return; } m_outputBuffer.remove(0, writen); if (m_outputBuffer.size()) QTimer::singleShot(0, this, SLOT(readyWrite())); else m_connection->disconnectFromHost(); }