QtWebKitで読み込まれたリソースの傍受、またはダブステップでユニコーンをサドルに乗せた方法



Habrahabr::Instance()->hello();









私は長い間、何も書きませんでした しかし先週、私はQtWebkit 5.1モジュールで**かなり揺れ動き、画面イメージなどをキャプチャしようとする場合に備えて、そこにどんな暗がりが待っているのか教えてくれるといいと思いました。



実際、私のタスクは、閲覧するすべてのページからすべての画像を保存するブラウザを作成することでした。 基本的なタスク、一見したところ:ハンドラーを別のスレッドに掛け、セレクター「img」によってすべてのQWebElementを反復処理し、QImageのQPainterを介してそのコンテンツ(QWebElement :: render())を描画し、QImageはファイルに保存されます。



しかし、残念なことに、すべてがそれほど単純ではないことが判明しました。 このポストのカットの下で私が設定したタスクを達成するために私が取ったサムライのパスについて。 いってらっしゃい!





ステージ1.問題



Mac上のGitの最新のQt 5について、前の段落で示したアルゴリズムを実装し、Clang 64ビットを構築しました。 一般に、アルゴリズムは機能しませんでした。 保存された画像はすべて、黒い長方形または地獄のゴミのいずれかでした。 そのとき、同様の機能を実装するQt 5の配信からのがあることを思い出しました。 READMEに示されているとおりに、すぐに組み立てて適用しました。 動作しません。 公式の例は、奇妙なことに、うまくいきません。 Linuxでも同様にテストしました。



そして何をすべきか? しかし、何も、このことは機能せず、理由は明らかではありません。 これに対処する時間がなかったので、代替ソリューションを探していました。 スタジオでの誘惑のムッシュを、JavaScriptを介してバックエンドに画像を転送する方法を試しました。 メソッドは非常に簡単です-写真を撮ってキャンバスに描画し、base64のキャンバスのコンテンツをバックエンドに送信します。 そこで、デコードし、きれいにし、鮮明な画像に変換します。



恥辱! この方法により、前の画像と同様の画像が得られました。 ここで明らかに何かが間違っていますが、私は走っています、振り返る時間がないので、別の決定が生まれます!





ステージ2.ソリューション



しかし、ページがロードするリソースをインターセプトするとどうなりますか? なぜだと思いました。 ドックQNetworkAccessManagerを読むためにすぐに左に-ちょっと! そして、それはそれがどのように機能するかです。 QWebViewがあります。これには、以前に定義したカスタムQNetworkAccessManagerでQWebPageを自由に設定します。実際には、クラスはInterceptorManager(QNAMから継承)です。



InterceptorManagerの定義は次のようなものです。



 class InterceptorManager : public QNetworkAccessManager { Q_OBJECT public: explicit InterceptorManager(QObject *parent = 0); protected: QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) { QNetworkReply *real = QNetworkAccessManager::createRequest(op, request, outgoingData); if (request.url().toString().endsWith(".png")) { NetworkReplyProxy *proxy = new NetworkReplyProxy(this, real); return proxy; } return real; } };
      
      





createRequest()をオーバーライドし、すべてのリクエストに対して、作成したQNetworkReplyクラスプロキシを返します。 なぜこれが必要なのですか? QIODeviceの子孫であるQNetworkReplyには、コンテンツを再度読み取る機能はありません。 画像をレンダリングするにはQWebPageが必要です。 プロキシを使用すると、コンテンツをコピーして後で使用できます。



QNetworkReplyのプロキシは簡単なタスクではないため、例を挙げます。

