From 2bb9a14110d909af1894426d456237bfc0b60ad4 Mon Sep 17 00:00:00 2001 From: Javier Date: Tue, 15 Dec 2015 02:42:46 +0100 Subject: implement the WebProxy agent --- sapd.pro | 6 ++- sapsocket.cc | 3 -- webproxyconn.cc | 160 +++++++++++++++++++++++++++++++++++++++++++++++++------ webproxyconn.h | 36 ++++++++++--- webproxytrans.cc | 74 +++++++++++++++++++++++++ webproxytrans.h | 42 +++++++++++++++ 6 files changed, 293 insertions(+), 28 deletions(-) create mode 100644 webproxytrans.cc create mode 100644 webproxytrans.h diff --git a/sapd.pro b/sapd.pro index f8a4f00..2ca4132 100644 --- a/sapd.pro +++ b/sapd.pro @@ -58,7 +58,8 @@ SOURCES += main.cc \ notificationagent.cc \ notificationconn.cc \ webproxyagent.cc \ - webproxyconn.cc + webproxyconn.cc \ + webproxytrans.cc HEADERS += \ sapbtlistener.h \ @@ -84,7 +85,8 @@ HEADERS += \ notificationconn.h \ endianhelpers.h \ webproxyagent.h \ - webproxyconn.h + webproxyconn.h \ + webproxytrans.h OTHER_FILES += \ rpm/sapd.yaml \ diff --git a/sapsocket.cc b/sapsocket.cc index b41e8f5..e7376d9 100644 --- a/sapsocket.cc +++ b/sapsocket.cc @@ -88,8 +88,6 @@ void SAPSocket::acceptIncomingData(const QByteArray &data) } else { _inLastSeqNum = frame.seqNum; - qDebug() << "Realiable received" << _inLastSeqNum; - if (!_timer.isActive()) { _timer.start(DELAYED_ACK_TIME, Qt::CoarseTimer, this); } @@ -110,7 +108,6 @@ void SAPSocket::timerEvent(QTimerEvent *event) { if (event->timerId() == _timer.timerId()) { if (_inLastSeqNum != _inLastAck) { - qDebug() << "Acking" << _inLastAck << _inLastSeqNum; peer()->writeAckToSession(_sessionId, _inLastSeqNum); _inLastAck = _inLastSeqNum; } diff --git a/webproxyconn.cc b/webproxyconn.cc index 1eb4909..d1590ca 100644 --- a/webproxyconn.cc +++ b/webproxyconn.cc @@ -1,7 +1,9 @@ #include +#include #include "sappeer.h" #include "endianhelpers.h" +#include "webproxytrans.h" #include "webproxyconn.h" WebProxyConn::WebProxyConn(SAPConnection *conn, QObject *parent) @@ -13,13 +15,13 @@ WebProxyConn::WebProxyConn(SAPConnection *conn, QObject *parent) connect(_in, SIGNAL(messageReceived()), SLOT(handleMessageReceived())); } -WebProxyConn::RequestMessage WebProxyConn::unpackRequestMessage(const QByteArray &data) +WebProxyConn::Message WebProxyConn::unpackMessage(const QByteArray &data) { - RequestMessage msg; + Message msg; int offset = 0; msg.command = read(data, offset); msg.subCommand = read(data, offset); - msg.type = static_cast(read(data, offset)); + msg.type = static_cast(read(data, offset)); msg.transactionId = read(data, offset); const quint32 len = read(data, offset); @@ -28,35 +30,163 @@ WebProxyConn::RequestMessage WebProxyConn::unpackRequestMessage(const QByteArray return msg; } -void WebProxyConn::handleStartTransaction(const RequestMessage &msg) +QByteArray WebProxyConn::packMessage(const Message &msg) { - QString req = QString::fromUtf8(msg.payload); + QByteArray data; + append(data, msg.command); + append(data, msg.subCommand); + append(data, msg.type); + append(data, msg.transactionId); + append(data, msg.payload.size()); + data.append(msg.payload); + return data; +} + +WebProxyConn::RequestHeader WebProxyConn::parseRequestHeader(const QByteArray &req) +{ + RequestHeader hdr; + int first = req.indexOf('\n'); + if (first <= 0) { + qWarning() << "Invalid request header"; + return hdr; + } + + QStringList reqLine = QString::fromLatin1(req.mid(0, first).trimmed()).split(' '); + QString method = reqLine.at(0); + + if (QString::compare(method, "connect", Qt::CaseInsensitive) == 0) { + hdr.connect = true; + QStringList host = reqLine.at(1).split(':'); + hdr.host = host.at(0); + hdr.port = host.at(1).toUInt(); + } else { + QUrl url(reqLine.at(1)); + hdr.connect = false; + hdr.host = url.host(QUrl::EncodeUnicode); + if (QString::compare(url.scheme(), "https", Qt::CaseInsensitive) == 0) { + hdr.port = url.port(443); + } else { + hdr.port = url.port(80); + } + } + + return hdr; +} + +QByteArray WebProxyConn::removeHeaders(const QByteArray &req) +{ + int offset = 0; + int next; + qDebug() << req; + qDebug() << "--- end ---"; + + while ((next = req.indexOf('\n', offset)) > 0) { + QByteArray line = req.mid(offset, next - offset).trimmed(); + if (line.isEmpty()) { + return req.mid(next + 1); + } + offset = next + 1; + } + + return QByteArray(); } -void WebProxyConn::handleCancelTransaction(const RequestMessage &msg) +void WebProxyConn::sendMessage(const Message &msg) { + _out->send(packMessage(msg)); +} + +void WebProxyConn::handleRequest(const Message &msg) +{ + QByteArray payload = msg.payload; + WebProxyTrans *trans = _trans.value(msg.transactionId, 0); + + if (!trans) { + RequestHeader hdr = parseRequestHeader(msg.payload); + qDebug() << "Starting transaction to" << hdr.host << hdr.port << (hdr.connect ? "tunnel" : "http"); + trans = new WebProxyTrans(msg.transactionId, hdr.connect, hdr.host, hdr.port, this); + connect(trans, SIGNAL(dataReceived(QByteArray)), this, SLOT(handleTransDataReceived(QByteArray))); + connect(trans, SIGNAL(disconnected()), this, SLOT(handleTransDisconnected())); + + // Discard request body if it was a CONNECT request. + if (hdr.connect) { + payload = removeHeaders(payload); + } + + _trans.insert(msg.transactionId, trans); + } + + if (!payload.isEmpty()) { + qDebug() << "Sending" << msg.transactionId << QString::fromLatin1(payload.mid(0, 30)) << "-"; + trans->write(payload); + } +} + +void WebProxyConn::handleAbort(const Message &msg) +{ + qDebug() << "Abort transaction" << msg.transactionId; + WebProxyTrans *trans = _trans.value(msg.transactionId, 0); + if (trans) { + delete trans; + _trans.remove(msg.transactionId); + } else { + qWarning() << "Transaction" << msg.transactionId << "does not exist"; + } } void WebProxyConn::handleMessageReceived() { QByteArray data = _in->receive(); - RequestMessage req = unpackRequestMessage(data); + Message msg = unpackMessage(data); - if (req.command != 1 || req.subCommand != 1) { - qWarning() << "Invalid command/subcommand: " << req.command << "/" << req.subCommand; + if (msg.command != 1 || msg.subCommand != 1) { + qWarning() << "Invalid command/subcommand: " << msg.command << "/" << msg.subCommand; return; } - switch (req.type) { - case RequestStartTransaction: - handleStartTransaction(req); + switch (msg.type) { + case MessageRequest: + handleRequest(msg); break; - case RequestCancelTransaction: - handleCancelTransaction(req); + case MessageAbort: + handleAbort(msg); break; default: - qWarning() << "Unknown request type" << req.type; + qWarning() << "Unknown request type" << msg.type; } } + +void WebProxyConn::handleTransDataReceived(const QByteArray &data) +{ + WebProxyTrans *trans = static_cast(sender()); + Message msg; + msg.command = 1; + msg.subCommand = 1; + msg.type = MessageResponse; + msg.transactionId = trans->transactionId(); + msg.payload = data; + + qDebug() << "Receiving" << msg.transactionId << QString::fromLatin1(data.mid(0, 30)) << "-"; + + sendMessage(msg); +} + +void WebProxyConn::handleTransDisconnected() +{ + WebProxyTrans *trans = static_cast(sender()); + Message msg; + msg.command = 1; + msg.subCommand = 1; + msg.type = MessageAbort; + msg.transactionId = trans->transactionId(); + msg.payload.clear(); // Empty payload signals disconnection + + qDebug() << "Sending disconnected event"; + + sendMessage(msg); + + _trans.remove(msg.transactionId); + trans->deleteLater(); +} diff --git a/webproxyconn.h b/webproxyconn.h index 705428d..c183462 100644 --- a/webproxyconn.h +++ b/webproxyconn.h @@ -5,6 +5,8 @@ #include "sapconnection.h" #include "sapsocket.h" +class WebProxyTrans; + class WebProxyConn : public QObject { Q_OBJECT @@ -13,31 +15,49 @@ public: WebProxyConn(SAPConnection *conn, QObject *parent = 0); protected: - enum RequestMessageType { - RequestStartTransaction = 1, - RequestCancelTransaction = 4 + enum MessageType { + MessageRequest = 1, + MessageResponse = 2, + MessageError = 3, + MessageAbort = 4 }; - struct RequestMessage { + struct Message { quint8 command; // Seems to be always 1 quint8 subCommand; // Seems to be always 1 - RequestMessageType type; + MessageType type; quint8 transactionId; // Monotonically increasing QByteArray payload; }; - static RequestMessage unpackRequestMessage(const QByteArray &data); + static Message unpackMessage(const QByteArray &data); + static QByteArray packMessage(const Message &msg); + + struct RequestHeader { + /** Whether this is a CONNECT request, i.e. tunnel. */ + bool connect; + QString host; + int port; + }; + + static RequestHeader parseRequestHeader(const QByteArray &req); + static QByteArray removeHeaders(const QByteArray &req); + + void sendMessage(const Message &msg); - void handleStartTransaction(const RequestMessage &msg); - void handleCancelTransaction(const RequestMessage &msg); + void handleRequest(const Message &msg); + void handleAbort(const Message &msg); private slots: void handleMessageReceived(); + void handleTransDataReceived(const QByteArray &data); + void handleTransDisconnected(); private: SAPConnection *_conn; SAPSocket *_in; SAPSocket *_out; + QMap _trans; }; #endif // WEBPROXYCONN_H diff --git a/webproxytrans.cc b/webproxytrans.cc new file mode 100644 index 0000000..23b41b7 --- /dev/null +++ b/webproxytrans.cc @@ -0,0 +1,74 @@ +#include +#include + +#include "webproxyconn.h" +#include "webproxytrans.h" + +static const QByteArray connectResponse("HTTP/1.1 200 Connection established\r\n\r\n"); + +WebProxyTrans::WebProxyTrans(int transactionId, bool tunnel, const QString &host, int port, WebProxyConn *conn) + : QObject(conn), _transactionId(transactionId), _tunnel(tunnel), _socket(new QTcpSocket(this)) +{ + connect(_socket, SIGNAL(connected()), this, SLOT(handleSocketConnected())); + connect(_socket, SIGNAL(bytesWritten(qint64)), this, SLOT(handleSocketDataWritten(qint64))); + connect(_socket, SIGNAL(readyRead()), this, SLOT(handleSocketDataReady())); + connect(_socket, SIGNAL(disconnected()), this, SLOT(handleSocketDisconnected())); + + _socket->connectToHost(host, port); +} + +int WebProxyTrans::transactionId() const +{ + return _transactionId; +} + +void WebProxyTrans::write(const QByteArray &data) +{ + if (!_outBuf.isEmpty() || !_socket->isValid()) { + _outBuf.append(data); + } else if (_socket->isValid()) { + // Write directly to socket + _socket->write(data); + } +} + +void WebProxyTrans::handleSocketConnected() +{ + qDebug() << "Transaction" << _transactionId << "Connected"; + if (_tunnel) { + emit dataReceived(connectResponse); + } + writeFromBuffer(); +} + +void WebProxyTrans::handleSocketDataWritten(qint64 written) +{ + Q_UNUSED(written); + writeFromBuffer(); +} + +void WebProxyTrans::handleSocketDataReady() +{ + emit dataReceived(_socket->readAll()); +} + +void WebProxyTrans::handleSocketDisconnected() +{ + qDebug() << "Transaction" << _transactionId << "Disconnected"; + emit disconnected(); +} + +WebProxyConn* WebProxyTrans::conn() +{ + return static_cast(parent()); +} + +void WebProxyTrans::writeFromBuffer() +{ + if (!_outBuf.isEmpty()) { + qint64 written = _socket->write(_outBuf); + if (written > 0) { + _outBuf.remove(0, written); + } + } +} diff --git a/webproxytrans.h b/webproxytrans.h new file mode 100644 index 0000000..950c7b9 --- /dev/null +++ b/webproxytrans.h @@ -0,0 +1,42 @@ +#ifndef WEBPROXYTRANS_H +#define WEBPROXYTRANS_H + +#include +#include + +class WebProxyConn; + +class WebProxyTrans : public QObject +{ + Q_OBJECT +public: + explicit WebProxyTrans(int transactionId, bool tunnel, const QString &host, int port, WebProxyConn *conn); + + int transactionId() const; + + void write(const QByteArray &data); + +signals: + void connected(); + void dataReceived(const QByteArray &data); + void error(); + void disconnected(); + +private slots: + void handleSocketConnected(); + void handleSocketDataWritten(qint64 written); + void handleSocketDataReady(); + void handleSocketDisconnected(); + +private: + WebProxyConn* conn(); + void writeFromBuffer(); + +private: + int _transactionId; + bool _tunnel; + QTcpSocket *_socket; + QByteArray _outBuf; +}; + +#endif // WEBPROXYTRANS_H -- cgit v1.2.3