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);
30 assert(settings->contains("ProxyModule/prefix"));
31 m_prefix = settings->value("ProxyModule/prefix").toString();
33 assert(settings->contains("ProxyHandler/js_file"));
34 m_js_file = settings->value("ProxyHandler/js_file").toString();
36 assert(settings->contains("ProxyHandler/css_file"));
37 m_css_file = settings->value("ProxyHandler/css_file").toString();
40 QNetworkAccessManager* TM::ProxyContext::network_access_manager()
45 quint16 TM::ProxyContext::http_port() const { return m_http_port; }
47 quint16 TM::ProxyContext::socket_port() const { return m_socket_port; }
49 QString TM::ProxyContext::prefix() const { return m_prefix; }
51 QString TM::ProxyContext::js_file() const { return m_js_file; }
53 QString TM::ProxyContext::css_file() const { return m_css_file; }
55 // ProxyModule ----------------------------------------------------------------
57 TM::ProxyModule::ProxyModule(Settings *settings, quint16 http, quint16 socket,
60 , m_thread(new QThread(this))
61 , m_context(new ProxyContext(settings, http, socket))
63 m_context->moveToThread(m_thread);
64 connect(m_thread, SIGNAL(finished()), m_context, SLOT(deleteLater()));
69 TM::ProxyModule::~ProxyModule()
75 HttpHandler* TM::ProxyModule::create_handler(QByteArray method, QByteArray url)
77 if(method.toLower() != "get") return nullptr;
78 if(!url.toLower().startsWith(m_context->prefix().toUtf8())) return nullptr;
80 HttpHandler *handler = new ProxyHandler(m_context);
81 handler->moveToThread(m_thread);
85 void TM::ProxyModule::destroy_handler(HttpHandler *handler)
87 QMetaObject::invokeMethod(handler, "deleteLater");
90 // ProxyHandler ---------------------------------------------------------------
92 TM::ProxyHandler::ProxyHandler(ProxyContext *context)
99 TM::ProxyHandler::~ProxyHandler()
101 if(m_reply) m_reply->deleteLater();
104 int TM::ProxyHandler::run()
106 QByteArray url_string =
107 m_request->url().remove(0, m_context->prefix().toUtf8().length());
108 url_string = QUrl::fromPercentEncoding(url_string).toUtf8();
109 m_targetUrl = QUrl::fromUserInput(url_string);
110 qDebug() << m_targetUrl;
113 m_reply = m_context->network_access_manager()->get(QNetworkRequest(m_targetUrl));
114 connect(m_reply, SIGNAL(finished()), this, SLOT(onNetworkReplyFinished()));
118 void TM::ProxyHandler::onNetworkReplyFinished()
123 int status = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
129 case 301: // リダイレクト先をブラウザにキャッシュさせないため302に固定。
139 * \brief ブラウザに302を返しリダイレクトさせます。
142 int TM::ProxyHandler::redirect()
145 QUrl url = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
146 if(url.scheme() != "http") return code_502();
150 host() + prefix() + url.toString().remove(0, 2); // スキーム(http:)を除いた後の//を取り除く
152 return code_302(QUrl(redirect));
159 int TM::ProxyHandler::response()
164 QByteArray bytes = m_reply->readAll();
165 if(bytes.isEmpty()) return code_502();
167 HtmlDocument document(bytes);
168 if(!document) return code_502();
170 HtmlNode html = document.first("html");
171 HtmlNode head = html.first("head");
172 HtmlNode body = html.first("body");
175 // Tidyが書き込むmetaタグを除去
176 head.remove(head.first("meta"));
178 for(Html::pointer p = html.lbegin(); p; p = sanitize(p));
179 // baseを設定(元のHTMLに設定されている場合、書き換えない)
180 HtmlNode base = head.first("base");
181 if(base.type() == HtmlNode::Null)
183 base = head.insert("base", head.begin());
184 base.set_attribute("href", m_targetUrl.toString());
187 node = head.insert("style", head.end());
188 node.insert_text(" .wordring-segment:hover{ color:#ff0000; } ", node.end());
191 QFile js_file(m_context->js_file());
192 js_file.open(QIODevice::ReadOnly);
193 assert(js_file.isOpen());
194 QByteArray js = js_file.readAll();
196 node = head.insert("script", head.end()).set_attribute("type", "text/javascript");
197 node.insert_comment(QString::fromUtf8(js), node.end());
200 QFile css_file(m_context->css_file());
201 css_file.open(QIODevice::ReadOnly);
202 assert(css_file.isOpen());
203 QByteArray css = css_file.readAll();
205 node = head.insert("style", head.end()).set_attribute("type", "text/css");
206 node.insert_text(QString::fromUtf8(css), node.end());
210 node = head.insert("script", head.end()).set_attribute("type", "text/javascript");
211 QString str1 = "\nwindow.wordring.port=";
212 str1 += QString::number(m_context->socket_port()) + ";\n";
213 str1 += "window.wordring.url='";
214 str1 += m_targetUrl.toString() + "';\n";
215 node.insert_comment(str1, node.end());
217 create_paragraph(body.lbegin(), body.ltail());
219 m_response.set_attribute("Content-Type", "text/html");
220 m_response.set_attribute("Transfer-Encoding", "chunked");
222 write(document.to_byte_array());
223 write(output.toUtf8());
230 * \brief 与えられたノードpを綺麗にします。
235 * - noscript要素をunwrapします。
238 Html::pointer TM::ProxyHandler::sanitize(Html::pointer p)
240 bool remove_ = false;
241 Html::pointer result = p->lnext();
243 if(p->type() == Html::Element && p->place() == Html::Open)
245 int tclass_ = p->tclass();
247 if(tclass_ == Html::Script) remove_ = true;
248 else if(tclass_ == Html::Noscript)
250 HtmlRange r(p, p->ltail());
255 if(tclass_ == Html::A)
257 QString href = p->attribute("href");
258 if(!href.isEmpty()) p->set_attribute("href", create_href(href));
266 result = p->ltail()->lnext();
267 p->lparent()->lremove(p->lbegin(), p->ltail());
273 * \brief Aタグのhreh属性を都合よく作り直して返します。
274 * \param href 元のhref。
277 QString TM::ProxyHandler::create_href(QString href)
279 if(href.startsWith("mailto:"));
280 else if(href.startsWith("https:"));
281 else if(href.startsWith("ftp:"));
282 else if(href.startsWith("http://"))
283 href.remove(0, 7).insert(0, host() + prefix());
284 else if(href.startsWith("//"))
285 href.remove(0, 2).insert(0, host() + prefix());
286 else if(href.startsWith("/"))
287 href = host() + prefix() + m_reply->url().host() + href;
288 else href = host() + prefix()
289 + m_reply->url().resolved(QUrl(href)).toString().remove(0, 7);
294 * \brief パラグラフと見做せる範囲を探し、SPANで囲みます。
295 * \param begin_ 探索範囲の始点。
296 * \param tail_ 探索範囲の終点。
298 void TM::ProxyHandler::create_paragraph(Html::pointer begin_, Html::pointer tail_)
302 Html::pointer skipper;
304 Html::pointer p = begin_, p1, p2;
310 if(p->type() == Html::Text && !(p->tinfo() & Html::Whitespace)) {
314 else if(p->tcategory() & Html::Ignore) {
320 if(p->type() == Html::Text && !(p->tinfo() & Html::Whitespace)) {
324 else if(p->tcategory() & Html::Ignore) {
328 else if(p->tcategory() & Html::Splitter) state = 0;
330 range = HtmlRange(p1, p2);
332 range.wrap("span")->set_attribute(
333 "data-wordring-segment", QString::number(count++)).set_attribute(
334 "class", "wordring-segment");
337 if(p == skipper->ltail()) state = 0;
345 * \brief ブラウザとの通信に使うスキーム、ホスト名、ポート番号までを返します。
348 * 例)http://localhost:80
350 QString TM::ProxyHandler::host() const
352 return QString("http://localhost:") + QString::number(m_context->http_port());
356 * \brief ブラウザとの通信に使う接頭辞を返します。
361 QString TM::ProxyHandler::prefix() const { return m_context->prefix(); }
363 // DefaultHtmlModule -----------------------------------------------------------------
365 TM::DefaultHtmlModule::DefaultHtmlModule(Settings *settings, quint16 port, QObject *parent)
367 , m_settings(settings)
370 QString path = settings->value("DefaultHtmlModule/file").toString();
371 QString prefix = settings->value("ProxyModule/prefix").toString();
374 file.open(QIODevice::ReadOnly);
375 QByteArray ba = file.readAll();
378 HtmlDocument document(ba);
381 HtmlNode html = document.first("html");
382 HtmlNode head = html.first("head");
385 // Tidyが書き込むmetaタグを除去
386 head.remove(head.first("meta"));
388 node = head.insert("script", head.end()).set_attribute("type", "text/javascript");
389 QString js = "\nwindow.wordring.port=";
390 js += QString::number(port) + ";\n";
391 js += "window.wordring.prefix='";
392 js += prefix + "';\n";
393 node.insert_comment(js, node.end());
395 m_html = document.to_byte_array();
398 TM::DefaultHtmlModule::~DefaultHtmlModule()
402 HttpHandler* TM::DefaultHtmlModule::create_handler(QByteArray method, QByteArray url)
404 if(method.toLower() != "get") return nullptr;
405 if(url != "/") return nullptr;
407 HttpHandler *handler = new DefaultHtmlHandler(m_settings, m_port, m_html, this);
411 void TM::DefaultHtmlModule::destroy_handler(HttpHandler *handler)
416 // DefaultHtmlHandler ----------------------------------------------------------------
418 TM::DefaultHtmlHandler::DefaultHtmlHandler(
419 Settings *settings, quint16 port, QByteArray html_, DefaultHtmlModule *module)
420 : HttpHandler(module)
421 , m_settings(settings)
425 QString path = settings->value("DefaultHtmlModule/file").toString();
428 file.open(QIODevice::ReadOnly);
429 QByteArray ba = file.readAll();
432 HtmlDocument document(ba);
435 HtmlNode html = document.first("html");
436 HtmlNode head = html.first("head");
438 // Tidyが書き込むmetaタグを除去
439 head.remove(head.first("meta"));
441 m_html = document.to_byte_array();
444 TM::DefaultHtmlHandler::~DefaultHtmlHandler()
449 int TM::DefaultHtmlHandler::run()
451 m_response.set_attribute("Content-Type", "text/html");
452 m_response.set_attribute("Transfer-Encoding", "chunked");