networkreplyproxy.h(非推奨-以下のリポジトリの最新版)
 #include <QApplication> #include <QWebFrame> #include <QWebPage> #include <QWebView> #include <QWebSettings> #include <QDebug> #include <QDateTime> #include <QDebug> #include <QFile> #include <QTimer> #include <QNetworkProxy> #include <QNetworkReply> #include <QNetworkCookie> class NetworkReplyProxy : public QNetworkReply { Q_OBJECT public: NetworkReplyProxy(QObject* parent, QNetworkReply* reply) : QNetworkReply(parent) , m_reply(reply) { // apply attributes... setOperation(m_reply->operation()); setRequest(m_reply->request()); setUrl(m_reply->url()); // handle these to forward connect(m_reply, SIGNAL(metaDataChanged()), SLOT(applyMetaData())); connect(m_reply, SIGNAL(readyRead()), SLOT(readInternal())); connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(errorInternal(QNetworkReply::NetworkError))); // forward signals connect(m_reply, SIGNAL(finished()), SIGNAL(finished())); connect(m_reply, SIGNAL(uploadProgress(qint64,qint64)), SIGNAL(uploadProgress(qint64,qint64))); connect(m_reply, SIGNAL(downloadProgress(qint64,qint64)), SIGNAL(downloadProgress(qint64,qint64))); // for the data proxy... setOpenMode(ReadOnly); } ~NetworkReplyProxy() { if (m_reply->url().scheme() != "data") writeDataPrivate(); delete m_reply; } // virtual methids void abort() { m_reply->abort(); } void close() { m_reply->close(); } bool isSequential() const { return m_reply->isSequential(); } // not possible... void setReadBufferSize(qint64 size) { QNetworkReply::setReadBufferSize(size); m_reply->setReadBufferSize(size); } // ssl magic is not done.... // isFinished()/isRunning can not be done *sigh* // QIODevice proxy... virtual qint64 bytesAvailable() const { return m_buffer.size() + QIODevice::bytesAvailable(); } virtual qint64 bytesToWrite() const { return -1; } virtual bool canReadLine() const { qFatal("not implemented"); return false; } virtual bool waitForReadyRead(int) { qFatal("not implemented"); return false; } virtual bool waitForBytesWritten(int) { qFatal("not implemented"); return false; } virtual qint64 readData(char* data, qint64 maxlen) { qint64 size = qMin(maxlen, qint64(m_buffer.size())); memcpy(data, m_buffer.constData(), size); m_buffer.remove(0, size); return size; } signals: void resourceIntercepted(QByteArray); public Q_SLOTS: void ignoreSslErrors() { m_reply->ignoreSslErrors(); } void applyMetaData() { QList<QByteArray> headers = m_reply->rawHeaderList(); foreach(QByteArray header, headers) setRawHeader(header, m_reply->rawHeader(header)); setHeader(QNetworkRequest::ContentTypeHeader, m_reply->header(QNetworkRequest::ContentTypeHeader)); setHeader(QNetworkRequest::ContentLengthHeader, m_reply->header(QNetworkRequest::ContentLengthHeader)); setHeader(QNetworkRequest::LocationHeader, m_reply->header(QNetworkRequest::LocationHeader)); setHeader(QNetworkRequest::LastModifiedHeader, m_reply->header(QNetworkRequest::LastModifiedHeader)); setHeader(QNetworkRequest::SetCookieHeader, m_reply->header(QNetworkRequest::SetCookieHeader)); setAttribute(QNetworkRequest::HttpStatusCodeAttribute, m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute)); setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute)); setAttribute(QNetworkRequest::RedirectionTargetAttribute, m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute)); setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, m_reply->attribute(QNetworkRequest::ConnectionEncryptedAttribute)); setAttribute(QNetworkRequest::CacheLoadControlAttribute, m_reply->attribute(QNetworkRequest::CacheLoadControlAttribute)); setAttribute(QNetworkRequest::CacheSaveControlAttribute, m_reply->attribute(QNetworkRequest::CacheSaveControlAttribute)); setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, m_reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute)); setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, m_reply->attribute(QNetworkRequest::DoNotBufferUploadDataAttribute)); emit metaDataChanged(); } void errorInternal(QNetworkReply::NetworkError _error) { setError(_error, errorString()); emit error(_error); } void readInternal() { QByteArray data = m_reply->readAll(); m_data += data; m_buffer += data; emit readyRead(); } protected: void writeDataPrivate() { QByteArray httpHeader; QList<QByteArray> headers = rawHeaderList(); foreach(QByteArray header, headers) { if (header.toLower() == "content-encoding" || header.toLower() == "transfer-encoding" || header.toLower() == "content-length" || header.toLower() == "connection") continue; // special case for cookies.... we need to generate separate lines // QNetworkCookie::toRawForm is a bit broken and we have to do this // ourselves... some simple heuristic here.. if (header.toLower() == "set-cookie") { QList<QNetworkCookie> cookies = QNetworkCookie::parseCookies(rawHeader(header)); foreach (QNetworkCookie cookie, cookies) { httpHeader += "set-cookie: " + cookie.toRawForm() + "\r\n"; } } else { httpHeader += header + ": " + rawHeader(header) + "\r\n"; } } httpHeader += "content-length: " + QByteArray::number(m_data.size()) + "\r\n"; httpHeader += "\r\n"; if(m_reply->error() != QNetworkReply::NoError) { qWarning() << "\tError with: " << this << url() << error(); return; } const QByteArray origUrl = m_reply->url().toEncoded(); const QByteArray strippedUrl = m_reply->url().toEncoded(QUrl::RemoveFragment | QUrl::RemoveQuery); interceptResource(origUrl, m_data, httpHeader, operation(), attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()); } void interceptResource(const QByteArray& url, const QByteArray& data, const QByteArray& header, int operation, int response) { Q_UNUSED(header); Q_UNUSED(url); Q_UNUSED(operation); Q_UNUSED(response); emit resourceIntercepted(data); } private: QNetworkReply* m_reply; QByteArray m_data; QByteArray m_buffer; };
      
      







上記の行の正確さについて特に答えることはできないとすぐに言わなければなりませんが、これはうまくいきます。これが最も重要なことです。 いずれにせよ、このプロキシを使用することの安全性はあなたの肩にのみかかっています。



おわりに



私はこれらすべてを1つの商業プロジェクトでチェックしました-それは動作します。 これが誰かの助けになることを願っています。



ご清聴ありがとうございました

名前空間



UPD: githubのプロジェクトとして開発設計しました-github.com/tucnak/qtwebkit-ri、bon appetit



All Articles