3 #include "htmlprivate.h"
9 #include <QNetworkAccessManager>
10 #include <QNetworkCookieJar>
11 #include <QNetworkRequest>
12 #include <QNetworkReply>
18 // ProxyContext ---------------------------------------------------------------
20 TM::ProxyContext::ProxyContext(Settings *settings, quint16 http, quint16 socket)
22 , m_settings(settings)
23 , m_manager(new QNetworkAccessManager(this))
24 , m_cookie(new QNetworkCookieJar(this))
26 , m_socket_port(socket)
28 m_manager->setCookieJar(m_cookie);
29 assert(settings->contains("ProxyModule/prefix"));
30 m_prefix = settings->value("ProxyModule/prefix").toString();
31 assert(settings->contains("ProxyHandler/jscode"));
32 m_jscode = settings->value("ProxyHandler/jscode").toString();
35 QNetworkAccessManager* TM::ProxyContext::network_access_manager()
40 quint16 TM::ProxyContext::http_port() const { return m_http_port; }
42 quint16 TM::ProxyContext::socket_port() const { return m_socket_port; }
44 QString TM::ProxyContext::prefix() const { return m_prefix; }
46 QString TM::ProxyContext::jscode() const { return m_jscode; }
48 // ProxyModule ----------------------------------------------------------------
50 TM::ProxyModule::ProxyModule(Settings *settings, quint16 http, quint16 socket,
53 , m_thread(new QThread(this))
54 , m_context(new ProxyContext(settings, http, socket))
56 m_context->moveToThread(m_thread);
57 connect(m_thread, SIGNAL(finished()), m_context, SLOT(deleteLater()));
62 TM::ProxyModule::~ProxyModule()
68 HttpHandler* TM::ProxyModule::create_handler(QByteArray method, QByteArray url)
70 if(method.toLower() != "get") return nullptr;
71 if(!url.toLower().startsWith(m_context->prefix().toUtf8())) return nullptr;
73 HttpHandler *handler = new ProxyHandler(m_context);
74 handler->moveToThread(m_thread);
78 void TM::ProxyModule::destroy_handler(HttpHandler *handler)
80 QMetaObject::invokeMethod(handler, "deleteLater");
83 // ProxyHandler ---------------------------------------------------------------
85 TM::ProxyHandler::ProxyHandler(ProxyContext *context)
92 TM::ProxyHandler::~ProxyHandler()
94 if(m_reply) m_reply->deleteLater();
97 int TM::ProxyHandler::run()
99 m_targetUrl = QUrl::fromUserInput(
100 m_request->url().remove(0, m_context->prefix().toUtf8().length()));
102 m_reply = m_context->network_access_manager()->get(QNetworkRequest(m_targetUrl));
103 connect(m_reply, SIGNAL(finished()), this, SLOT(onNetworkReplyFinished()));
107 void TM::ProxyHandler::onNetworkReplyFinished()
112 int status = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
118 case 301: // リダイレクト先をブラウザにキャッシュさせないため302に固定。
128 * \brief ブラウザに302を返しリダイレクトさせます。
131 int TM::ProxyHandler::redirect()
134 QUrl url = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
135 if(url.scheme() != "http") return code_502();
139 host() + prefix() + url.toString().remove(0, 2); // スキーム(http:)を除いた後の//を取り除く
141 return code_302(QUrl(redirect));
148 int TM::ProxyHandler::response()
153 QByteArray bytes = m_reply->readAll();
154 if(bytes.isEmpty()) return code_502();
156 HtmlDocument document(bytes);
157 if(!document) return code_502();
159 HtmlNode html = document.first("html");
160 HtmlNode head = html.first("head");
161 HtmlNode body = html.first("body");
164 // Tidyが書き込むmetaタグを除去
165 head.remove(head.first("meta"));
167 for(Html::pointer p = html.lbegin(); p; p = sanitize(p));
168 // baseを設定(元のHTMLに設定されている場合、書き換えない)
169 HtmlNode base = head.first("base");
170 if(base.type() == HtmlNode::Null)
172 base = head.insert("base", head.begin());
173 base.set_attribute("href", m_targetUrl.toString());
176 node = head.insert("style", head.end());
177 node.insert_text(" .wordring-segment:hover{ color:#ff0000; } ", node.end());
180 QFile file(m_context->jscode());
181 file.open(QIODevice::ReadOnly);
182 QByteArray js = file.readAll();
184 node = head.insert("script", head.end()).set_attribute("type", "text/javascript");
185 node.insert_comment(QString::fromUtf8(js), node.end());
187 node = head.insert("script", head.end()).set_attribute("type", "text/javascript");
188 QString str1 = "\nwindow.wordring.port=";
189 str1 += QString::number(m_context->socket_port()) + ";\n";
190 str1 += "window.wordring.url='";
191 str1 += m_targetUrl.toString() + "';\n";
192 node.insert_comment(str1, node.end());
194 create_paragraph(body.lbegin(), body.ltail());
196 m_response.set_attribute("Content-Type", "text/html");
197 m_response.set_attribute("Transfer-Encoding", "chunked");
199 write(document.to_byte_array());
200 write(output.toUtf8());
207 * \brief 与えられたノードpを綺麗にします。
212 * - noscript要素をunwrapします。
215 Html::pointer TM::ProxyHandler::sanitize(Html::pointer p)
217 bool remove_ = false;
218 Html::pointer result = p->lnext();
220 if(p->type() == Html::Element && p->place() == Html::Open)
222 int tclass_ = p->tclass();
224 if(tclass_ == Html::Script) remove_ = true;
225 else if(tclass_ == Html::Noscript)
227 HtmlRange r(p, p->ltail());
232 if(tclass_ == Html::A)
234 QString href = p->attribute("href");
235 if(!href.isEmpty()) p->set_attribute("href", create_href(href));
243 result = p->ltail()->lnext();
244 p->lparent()->lremove(p->lbegin(), p->ltail());
250 * \brief Aタグのhreh属性を都合よく作り直して返します。
251 * \param href 元のhref。
254 QString TM::ProxyHandler::create_href(QString href)
256 if(href.startsWith("mailto:"));
257 else if(href.startsWith("https:"));
258 else if(href.startsWith("ftp:"));
259 else if(href.startsWith("http://"))
260 href.remove(0, 7).insert(0, host() + prefix());
261 else if(href.startsWith("//"))
262 href.remove(0, 2).insert(0, host() + prefix());
263 else if(href.startsWith("/"))
264 href = host() + prefix() + m_reply->url().host() + href;
265 else href = host() + prefix()
266 + m_reply->url().resolved(QUrl(href)).toString().remove(0, 7);
271 * \brief パラグラフと見做せる範囲を探し、SPANで囲みます。
272 * \param begin_ 探索範囲の始点。
273 * \param tail_ 探索範囲の終点。
275 void TM::ProxyHandler::create_paragraph(Html::pointer begin_, Html::pointer tail_)
279 Html::pointer skipper;
281 Html::pointer p = begin_, p1, p2;
287 if(p->type() == Html::Text && !(p->tinfo() & Html::Whitespace)) {
291 else if(p->tcategory() & Html::Ignore) {
297 if(p->type() == Html::Text && !(p->tinfo() & Html::Whitespace)) {
301 else if(p->tcategory() & Html::Ignore) {
305 else if(p->tcategory() & Html::Splitter) state = 0;
307 range = HtmlRange(p1, p2);
309 range.wrap("span")->set_attribute(
310 "data-wordring-segment", QString::number(count++)).set_attribute(
311 "class", "wordring-segment");
314 if(p == skipper->ltail()) state = 0;
322 * \brief ブラウザとの通信に使うスキーム、ホスト名、ポート番号までを返します。
325 * 例)http://localhost:80
327 QString TM::ProxyHandler::host() const
329 return QString("http://localhost:") + QString::number(m_context->http_port());
333 * \brief ブラウザとの通信に使う接頭辞を返します。
338 QString TM::ProxyHandler::prefix() const { return m_context->prefix(); }
340 // DefaultHtmlModule -----------------------------------------------------------------
342 TM::DefaultHtmlModule::DefaultHtmlModule(Settings *settings, quint16 port, QObject *parent)
344 , m_settings(settings)
347 QString path = settings->value("DefaultHtmlModule/file").toString();
348 QString prefix = settings->value("ProxyModule/prefix").toString();
351 file.open(QIODevice::ReadOnly);
352 QByteArray ba = file.readAll();
355 HtmlDocument document(ba);
358 HtmlNode html = document.first("html");
359 HtmlNode head = html.first("head");
362 // Tidyが書き込むmetaタグを除去
363 head.remove(head.first("meta"));
365 node = head.insert("script", head.end()).set_attribute("type", "text/javascript");
366 QString js = "\nwindow.wordring.port=";
367 js += QString::number(port) + ";\n";
368 js += "window.wordring.prefix='";
369 js += prefix + "';\n";
370 node.insert_comment(js, node.end());
372 m_html = document.to_byte_array();
375 TM::DefaultHtmlModule::~DefaultHtmlModule()
379 HttpHandler* TM::DefaultHtmlModule::create_handler(QByteArray method, QByteArray url)
381 if(method.toLower() != "get") return nullptr;
382 if(url != "/") return nullptr;
384 HttpHandler *handler = new DefaultHtmlHandler(m_settings, m_port, m_html, this);
388 void TM::DefaultHtmlModule::destroy_handler(HttpHandler *handler)
393 // DefaultHtmlHandler ----------------------------------------------------------------
395 TM::DefaultHtmlHandler::DefaultHtmlHandler(
396 Settings *settings, quint16 port, QByteArray html_, DefaultHtmlModule *module)
397 : HttpHandler(module)
398 , m_settings(settings)
402 QString path = settings->value("DefaultHtmlModule/file").toString();
403 QString prefix = settings->value("ProxyModule/prefix").toString();
406 file.open(QIODevice::ReadOnly);
407 QByteArray ba = file.readAll();
410 HtmlDocument document(ba);
413 HtmlNode html = document.first("html");
414 HtmlNode head = html.first("head");
417 // Tidyが書き込むmetaタグを除去
418 head.remove(head.first("meta"));
420 node = head.insert("script", head.end()).set_attribute("type", "text/javascript");
421 QString js = "\nwindow.wordring.port=";
422 js += QString::number(port) + ";\n";
423 js += "window.wordring.prefix='";
424 js += prefix + "';\n";
425 node.insert_comment(js, node.end());
427 m_html = document.to_byte_array();
430 TM::DefaultHtmlHandler::~DefaultHtmlHandler()
435 int TM::DefaultHtmlHandler::run()
437 m_response.set_attribute("Content-Type", "text/html");
438 m_response.set_attribute("Transfer-Encoding", "chunked");