You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by bc...@apache.org on 2016/02/10 02:23:07 UTC
[2/2] trafficserver git commit: TS-4132: Open source Yahoo's
ats-inliner plug-in
TS-4132: Open source Yahoo's ats-inliner plug-in
This closes #459
Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/7b8417cc
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/7b8417cc
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/7b8417cc
Branch: refs/heads/master
Commit: 7b8417ccef5133125dc41c721738ca5edfb12e71
Parents: 8c38c6c
Author: Daniel Vitor Morilha <dm...@yahoo-inc.com>
Authored: Tue Feb 9 17:22:28 2016 -0800
Committer: Bryan Call <bc...@apache.org>
Committed: Tue Feb 9 17:22:28 2016 -0800
----------------------------------------------------------------------
NOTICE | 3 +
configure.ac | 1 +
plugins/experimental/Makefile.am | 1 +
plugins/experimental/inliner/Makefile.am | 33 ++
plugins/experimental/inliner/README | 29 ++
plugins/experimental/inliner/ats-inliner.cc | 217 +++++++++
plugins/experimental/inliner/cache-handler.h | 337 +++++++++++++
plugins/experimental/inliner/cache.cc | 80 ++++
plugins/experimental/inliner/cache.h | 123 +++++
plugins/experimental/inliner/chunk-decoder.cc | 177 +++++++
plugins/experimental/inliner/chunk-decoder.h | 86 ++++
plugins/experimental/inliner/fetcher.cc | 84 ++++
plugins/experimental/inliner/fetcher.h | 325 +++++++++++++
plugins/experimental/inliner/gif.h | 50 ++
plugins/experimental/inliner/html-parser.cc | 330 +++++++++++++
plugins/experimental/inliner/html-parser.h | 146 ++++++
plugins/experimental/inliner/inliner-handler.cc | 149 ++++++
plugins/experimental/inliner/inliner-handler.h | 70 +++
plugins/experimental/inliner/jpeg.h | 47 ++
plugins/experimental/inliner/png.h | 124 +++++
plugins/experimental/inliner/ts.cc | 471 +++++++++++++++++++
plugins/experimental/inliner/ts.h | 319 +++++++++++++
plugins/experimental/inliner/util.h | 43 ++
plugins/experimental/inliner/vconnection.h | 105 +++++
24 files changed, 3350 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/NOTICE
----------------------------------------------------------------------
diff --git a/NOTICE b/NOTICE
index 32678be..15d94b6 100644
--- a/NOTICE
+++ b/NOTICE
@@ -78,6 +78,9 @@ Copyright (C) 2014 Yahoo! Inc. All rights reserved.
multiplexer: Plugin for request multiplixing
Copyright (C) 2015 Yahoo! Inc. All rights reserved.
+inliner: Plugin for inlining base64 images
+Copyright (C) 2016 Yahoo! Inc. All rights reserved.
+
~~~
healthchecks: Plugin for ATS healthchecks.
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/configure.ac
----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 58adee7..aa2b805 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1920,6 +1920,7 @@ AS_IF([test "x$enable_experimental_plugins" = "xyes"], [
plugins/experimental/geoip_acl/Makefile
plugins/experimental/header_normalize/Makefile
plugins/experimental/hipes/Makefile
+ plugins/experimental/inliner/Makefile
plugins/experimental/memcache/Makefile
plugins/experimental/memcached_remap/Makefile
plugins/experimental/metalink/Makefile
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/Makefile.am b/plugins/experimental/Makefile.am
index a3252a4..c5055ae 100644
--- a/plugins/experimental/Makefile.am
+++ b/plugins/experimental/Makefile.am
@@ -31,6 +31,7 @@ SUBDIRS = \
geoip_acl \
header_normalize \
hipes \
+ inliner \
metalink \
multiplexer \
memcache \
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/Makefile.am b/plugins/experimental/inliner/Makefile.am
new file mode 100644
index 0000000..1f96240
--- /dev/null
+++ b/plugins/experimental/inliner/Makefile.am
@@ -0,0 +1,33 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+include $(top_srcdir)/build/plugins.mk
+
+AM_CPPFLAGS += -DPLUGIN_TAG=\"inliner\" -std=c++11
+
+pkglib_LTLIBRARIES = inliner.la
+
+inliner_la_SOURCES = \
+ ats-inliner.cc \
+ cache.cc \
+ chunk-decoder.cc \
+ fetcher.cc \
+ html-parser.cc \
+ inliner-handler.cc \
+ ts.cc
+
+inliner_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS)
+
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/README
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/README b/plugins/experimental/inliner/README
new file mode 100644
index 0000000..6c5eea7
--- /dev/null
+++ b/plugins/experimental/inliner/README
@@ -0,0 +1,29 @@
+ATS (Apache Traffic Server) Inliner plug-in
+-----------------------------------------------
+
+This is a global transformation plug-in that inlines base64 images on text/html responses.
+
+Inliner:
+ 1. Detects the response to have header "Content-Type" set to "text/html".
+ 2. Without impacting streaming, scans the mark-up looking for "src" attributes
+ into "img" tags.
+ 3. For each detected http/https url, checks for a "#inline" at its end.
+ 4. Checks on ATS cache if the url exists.
+ 5. In case the url exists into the cache. Inliner replaces the image with a 1x1
+ base64 pixel and starts retrieving the content from the cache.
+ 6. Once the image is retrieved, buffers its content into memory.
+ 7. At the end of the orginal document, Inliner outputs a little JavaScript
+ snippet.
+ 8. Inliner outputs every image found into the cache into a JavaScript function
+ call.
+ 9. Once the JavaScript gets executed by the browser, the 1x1 pixels
+ are replaced by their actual content.
+ 10. For the images which do not exist into the cache, Inliner preserve their
+ original url and retrieves their content into background, also converting
+ them to their base64 representation.
+
+Please use "inliner" tag for debugging purposes.
+
+Please add the inliner library into ATS plugins.conf configuration file.
+
+No special configurations are required
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/ats-inliner.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/ats-inliner.cc b/plugins/experimental/inliner/ats-inliner.cc
new file mode 100644
index 0000000..773607b
--- /dev/null
+++ b/plugins/experimental/inliner/ats-inliner.cc
@@ -0,0 +1,217 @@
+/** @file
+
+ Inlines base64 images from the ATS cache
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+#include <assert.h>
+#include <cstring>
+#include <dlfcn.h>
+#include <inttypes.h>
+#include <limits>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "inliner-handler.h"
+#include "ts.h"
+
+#ifndef PLUGIN_TAG
+#error Please define a PLUGIN_TAG before including this file.
+#endif
+
+// disable timeout for now
+const size_t timeout = 0;
+
+struct MyData {
+ ats::inliner::Handler handler;
+
+ MyData(const TSIOBufferReader r, const TSVConn v)
+ : handler(r, ats::io::IOSink::Create(TSTransformOutputVConnGet(v), TSContMutexGet(v), timeout))
+ {
+ assert(r != nullptr);
+ assert(v != nullptr);
+ }
+};
+
+void
+handle_transform(const TSCont c)
+{
+ const TSVIO vio = TSVConnWriteVIOGet(c);
+
+ MyData *const data = static_cast<MyData *>(TSContDataGet(c));
+
+ if (!TSVIOBufferGet(vio)) {
+ TSVConnShutdown(c, 1, 0);
+ TSContDataSet(c, nullptr);
+ delete data;
+ return;
+ }
+
+ auto todo = TSVIONTodoGet(vio);
+
+ if (todo > 0) {
+ const TSIOBufferReader reader = TSVIOReaderGet(vio);
+ todo = std::min(todo, TSIOBufferReaderAvail(reader));
+
+ if (todo > 0) {
+ if (!data) {
+ const_cast<MyData *&>(data) = new MyData(TSVIOReaderGet(vio), c);
+ TSContDataSet(c, data);
+ }
+
+ data->handler.parse();
+
+ TSIOBufferReaderConsume(reader, todo);
+ TSVIONDoneSet(vio, TSVIONDoneGet(vio) + todo);
+ }
+ }
+
+ if (TSVIONTodoGet(vio) > 0) {
+ if (todo > 0) {
+ TSContCall(TSVIOContGet(vio), TS_EVENT_VCONN_WRITE_READY, vio);
+ }
+ } else {
+ TSContCall(TSVIOContGet(vio), TS_EVENT_VCONN_WRITE_COMPLETE, vio);
+ TSVConnShutdown(c, 1, 0);
+ TSContDataSet(c, nullptr);
+ delete data;
+ }
+}
+
+int
+null_transform(TSCont c, TSEvent e, void *)
+{
+ if (TSVConnClosedGet(c)) {
+ TSDebug(PLUGIN_TAG, "connection closed");
+ MyData *const data = static_cast<MyData *>(TSContDataGet(c));
+ if (data != nullptr) {
+ TSContDataSet(c, nullptr);
+ data->handler.abort();
+ delete data;
+ }
+ TSContDestroy(c);
+ } else {
+ switch (e) {
+ case TS_EVENT_ERROR: {
+ const TSVIO vio = TSVConnWriteVIOGet(c);
+ assert(vio != nullptr);
+ TSContCall(TSVIOContGet(vio), TS_EVENT_ERROR, vio);
+ } break;
+
+ case TS_EVENT_IMMEDIATE:
+ handle_transform(c);
+ break;
+
+ default:
+ TSError("[" PLUGIN_TAG "] Unknown event: %i", e);
+ assert(false); // UNREACHABLE
+ }
+ }
+
+ return 0;
+}
+
+bool
+transformable(TSHttpTxn txnp)
+{
+ bool returnValue;
+ TSMBuffer buffer;
+ TSMLoc location;
+ CHECK(TSHttpTxnServerRespGet(txnp, &buffer, &location));
+ assert(buffer != nullptr);
+ assert(location != nullptr);
+
+ returnValue = TSHttpHdrStatusGet(buffer, location) == TS_HTTP_STATUS_OK;
+
+ if (returnValue) {
+ returnValue = false;
+ const TSMLoc field = TSMimeHdrFieldFind(buffer, location, TS_MIME_FIELD_CONTENT_TYPE, TS_MIME_LEN_CONTENT_TYPE);
+
+ if (field != TS_NULL_MLOC) {
+ int length = 0;
+ const char *const content = TSMimeHdrFieldValueStringGet(buffer, location, field, 0, &length);
+
+ if (content != nullptr && length > 0) {
+ returnValue = strncasecmp(content, "text/html", 9) == 0;
+ }
+
+ TSHandleMLocRelease(buffer, location, field);
+ }
+ }
+
+ CHECK(TSHandleMLocRelease(buffer, TS_NULL_MLOC, location));
+
+ returnValue &= TSHttpTxnIsInternal(txnp) != TS_SUCCESS;
+ return returnValue;
+}
+
+void
+transform_add(const TSHttpTxn t)
+{
+ assert(t != nullptr);
+ const TSVConn vconnection = TSTransformCreate(null_transform, t);
+ assert(vconnection != nullptr);
+ TSHttpTxnHookAdd(t, TS_HTTP_RESPONSE_TRANSFORM_HOOK, vconnection);
+}
+
+int
+transform_plugin(TSCont, TSEvent e, void *d)
+{
+ assert(TS_EVENT_HTTP_READ_RESPONSE_HDR == e);
+ assert(d != nullptr);
+
+ const TSHttpTxn transaction = static_cast<TSHttpTxn>(d);
+
+ switch (e) {
+ case TS_EVENT_HTTP_READ_RESPONSE_HDR:
+ if (transformable(transaction)) {
+ transform_add(transaction);
+ }
+
+ TSHttpTxnReenable(transaction, TS_EVENT_HTTP_CONTINUE);
+ break;
+
+ default:
+ assert(false); // UNRECHEABLE
+ break;
+ }
+
+ return TS_SUCCESS;
+}
+
+void
+TSPluginInit(int, const char **)
+{
+ TSPluginRegistrationInfo info;
+
+ info.plugin_name = const_cast<char *>(PLUGIN_TAG);
+ info.vendor_name = const_cast<char *>("MyCompany");
+ info.support_email = const_cast<char *>("ts-api-support@MyCompany.com");
+
+ if (TSPluginRegister(&info) != TS_SUCCESS) {
+ TSError("[" PLUGIN_TAG "] Plugin registration failed.\n");
+ goto error;
+ }
+
+ TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK, TSContCreate(transform_plugin, nullptr));
+ return;
+
+error:
+ TSError("[null-tranform] Unable to initialize plugin (disabled).\n");
+}
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/cache-handler.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/cache-handler.h b/plugins/experimental/inliner/cache-handler.h
new file mode 100644
index 0000000..97a3784
--- /dev/null
+++ b/plugins/experimental/inliner/cache-handler.h
@@ -0,0 +1,337 @@
+/** @file
+
+ Inlines base64 images from the ATS cache
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+#ifndef CACHE_HANDLER_H
+#define CACHE_HANDLER_H
+
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <assert.h>
+
+#include "cache.h"
+#include "fetcher.h"
+#include "gif.h"
+#include "jpeg.h"
+#include "png.h"
+#include "ts.h"
+#include "vconnection.h"
+
+#ifndef PLUGIN_TAG
+#error Please define a PLUGIN_TAG before including this file.
+#endif
+
+#define ONE_PIXEL "data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
+
+#define VERSION "&version=1"
+
+namespace ats
+{
+bool
+getHeader(TSMBuffer buffer, TSMLoc location, const std::string &name, std::string &value)
+{
+ bool result = false;
+ const TSMLoc field = TSMimeHdrFieldFind(buffer, location, name.c_str(), name.size());
+ if (field != nullptr) {
+ int length = 0;
+ const char *const content = TSMimeHdrFieldValueStringGet(buffer, location, field, -1, &length);
+ if (content != nullptr && length > 0) {
+ value = std::string(content, length);
+ result = true;
+ }
+ TSHandleMLocRelease(buffer, location, field);
+ }
+ return result;
+}
+
+namespace inliner
+{
+ struct AnotherClass {
+ util::Buffer content_;
+ std::string contentType_;
+ const std::string url_;
+
+ AnotherClass(const std::string &u) : url_(u) {}
+ int64_t
+ data(const TSIOBufferReader r, int64_t l)
+ {
+ assert(r != nullptr);
+ TSIOBufferBlock block = TSIOBufferReaderStart(r);
+
+ assert(l >= 0);
+ if (l == 0) {
+ l = TSIOBufferReaderAvail(r);
+ assert(l >= 0);
+ }
+
+ int64_t length = 0;
+
+ for (; block && l > 0; block = TSIOBufferBlockNext(block)) {
+ int64_t size = 0;
+ const char *const pointer = TSIOBufferBlockReadStart(block, r, &size);
+ if (pointer != nullptr && size > 0) {
+ size = std::min(size, l);
+ std::copy(pointer, pointer + size, std::back_inserter(content_));
+ length += size;
+ l -= size;
+ }
+ }
+
+ return length;
+ }
+
+ void
+ done(void)
+ {
+ if (GIF::verifySignature(content_)) {
+ contentType_ = "image/gif";
+ } else if (JPEG::verifySignature(content_)) {
+ contentType_ = "image/jpeg";
+ } else if (PNG::verifySignature(content_)) {
+ contentType_ = "image/png";
+ } else {
+ // TODO(dmorilha): check png signature code.
+ TSDebug(PLUGIN_TAG, "Invalid signature for: %s", url_.c_str());
+ }
+
+ if (contentType_ != "image/gif" && contentType_ != "image/jpeg" && contentType_ != "image/jpg" &&
+ contentType_ != "image/png") {
+ return;
+ }
+
+ if (!contentType_.empty() && !content_.empty()) {
+ std::string output;
+ output.reserve(content_.size() * 5);
+ output += "data:";
+ output += contentType_;
+ output += ";base64,";
+
+ {
+ const int64_t s = output.size();
+ size_t size = 0;
+ output.resize(content_.size() * 5);
+ CHECK(TSBase64Encode(content_.data(), content_.size(), const_cast<char *>(output.data()) + s, output.size() - s, &size));
+ output.resize(size + s);
+ }
+
+ TSDebug(PLUGIN_TAG, "%s (%s) %lu %lu", url_.c_str(), contentType_.c_str(), content_.size(), output.size());
+
+ cache::write(url_ + VERSION, std::move(output));
+ }
+ }
+
+ void
+ header(TSMBuffer b, TSMLoc l)
+ {
+ if (!getHeader(b, l, "Content-Type", contentType_)) {
+ getHeader(b, l, "content-type", contentType_);
+ }
+
+ {
+ std::string contentLengthValue;
+
+ if (!getHeader(b, l, "Content-Length", contentLengthValue)) {
+ getHeader(b, l, "content-length", contentLengthValue);
+ }
+
+ if (!contentLengthValue.empty()) {
+ std::stringstream ss(contentLengthValue);
+ uint32_t contentLength = 0;
+ ss >> contentLength;
+ TSDebug(PLUGIN_TAG, "Content-Length: %i", contentLength);
+ content_.reserve(contentLength);
+ }
+ }
+ }
+
+ void
+ timeout(void) const
+ {
+ TSDebug(PLUGIN_TAG, "Fetch timeout");
+ }
+
+ void
+ error(void) const
+ {
+ TSDebug(PLUGIN_TAG, "Fetch error");
+ }
+ };
+
+ uint64_t
+ read(const TSIOBufferReader &r, std::string &o, int64_t l = 0)
+ {
+ assert(r != nullptr);
+ TSIOBufferBlock block = TSIOBufferReaderStart(r);
+
+ assert(l >= 0);
+ if (l == 0) {
+ l = TSIOBufferReaderAvail(r);
+ assert(l >= 0);
+ }
+
+ uint64_t length = 0;
+
+ for (; block && l > 0; block = TSIOBufferBlockNext(block)) {
+ int64_t size = 0;
+ const char *const pointer = TSIOBufferBlockReadStart(block, r, &size);
+ if (pointer != nullptr && size > 0) {
+ size = std::min(size, l);
+ o.append(pointer, size);
+ length += size;
+ l -= size;
+ }
+ }
+
+ return length;
+ }
+
+ struct CacheHandler {
+ std::string src_;
+ std::string original_;
+ std::string classes_;
+ std::string id_;
+ io::SinkPointer sink_;
+ io::SinkPointer sink2_;
+ TSIOBufferReader reader_;
+
+ ~CacheHandler()
+ {
+ if (reader_ != nullptr) {
+ TSIOBufferReaderConsume(reader_, TSIOBufferReaderAvail(reader_));
+ assert(TSIOBufferReaderAvail(reader_) == 0);
+ TSIOBufferReaderFree(reader_);
+ reader_ = nullptr;
+ }
+ }
+
+ template <class T1, class T2>
+ CacheHandler(const std::string &s, const std::string &o, const std::string c, const std::string &i, T1 &&si, T2 &&si2)
+ : src_(s), original_(o), classes_(c), id_(i), sink_(std::forward<T1>(si)), sink2_(std::forward<T2>(si2)), reader_(nullptr)
+ {
+ assert(sink_ != nullptr);
+ assert(sink2_ != nullptr);
+ }
+
+ CacheHandler(CacheHandler &&h)
+ : src_(std::move(h.src_)), original_(std::move(h.original_)), classes_(std::move(h.classes_)), id_(std::move(h.id_)),
+ sink_(std::move(h.sink_)), sink2_(std::move(h.sink2_)), reader_(h.reader_)
+ {
+ h.reader_ = nullptr;
+ }
+
+ CacheHandler(const CacheHandler &) = delete;
+ CacheHandler &operator=(const CacheHandler &) = delete;
+
+ void
+ done(void)
+ {
+ assert(reader_ != nullptr);
+ assert(sink2_ != nullptr);
+ std::string o;
+ read(reader_, o);
+ o = "<script>h(\"" + id_ + "\",\"" + o + "\");</script>";
+ *sink2_ << o;
+ }
+
+ void
+ data(TSIOBufferReader r)
+ {
+ // TODO(dmorilha): API maybe address as a different single event?
+ if (reader_ == nullptr) {
+ reader_ = TSIOBufferReaderClone(r);
+ }
+ }
+
+ void
+ hit(TSVConn v)
+ {
+ assert(v != nullptr);
+
+ TSDebug(PLUGIN_TAG, "cache hit for %s (%" PRId64 " bytes)", src_.c_str(), TSVConnCacheObjectSizeGet(v));
+
+ assert(sink_);
+
+ *sink_ << original_;
+ *sink_ << "src=\"" ONE_PIXEL "\" ";
+ assert(!id_.empty());
+ *sink_ << "class=\"" << id_;
+ if (!classes_.empty()) {
+ *sink_ << " " << classes_;
+ }
+ *sink_ << "\" ";
+
+ sink_.reset();
+
+ io::vconnection::read(v, std::move(*this), TSVConnCacheObjectSizeGet(v));
+ }
+
+ void
+ miss(void)
+ {
+ assert(sink_ != nullptr);
+ *sink_ << original_;
+ if (!src_.empty()) {
+ *sink_ << "src=\"" << src_ << "\" ";
+ }
+ if (!classes_.empty()) {
+ *sink_ << "class=\"" << classes_ << "\" ";
+ }
+ sink_.reset();
+
+ assert(sink2_ != nullptr);
+ sink2_.reset();
+
+ const std::string b;
+
+ {
+ const size_t DOUBLE_SLASH = src_.find("//");
+ if (DOUBLE_SLASH != std::string::npos) {
+ const_cast<std::string &>(b) = src_.substr(DOUBLE_SLASH + 2);
+ } else {
+ const_cast<std::string &>(b) = src_;
+ }
+ }
+
+ const std::string::const_iterator b1 = b.begin(), b2 = b.end(), i = std::find(b1, b2, '/');
+
+ std::string request = "GET ";
+ request += std::string(i, b2);
+ request += " HTTP/1.1\r\n";
+ request += "Host: ";
+ request += std::string(b1, i);
+ request += "\r\n\r\n";
+
+ ats::io::IO *const io = new io::IO();
+
+ TSDebug(PLUGIN_TAG, "request:\n%s", request.c_str());
+
+ ats::get(io, io->copy(request), AnotherClass(src_));
+ }
+ };
+
+} // end of inliner namespace
+} // end of ats namespace
+
+#undef ONE_PIXEL
+
+#endif // CACHE_HANDLER_H
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/cache.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/cache.cc b/plugins/experimental/inliner/cache.cc
new file mode 100644
index 0000000..cb6ad6f
--- /dev/null
+++ b/plugins/experimental/inliner/cache.cc
@@ -0,0 +1,80 @@
+/** @file
+
+ Inlines base64 images from the ATS cache
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+#include "cache.h"
+
+#ifndef PLUGIN_TAG
+#error Please define a PLUGIN_TAG before including this file.
+#endif
+
+namespace ats
+{
+namespace cache
+{
+ void
+ write(const std::string &k, std::string &&s)
+ {
+ Key key(k);
+ TSCont continuation = TSContCreate(Write::handle, nullptr);
+ assert(continuation != nullptr);
+ TSContDataSet(continuation, new Write(std::move(s)));
+ TSCacheWrite(continuation, key.key());
+ }
+
+ int
+ Write::handle(TSCont c, TSEvent e, void *v)
+ {
+ assert(c != nullptr);
+ Write *const self = static_cast<Write *>(TSContDataGet(c));
+ assert(self != nullptr);
+ switch (e) {
+ case TS_EVENT_CACHE_OPEN_WRITE:
+ assert(v != nullptr);
+ self->vconnection_ = static_cast<TSVConn>(v);
+ assert(self->out_ == nullptr);
+ self->out_ = io::IO::write(self->vconnection_, c, self->content_.size());
+ break;
+ case TS_EVENT_CACHE_OPEN_WRITE_FAILED:
+ TSDebug(PLUGIN_TAG, "write failed");
+ delete self;
+ TSContDataSet(c, nullptr);
+ TSContDestroy(c);
+ break;
+ case TS_EVENT_VCONN_WRITE_COMPLETE:
+ TSDebug(PLUGIN_TAG, "write completed");
+ assert(self->vconnection_ != nullptr);
+ TSVConnClose(self->vconnection_);
+ delete self;
+ TSContDataSet(c, nullptr);
+ TSContDestroy(c);
+ break;
+ case TS_EVENT_VCONN_WRITE_READY:
+ TSIOBufferWrite(self->out_->buffer, self->content_.data(), self->content_.size());
+ break;
+ default:
+ assert(false); // UNRECHEABLE.
+ break;
+ }
+ return 0;
+ }
+} // end of cache namespace
+} // end of ats namespace
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/cache.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/cache.h b/plugins/experimental/inliner/cache.h
new file mode 100644
index 0000000..c5c1133
--- /dev/null
+++ b/plugins/experimental/inliner/cache.h
@@ -0,0 +1,123 @@
+/** @file
+
+ Inlines base64 images from the ATS cache
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+#ifndef CACHE_H
+#define CACHE_H
+
+#include <assert.h>
+#include <string>
+
+#include "ts.h"
+#include "util.h"
+
+namespace ats
+{
+namespace cache
+{
+ struct Key {
+ ~Key()
+ {
+ assert(key_ != nullptr);
+ TSCacheKeyDestroy(key_);
+ }
+
+ Key(void) : key_(TSCacheKeyCreate()) { assert(key_ != nullptr); }
+ Key(const Key &) = delete;
+ Key &operator=(const Key &) = delete;
+
+ explicit Key(const std::string &s) : key_(TSCacheKeyCreate())
+ {
+ assert(key_ != nullptr);
+ CHECK(TSCacheKeyDigestSet(key_, s.c_str(), s.size()));
+ }
+
+ TSCacheKey
+ key(void) const
+ {
+ return key_;
+ }
+
+ TSCacheKey key_;
+ };
+
+ template <class T> struct Read {
+ typedef Read<T> Self;
+
+ T t_;
+
+ template <class... A> Read(A &&... a) : t_(std::forward<A>(a)...) {}
+ static int
+ handle(TSCont c, TSEvent e, void *d)
+ {
+ Self *const self = static_cast<Self *const>(TSContDataGet(c));
+ assert(self != nullptr);
+ switch (e) {
+ case TS_EVENT_CACHE_OPEN_READ:
+ assert(d != nullptr);
+ self->t_.hit(static_cast<TSVConn>(d));
+ break;
+ case TS_EVENT_CACHE_OPEN_READ_FAILED:
+ self->t_.miss();
+ break;
+ default:
+ assert(false); // UNRECHEABLE.
+ break;
+ }
+ delete self;
+ TSContDataSet(c, nullptr);
+ TSContDestroy(c);
+ return TS_SUCCESS;
+ }
+ };
+
+ template <class T, class... A>
+ void
+ fetch(const std::string &k, A &&... a)
+ {
+ const Key key(k);
+ const TSCont continuation = TSContCreate(Read<T>::handle, TSMutexCreate());
+ assert(continuation != nullptr);
+ TSContDataSet(continuation, new Read<T>(std::forward<A>(a)...));
+ TSCacheRead(continuation, key.key());
+ }
+
+ struct Write {
+ const std::string content_;
+ io::IO *out_;
+ TSVConn vconnection_;
+
+ ~Write()
+ {
+ if (out_ != nullptr) {
+ delete out_;
+ }
+ }
+
+ Write(std::string &&s) : content_(std::move(s)), out_(nullptr), vconnection_(nullptr) {}
+ static int handle(TSCont, TSEvent, void *);
+ };
+
+ void write(const std::string &, std::string &&);
+
+} // end of cache namespace
+} // end of ats namespace
+#endif // CACHE_H
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/chunk-decoder.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/chunk-decoder.cc b/plugins/experimental/inliner/chunk-decoder.cc
new file mode 100644
index 0000000..d9ad9b9
--- /dev/null
+++ b/plugins/experimental/inliner/chunk-decoder.cc
@@ -0,0 +1,177 @@
+/** @file
+
+ Inlines base64 images from the ATS cache
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+/** @file
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+#include <algorithm>
+#include <assert.h>
+#include <cstring>
+
+#include "chunk-decoder.h"
+
+void
+ChunkDecoder::parseSizeCharacter(const char a)
+{
+ assert(state_ == State::kSize);
+ if (a >= '0' && a <= '9') {
+ size_ = (size_ << 4) | (a - '0');
+ } else if (a >= 'A' && a <= 'F') {
+ size_ = (size_ << 4) | (a - 'A' + 10);
+ } else if (a >= 'a' && a <= 'f') {
+ size_ = (size_ << 4) | (a - 'a' + 10);
+ } else if (a == '\r') {
+ state_ = size_ == 0 ? State::kEndN : State::kDataN;
+ } else {
+ assert(false); // invalid input
+ }
+}
+
+int
+ChunkDecoder::parseSize(const char *p, const int64_t s)
+{
+ assert(p != NULL);
+ int length = 0;
+ while (state_ != State::kData && *p != '\0' && length < s) {
+ assert(state_ < State::kUpperBound); // VALID RANGE
+ switch (state_) {
+ case State::kData:
+ case State::kEnd:
+ case State::kInvalid:
+ case State::kUnknown:
+ case State::kUpperBound:
+ assert(false);
+ break;
+
+ case State::kDataN:
+ assert(*p == '\n');
+ state_ = (*p == '\n') ? State::kData : State::kInvalid;
+ break;
+
+ case State::kEndN:
+ assert(*p == '\n');
+ state_ = (*p == '\n') ? State::kEnd : State::kInvalid;
+ return length;
+
+ case State::kSizeR:
+ assert(*p == '\r');
+ state_ = (*p == '\r') ? State::kSizeN : State::kInvalid;
+ break;
+
+ case State::kSizeN:
+ assert(*p == '\n');
+ state_ = (*p == '\n') ? State::kSize : State::kInvalid;
+ break;
+
+ case State::kSize:
+ parseSizeCharacter(*p);
+ break;
+ }
+ ++length;
+ ++p;
+ assert(state_ != State::kInvalid);
+ }
+ return length;
+}
+
+bool
+ChunkDecoder::isSizeState(void) const
+{
+ return state_ == State::kDataN || state_ == State::kEndN || state_ == State::kSize || state_ == State::kSizeN ||
+ state_ == State::kSizeR;
+}
+
+int
+ChunkDecoder::decode(const TSIOBufferReader &r)
+{
+ assert(r != NULL);
+
+ if (state_ == State::kEnd) {
+ return 0;
+ }
+
+ {
+ const int l = TSIOBufferReaderAvail(r);
+ if (l < size_) {
+ size_ -= l;
+ return l;
+ }
+ }
+
+ int64_t size;
+ TSIOBufferBlock block = TSIOBufferReaderStart(r);
+
+ if (isSizeState()) {
+ while (block != NULL && size_ == 0) {
+ const char *p = TSIOBufferBlockReadStart(block, r, &size);
+ assert(p != NULL);
+ const int i = parseSize(p, size);
+ size -= i;
+ TSIOBufferReaderConsume(r, i);
+ if (state_ == State::kEnd) {
+ assert(size_ == 0);
+ return 0;
+ }
+ if (isSizeState()) {
+ assert(size == 0);
+ block = TSIOBufferBlockNext(block);
+ }
+ }
+ }
+
+ int length = 0;
+
+ while (block != NULL && state_ == State::kData) {
+ const char *p = TSIOBufferBlockReadStart(block, r, &size);
+ if (p != NULL) {
+ if (size > size_) {
+ length += size_;
+ size_ = 0;
+ state_ = State::kSizeR;
+ break;
+ } else {
+ length += size;
+ size_ -= size;
+ }
+ }
+ block = TSIOBufferBlockNext(block);
+ }
+
+ return length;
+}
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/chunk-decoder.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/chunk-decoder.h b/plugins/experimental/inliner/chunk-decoder.h
new file mode 100644
index 0000000..09cfddb
--- /dev/null
+++ b/plugins/experimental/inliner/chunk-decoder.h
@@ -0,0 +1,86 @@
+/** @file
+
+ Inlines base64 images from the ATS cache
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+/** @file
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+#ifndef CHUNK_DECODER_H
+#define CHUNK_DECODER_H
+
+#include <ts/ts.h>
+#include <inttypes.h>
+
+class ChunkDecoder
+{
+ struct State {
+ enum STATES {
+ kUnknown,
+
+ kInvalid,
+
+ kData,
+ kDataN,
+ kEnd,
+ kEndN,
+ kSize,
+ kSizeN,
+ kSizeR,
+
+ kUpperBound,
+ };
+ };
+
+ State::STATES state_;
+ int64_t size_;
+
+public:
+ ChunkDecoder(void) : state_(State::kSize), size_(0) {}
+ void parseSizeCharacter(const char);
+ int parseSize(const char *, const int64_t);
+ int decode(const TSIOBufferReader &);
+ bool isSizeState(void) const;
+
+ inline bool
+ isEnd(void) const
+ {
+ return state_ == State::kEnd;
+ }
+};
+
+#endif // CHUNK_DECODER_H
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/fetcher.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/fetcher.cc b/plugins/experimental/inliner/fetcher.cc
new file mode 100644
index 0000000..8dd9ac0
--- /dev/null
+++ b/plugins/experimental/inliner/fetcher.cc
@@ -0,0 +1,84 @@
+/** @file
+
+ Inlines base64 images from the ATS cache
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+/** @file
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+#include "fetcher.h"
+
+namespace ats
+{
+void
+HttpParser::destroyParser(void)
+{
+ if (parser_ != NULL) {
+ TSHttpParserClear(parser_);
+ TSHttpParserDestroy(parser_);
+ parser_ = NULL;
+ }
+}
+
+bool
+HttpParser::parse(io::IO &io)
+{
+ if (parsed_) {
+ return true;
+ }
+ TSIOBufferBlock block = TSIOBufferReaderStart(io.reader);
+ while (block != NULL) {
+ int64_t size = 0;
+ const char *const begin = TSIOBufferBlockReadStart(block, io.reader, &size);
+ const char *iterator = begin;
+
+ parsed_ = (TSHttpHdrParseResp(parser_, buffer_, location_, &iterator, iterator + size) == TS_PARSE_DONE);
+ TSIOBufferReaderConsume(io.reader, iterator - begin);
+
+ if (parsed_) {
+ TSDebug(PLUGIN_TAG, "HttpParser: response parsing is complete (%u response status code)", statusCode());
+ assert(parser_ != NULL);
+ destroyParser();
+ return true;
+ }
+
+ block = TSIOBufferBlockNext(block);
+ }
+ return false;
+}
+
+} // end of ats namespace
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/fetcher.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/fetcher.h b/plugins/experimental/inliner/fetcher.h
new file mode 100644
index 0000000..aaae553
--- /dev/null
+++ b/plugins/experimental/inliner/fetcher.h
@@ -0,0 +1,325 @@
+/** @file
+
+ Inlines base64 images from the ATS cache
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+/** @file
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+#ifndef FETCHER_H
+#define FETCHER_H
+
+#include <arpa/inet.h>
+#include <cstring>
+#include <iostream>
+#include <limits>
+
+#include <inttypes.h>
+
+#include "chunk-decoder.h"
+#include "ts.h"
+
+#ifndef PLUGIN_TAG
+#error Please define a PLUGIN_TAG before including this file.
+#endif
+
+#define unlikely(x) __builtin_expect((x), 0)
+
+namespace ats
+{
+struct HttpParser {
+ bool parsed_;
+ TSHttpParser parser_;
+ TSMBuffer buffer_;
+ TSMLoc location_;
+
+ void destroyParser(void);
+
+ ~HttpParser()
+ {
+ TSHandleMLocRelease(buffer_, TS_NULL_MLOC, location_);
+ TSMBufferDestroy(buffer_);
+ destroyParser();
+ }
+
+ HttpParser(void) : parsed_(false), parser_(TSHttpParserCreate()), buffer_(TSMBufferCreate()), location_(TSHttpHdrCreate(buffer_))
+ {
+ TSHttpHdrTypeSet(buffer_, location_, TS_HTTP_TYPE_RESPONSE);
+ }
+
+ bool parse(io::IO &);
+
+ int
+ statusCode(void) const
+ {
+ return static_cast<int>(TSHttpHdrStatusGet(buffer_, location_));
+ }
+};
+
+template <class T> struct HttpTransaction {
+ typedef HttpTransaction<T> Self;
+
+ bool parsingHeaders_;
+ bool abort_;
+ bool timeout_;
+ io::IO *in_;
+ io::IO *out_;
+ TSVConn vconnection_;
+ TSCont continuation_;
+ T t_;
+ HttpParser parser_;
+ ChunkDecoder *chunkDecoder_;
+
+ ~HttpTransaction()
+ {
+ if (in_ != NULL) {
+ delete in_;
+ in_ = NULL;
+ }
+ if (out_ != NULL) {
+ delete out_;
+ out_ = NULL;
+ }
+ timeout(0);
+ assert(vconnection_ != NULL);
+ if (abort_) {
+ TSVConnAbort(vconnection_, TS_VC_CLOSE_ABORT);
+ } else {
+ TSVConnClose(vconnection_);
+ }
+ assert(continuation_ != NULL);
+ TSContDestroy(continuation_);
+ if (chunkDecoder_ != NULL) {
+ delete chunkDecoder_;
+ }
+ }
+
+ HttpTransaction(TSVConn v, TSCont c, io::IO *const i, const uint64_t l, const T &t)
+ : parsingHeaders_(false), abort_(false), timeout_(false), in_(NULL), out_(i), vconnection_(v), continuation_(c), t_(t),
+ chunkDecoder_(NULL)
+ {
+ assert(vconnection_ != NULL);
+ assert(continuation_ != NULL);
+ assert(out_ != NULL);
+ assert(l > 0);
+ out_->vio = TSVConnWrite(vconnection_, continuation_, out_->reader, l);
+ }
+
+ inline void
+ abort(const bool b = true)
+ {
+ abort_ = b;
+ }
+
+ void
+ timeout(const int64_t t)
+ {
+ assert(t >= 0);
+ assert(vconnection_ != NULL);
+ if (timeout_) {
+ TSVConnActiveTimeoutCancel(vconnection_);
+ timeout_ = false;
+ } else {
+ TSVConnActiveTimeoutSet(vconnection_, t);
+ timeout_ = true;
+ }
+ }
+
+ static void
+ close(Self *const s)
+ {
+ assert(s != NULL);
+ TSVConnShutdown(s->vconnection_, 1, 0);
+ delete s;
+ }
+
+ static bool
+ isChunkEncoding(const TSMBuffer b, const TSMLoc l)
+ {
+ assert(b != NULL);
+ assert(l != NULL);
+ bool result = false;
+ const TSMLoc field = TSMimeHdrFieldFind(b, l, TS_MIME_FIELD_TRANSFER_ENCODING, TS_MIME_LEN_TRANSFER_ENCODING);
+ if (field != NULL) {
+ int length;
+ const char *const value = TSMimeHdrFieldValueStringGet(b, l, field, -1, &length);
+ if (value != NULL && length == TS_HTTP_LEN_CHUNKED) {
+ result = strncasecmp(value, TS_HTTP_VALUE_CHUNKED, TS_HTTP_LEN_CHUNKED) == 0;
+ }
+ TSHandleMLocRelease(b, l, field);
+ }
+ return result;
+ }
+
+ static int
+ handle(TSCont c, TSEvent e, void *d)
+ {
+ Self *const self = static_cast<Self *const>(TSContDataGet(c));
+ assert(self != NULL);
+ switch (e) {
+ case TS_EVENT_ERROR:
+ TSDebug(PLUGIN_TAG, "HttpTransaction: ERROR");
+ self->t_.error();
+ self->abort();
+ close(self);
+ TSContDataSet(c, NULL);
+ break;
+ case TS_EVENT_VCONN_EOS:
+ TSDebug(PLUGIN_TAG, "HttpTransaction: EOS");
+ goto here;
+
+ case TS_EVENT_VCONN_READ_COMPLETE:
+ TSDebug(PLUGIN_TAG, "HttpTransaction: Read Complete");
+ goto here;
+
+ case TS_EVENT_VCONN_READ_READY:
+ TSDebug(PLUGIN_TAG, "HttpTransaction: Read");
+ here : {
+ assert(self->in_ != NULL);
+ assert(self->in_->reader != NULL);
+ assert(self->in_->vio != NULL);
+ const int64_t available = TSIOBufferReaderAvail(self->in_->reader);
+ if (available > 0) {
+ if (self->parsingHeaders_) {
+ if (self->parser_.parse(*self->in_)) {
+ if (isChunkEncoding(self->parser_.buffer_, self->parser_.location_)) {
+ assert(self->chunkDecoder_ == NULL);
+ self->chunkDecoder_ = new ChunkDecoder();
+ }
+ self->t_.header(self->parser_.buffer_, self->parser_.location_);
+ self->parsingHeaders_ = false;
+ }
+ }
+ if (!self->parsingHeaders_) {
+ if (self->chunkDecoder_ != NULL) {
+ int64_t i = self->chunkDecoder_->decode(self->in_->reader);
+ do {
+ assert(i <= available);
+ self->t_.data(self->in_->reader, i);
+ TSIOBufferReaderConsume(self->in_->reader, i);
+ i = self->chunkDecoder_->decode(self->in_->reader);
+ } while (i > 0);
+ } else {
+ const int64_t l = self->t_.data(self->in_->reader, available);
+ TSIOBufferReaderConsume(self->in_->reader, l);
+ }
+ }
+ }
+ if (e == TS_EVENT_VCONN_READ_COMPLETE || e == TS_EVENT_VCONN_EOS) {
+ self->t_.done();
+ close(self);
+ TSContDataSet(c, NULL);
+ } else if (self->chunkDecoder_ != NULL && self->chunkDecoder_->isEnd()) {
+ assert(self->parsingHeaders_ == false);
+ assert(isChunkEncoding(self->parser_.buffer_, self->parser_.location_));
+ self->abort();
+ self->t_.done();
+ close(self);
+ TSContDataSet(c, NULL);
+ } else {
+ TSVIOReenable(self->in_->vio);
+ }
+ } break;
+ case TS_EVENT_VCONN_WRITE_COMPLETE:
+ TSDebug(PLUGIN_TAG, "HttpTransaction: Write Complete");
+ self->parsingHeaders_ = true;
+ assert(self->in_ == NULL);
+ self->in_ = io::IO::read(self->vconnection_, c);
+ assert(self->in_ != NULL);
+ assert(self->vconnection_);
+ TSVConnShutdown(self->vconnection_, 0, 1);
+ assert(self->out_ != NULL);
+ delete self->out_;
+ self->out_ = NULL;
+ break;
+ case TS_EVENT_VCONN_WRITE_READY:
+ TSDebug(PLUGIN_TAG, "HttpTransaction: Write Ready (Done: %" PRId64 " Todo: %" PRId64 ")", TSVIONDoneGet(self->out_->vio),
+ TSVIONTodoGet(self->out_->vio));
+ assert(self->out_ != NULL);
+ TSVIOReenable(self->out_->vio);
+ break;
+ case 106:
+ case TS_EVENT_TIMEOUT:
+ case TS_EVENT_VCONN_INACTIVITY_TIMEOUT:
+ TSDebug(PLUGIN_TAG, "HttpTransaction: Timeout");
+ self->t_.timeout();
+ self->abort();
+ close(self);
+ TSContDataSet(c, NULL);
+ break;
+
+ default:
+ assert(false); // UNRECHEABLE.
+ }
+ return 0;
+ }
+};
+
+template <class T>
+bool
+get(const std::string &a, io::IO *const i, const int64_t l, const T &t, const int64_t ti = 0)
+{
+ typedef HttpTransaction<T> Transaction;
+ struct sockaddr_in socket;
+ socket.sin_family = AF_INET;
+ socket.sin_port = 80;
+ if (!inet_pton(AF_INET, a.c_str(), &socket.sin_addr)) {
+ TSDebug(PLUGIN_TAG, "ats::get Invalid address provided \"%s\".", a.c_str());
+ return false;
+ }
+ TSVConn vconn = TSHttpConnect(reinterpret_cast<sockaddr *>(&socket));
+ assert(vconn != NULL);
+ TSCont contp = TSContCreate(Transaction::handle, NULL);
+ assert(contp != NULL);
+ Transaction *transaction = new Transaction(vconn, contp, i, l, t);
+ TSContDataSet(contp, transaction);
+ if (ti > 0) {
+ TSDebug(PLUGIN_TAG, "ats::get Setting active timeout to: %" PRId64, ti);
+ transaction->timeout(ti);
+ }
+ return true;
+}
+
+template <class T>
+bool
+get(io::IO *const i, const int64_t l, const T &t, const int64_t ti = 0)
+{
+ return get("127.0.0.1", i, l, t, ti);
+}
+} // end of ats namespace
+
+#endif // FETCHER_H
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/gif.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/gif.h b/plugins/experimental/inliner/gif.h
new file mode 100644
index 0000000..c2a81ea
--- /dev/null
+++ b/plugins/experimental/inliner/gif.h
@@ -0,0 +1,50 @@
+/** @file
+
+ Inlines base64 images from the ATS cache
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+#ifndef GIF_H
+#define GIF_H
+
+#include <algorithm>
+
+namespace ats
+{
+namespace inliner
+{
+ struct GIF {
+ template <class C>
+ static bool
+ verifySignature(const C &content)
+ {
+ static const uint32_t SIGNATURE_SIZE = 6;
+
+ static const char SIGNATURE1[SIGNATURE_SIZE] = {0x47, 0x49, 0x46, 0x38, 0x37, 0x61};
+
+ static const char SIGNATURE2[SIGNATURE_SIZE] = {0x47, 0x49, 0x46, 0x38, 0x39, 0x61};
+
+ return content.size() >= SIGNATURE_SIZE && (std::equal(SIGNATURE1, SIGNATURE1 + SIGNATURE_SIZE, content.begin()) ||
+ std::equal(SIGNATURE2, SIGNATURE2 + SIGNATURE_SIZE, content.begin()));
+ }
+ };
+} // end of inliner namespace
+} // end of ats namespace
+
+#endif // GIF_H
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/html-parser.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/html-parser.cc b/plugins/experimental/inliner/html-parser.cc
new file mode 100644
index 0000000..1365bf8
--- /dev/null
+++ b/plugins/experimental/inliner/html-parser.cc
@@ -0,0 +1,330 @@
+/** @file
+
+ Inlines base64 images from the ATS cache
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#include <assert.h>
+#include <locale>
+#include <string>
+
+#include "html-parser.h"
+
+namespace ats
+{
+namespace inliner
+{
+ Attributes::operator std::string(void) const
+ {
+ std::string result;
+ for (const auto &item : *this) {
+ if (!item.first.empty()) {
+ if (!item.second.empty()) {
+ result += item.first + "=\"" + item.second += "\" ";
+ } else {
+ result += item.first;
+ }
+ }
+ }
+ return result;
+ }
+
+ bool
+ HtmlParser::parseTag(const char c)
+ {
+ switch (c) {
+ case ' ':
+ case '/':
+ case '>':
+ case '\b':
+ case '\n':
+ case '\r':
+ case '\t':
+ return tag_ == Tag::kTagIMG || tag_ == Tag::kTagLINK || tag_ == Tag::kTagSCRIPT || tag_ == Tag::kTagSTYLE;
+ break;
+
+ case 'R':
+ case 'r':
+ if (tag_ == Tag::kTagSC) {
+ tag_ = Tag::kTagSCR;
+ return false;
+ }
+ break;
+
+ case 'C':
+ case 'c':
+ if (tag_ == Tag::kTagS) {
+ tag_ = Tag::kTagSC;
+ return false;
+ }
+ break;
+
+ case 'S':
+ case 's':
+ if (tag_ == Tag::kTag) {
+ tag_ = Tag::kTagS;
+ return false;
+ }
+ break;
+
+ case 'T':
+ case 't':
+ if (tag_ == Tag::kTagS) {
+ tag_ = Tag::kTagST;
+ return false;
+ } else if (tag_ == Tag::kTagSCRIP) {
+ tag_ = Tag::kTagSCRIPT;
+ return false;
+ }
+ break;
+
+ case 'I':
+ case 'i':
+ if (tag_ == Tag::kTag) {
+ tag_ = Tag::kTagI;
+ return false;
+ } else if (tag_ == Tag::kTagSCR) {
+ tag_ = Tag::kTagSCRI;
+ return false;
+ } else if (tag_ == Tag::kTagL) {
+ tag_ = Tag::kTagLI;
+ return false;
+ }
+ break;
+
+ case 'P':
+ case 'p':
+ if (tag_ == Tag::kTagSCRI) {
+ tag_ = Tag::kTagSCRIP;
+ return false;
+ }
+ break;
+
+ case 'Y':
+ case 'y':
+ if (tag_ == Tag::kTagST) {
+ tag_ = Tag::kTagSTY;
+ return false;
+ }
+ break;
+
+ case 'L':
+ case 'l':
+ if (tag_ == Tag::kTag) {
+ tag_ = Tag::kTagL;
+ return false;
+ } else if (tag_ == Tag::kTagSTY) {
+ tag_ = Tag::kTagSTYL;
+ return false;
+ }
+ break;
+
+ case 'E':
+ case 'e':
+ if (tag_ == Tag::kTagSTYL) {
+ tag_ = Tag::kTagSTYLE;
+ return false;
+ }
+ break;
+
+ case 'N':
+ case 'n':
+ if (tag_ == Tag::kTagLI) {
+ tag_ = Tag::kTagLIN;
+ return false;
+ }
+ break;
+
+ case 'K':
+ case 'k':
+ if (tag_ == Tag::kTagLIN) {
+ tag_ = Tag::kTagLINK;
+ return false;
+ }
+ break;
+
+ case 'M':
+ case 'm':
+ if (tag_ == Tag::kTagI) {
+ tag_ = Tag::kTagIM;
+ return false;
+ }
+ break;
+
+ case 'G':
+ case 'g':
+ if (tag_ == Tag::kTagIM) {
+ tag_ = Tag::kTagIMG;
+ return false;
+ }
+ break;
+ }
+ tag_ = Tag::kTagInvalid;
+ return false;
+ }
+
+ bool
+ AttributeParser::parse(const char c)
+ {
+ switch (state_) {
+ case Attribute::kPreName:
+ if (isValidName(c)) {
+ state_ = Attribute::kName;
+ attributes.push_back(Pair());
+ attributes.back().first += c;
+ } else if (c == '/' || c == '>') {
+ return true;
+ }
+ break;
+ case Attribute::kName:
+ if (isValidName(c)) {
+ attributes.back().first += c;
+ } else if (c == '=') {
+ state_ = Attribute::kPreValue;
+ } else if (c == '/' || c == '>') {
+ return true;
+ } else {
+ state_ = Attribute::kPostName;
+ }
+ break;
+ case Attribute::kPostName:
+ if (isValidName(c)) {
+ state_ = Attribute::kName;
+ attributes.push_back(Pair());
+ attributes.back().first += c;
+ } else if (c == '=') {
+ state_ = Attribute::kPreValue;
+ } else if (c == '/' || c == '>') {
+ return true;
+ }
+ break;
+ case Attribute::kPreValue:
+ // TODO(dmorilha) add the unquoted value.
+ if (c == '\'') {
+ state_ = Attribute::kSingleQuotedValue;
+ } else if (c == '"') {
+ state_ = Attribute::kDoubleQuotedValue;
+ // VERY BROKEN SYNTAX
+ } else if (c == '/' || c == '>') {
+ return true;
+ } else if (isValidValue(c)) {
+ state_ = Attribute::kUnquotedValue;
+ attributes.back().second += c;
+ }
+ break;
+ case Attribute::kUnquotedValue:
+ if (isValidValue(c)) {
+ attributes.back().second += c;
+ } else if (c == '/' || c == '>' || c == '"' || c == '\'') {
+ return true;
+ // space?
+ } else {
+ state_ = Attribute::kPreName;
+ }
+ break;
+ case Attribute::kSingleQuotedValue:
+ if (c == '\'') {
+ state_ = Attribute::kPreName;
+ } else {
+ attributes.back().second += c;
+ }
+ break;
+ case Attribute::kDoubleQuotedValue:
+ if (c == '"') {
+ state_ = Attribute::kPreName;
+ } else {
+ attributes.back().second += c;
+ }
+ break;
+ default:
+ assert(false); // UNREACHABLE;
+ break;
+ }
+ return false;
+ }
+
+ size_t
+ HtmlParser::parse(const char *b, size_t l, size_t o)
+ {
+ const char *const end = b + l, *c = b;
+
+ size_t done = 0;
+
+ for (; c != end; ++c) {
+ if (state_ == State::kAttributes) {
+ if (attributeParser_.parse(*c)) {
+ switch (tag_) {
+ case Tag::kTagIMG:
+ handleImage(attributeParser_.attributes);
+ attributeParser_.reset();
+ o += c - b;
+ l -= c - b;
+ b = c;
+ break;
+ default:
+ break;
+ }
+ state_ = State::kTagBypass;
+ }
+ continue;
+ }
+
+ if (state_ == State::kTag) {
+ if (parseTag(*c)) {
+ state_ = State::kAttributes;
+ attributeParser_.reset();
+ const size_t p = c - b;
+ if (p > 0 && tag_ == Tag::kTagIMG) {
+ done += bypass(p, o);
+ o += p;
+ l -= p;
+ b = c;
+ }
+ } else if (tag_ == Tag::kTagInvalid) {
+ state_ = State::kTagBypass;
+ }
+ continue;
+ }
+
+ if (state_ == State::kTagBypass) {
+ if (*c == '>') {
+ state_ = State::kUndefined;
+ }
+ continue;
+ }
+
+ if (state_ == State::kUndefined) {
+ if (*c == '<') {
+ state_ = State::kTag;
+ tag_ = Tag::kTag;
+ }
+ continue;
+ }
+ }
+
+ if (l > 0 && (state_ != State::kAttributes || tag_ != Tag::kTagIMG)) {
+ done += bypass(l, o);
+ }
+
+ return done;
+ }
+
+} // end of inliner namespace
+} // end of ats namespace
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/html-parser.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/html-parser.h b/plugins/experimental/inliner/html-parser.h
new file mode 100644
index 0000000..646c2fb
--- /dev/null
+++ b/plugins/experimental/inliner/html-parser.h
@@ -0,0 +1,146 @@
+/** @file
+
+ Inlines base64 images from the ATS cache
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+#ifndef HTML_PARSER_H
+#define HTML_PARSER_H
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+namespace ats
+{
+namespace inliner
+{
+ typedef std::pair<std::string, std::string> Pair;
+ typedef std::vector<Pair> AttributeVector;
+
+ struct Attributes : AttributeVector {
+ operator std::string(void) const;
+ };
+
+ struct Tag {
+ enum TAGS {
+ kUndefined = 0,
+
+ kTag,
+
+ kTagI,
+ kTagIM,
+ kTagIMG,
+
+ kTagS,
+ kTagSC,
+ kTagSCR,
+ kTagSCRI,
+ kTagSCRIP,
+ kTagSCRIPT,
+
+ kTagST,
+ kTagSTY,
+ kTagSTYL,
+ kTagSTYLE,
+
+ kTagL,
+ kTagLI,
+ kTagLIN,
+ kTagLINK,
+
+ kTagInvalid,
+
+ kUpperBound,
+ };
+ };
+
+ struct Attribute {
+ enum ATTRIBUTES {
+ kUndefined = 0,
+
+ kPreName,
+ kName,
+ kPostName,
+ kPreValue,
+ kUnquotedValue,
+ kSingleQuotedValue,
+ kDoubleQuotedValue,
+
+ kUpperBound,
+ };
+ };
+
+ struct State {
+ enum STATES {
+ kUndefined = 0,
+
+ kTag,
+ kTagBypass,
+ kClosingTag,
+ kAttributes,
+
+ kUpperBound,
+ };
+ };
+
+ struct AttributeParser {
+ Attribute::ATTRIBUTES state_;
+ Attributes attributes;
+
+ AttributeParser(void) : state_(Attribute::kPreName) {}
+ void
+ reset(void)
+ {
+ state_ = Attribute::kPreName;
+ attributes.clear();
+ }
+
+ bool
+ isValidName(char c) const
+ {
+ return std::isalnum(c) || c == '-' || c == '.' || c == '_';
+ }
+
+ // TODO(dmorilha): what is valid value? Check w3c.
+ bool
+ isValidValue(char c) const
+ {
+ return std::isalnum(c) || c == '-' || c == '.' || c == '_';
+ }
+
+ bool parse(const char);
+ };
+
+ struct HtmlParser {
+ State::STATES state_;
+ Tag::TAGS tag_;
+ AttributeParser attributeParser_;
+
+ HtmlParser(void) : state_(State::kUndefined), tag_(Tag::kUndefined) {}
+ bool parseTag(const char);
+ size_t parse(const char *, size_t, size_t o = 0);
+ virtual void handleImage(const Attributes &) = 0;
+ virtual size_t bypass(const size_t, const size_t) = 0;
+ };
+
+} // end of inliner namespace
+} // end of ats namespace
+
+#endif // HTML_PARSER_H
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/inliner-handler.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/inliner-handler.cc b/plugins/experimental/inliner/inliner-handler.cc
new file mode 100644
index 0000000..ab5342a
--- /dev/null
+++ b/plugins/experimental/inliner/inliner-handler.cc
@@ -0,0 +1,149 @@
+/** @file
+
+ Inlines base64 images from the ATS cache
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+#include <assert.h>
+#include <iostream>
+#include <sstream>
+
+#include "inliner-handler.h"
+#include "cache.h"
+#include "cache-handler.h"
+
+namespace ats
+{
+namespace inliner
+{
+ Handler::Handler(const TSIOBufferReader r, ats::io::IOSinkPointer &&i)
+ : ioSink_(i), sink_(ioSink_->branch()), sink2_(sink_->branch()), reader_(TSIOBufferReaderClone(r)), counter_(0)
+ {
+ assert(ioSink_);
+ assert(sink_);
+ assert(sink_->data_);
+ assert(sink2_);
+ assert(sink2_->data_);
+ assert(reader_ != nullptr);
+ *sink_ << "<script>"
+ "var a=document,b=a.getElementsByTagName(\"img\"),c=b.length,w=window,d=function(){var "
+ "m=w.addEventListener,n=w.attachEvent;return "
+ "m?function(k){m(\"load\",k)}:n?function(k){n(\"onload\",k)}:function(k){k()}}(),e=function(){var "
+ "m=window,n=a.documentElement,k=a.getElementsByTagName(\"body\")[0];return "
+ "function(l){l=l.getBoundingClientRect();return "
+ "0<=l.top&&0<=l.left&&l.bottom<=(m.innerHeight||n.clientHeight||k.clientHeight)&&l.right<=(m.innerWidth||n."
+ "clientWidth||k.clientWidth)}}();function f(m,n){var k=new Image;k.onload=function(){k=null;n(m)};k.src=m}function "
+ "g(m,n){var k,l;for(k=0;k<c;++k)l=b[k],0===l.className.indexOf(m+\" \")&&n(l)}function "
+ "h(m,n){f(n,function(k){g(m,function(l){l.src=k})})}function i(m,n){function k(k){var "
+ "l;for(l=0;l<q;l++)p[l].src=k}var "
+ "l=!1,p=[],q;g(m,function(k){l|=e(k);p.push(k)});q=p.length;l?f(n,k):d(function(){f(n,k)})};"
+ "</script>";
+ }
+
+ size_t
+ Handler::bypass(const size_t s, const size_t o)
+ {
+ assert(s > 0);
+ assert(sink2_);
+ // TSDebug(PLUGIN_TAG, "size: %lu, offset: %lu, sum: %lu", s, o, (s + o));
+ *sink2_ << ats::io::ReaderSize(reader_, s, o);
+ return s;
+ }
+
+ void
+ Handler::parse(void)
+ {
+ assert(reader_ != nullptr);
+ TSIOBufferBlock block = TSIOBufferReaderStart(reader_);
+ int64_t offset = 0;
+ while (block != nullptr) {
+ int64_t length = 0;
+ const char *const buffer = TSIOBufferBlockReadStart(block, reader_, &length);
+ assert(buffer != nullptr);
+ if (length > 0) {
+ HtmlParser::parse(buffer, length, offset);
+ offset += length;
+ }
+ block = TSIOBufferBlockNext(block);
+ }
+ assert(offset == TSIOBufferReaderAvail(reader_));
+ if (offset > 0) {
+ TSIOBufferReaderConsume(reader_, offset);
+ }
+ assert(TSIOBufferReaderAvail(reader_) == 0);
+ }
+
+ void
+ Handler::handleImage(const Attributes &a)
+ {
+ std::string src;
+
+ for (const auto &item : a) {
+ if (!item.first.empty()) {
+ src = item.second;
+ }
+ }
+
+ const bool isTagged =
+ (src.find("http://") == 0 || src.find("https://") == 0) && src.find("inline", src.find("#")) != std::string::npos;
+
+ if (isTagged) {
+ std::string classes, original = " ";
+ for (const auto &item : a) {
+ if (!item.first.empty()) {
+ if (!item.second.empty()) {
+ if (item.first == "class") {
+ classes = item.second;
+ } else if (item.first.find("src") == std::string::npos) {
+ original += item.first + "=\"" + item.second += "\" ";
+ }
+ }
+ } else {
+ original += item.first + " ";
+ }
+ }
+
+ assert(sink_ != nullptr);
+ assert(sink2_ != nullptr);
+ src.erase(src.find('#'));
+ cache::fetch<CacheHandler>(src + VERSION, src, original, classes, generateId(), sink2_->branch(), sink_);
+ } else {
+ assert(sink2_ != nullptr);
+ *sink2_ << " " << static_cast<std::string>(a);
+ }
+ }
+
+ std::string
+ Handler::generateId(void)
+ {
+ std::stringstream ss;
+ // TODO(dmorilha): stop using memory address here.
+ ss << "ii-" << static_cast<void *>(this) << "-" << ++counter_;
+ return ss.str();
+ }
+
+ void
+ Handler::abort(void)
+ {
+ assert(ioSink_);
+ ioSink_->abort();
+ }
+
+} // end of inliner namespace
+} // end of ats namespace
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/inliner-handler.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/inliner-handler.h b/plugins/experimental/inliner/inliner-handler.h
new file mode 100644
index 0000000..3ec5602
--- /dev/null
+++ b/plugins/experimental/inliner/inliner-handler.h
@@ -0,0 +1,70 @@
+/** @file
+
+ Inlines base64 images from the ATS cache
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+#ifndef INLINER_HANDLER_H
+#define INLINER_HANDLER_H
+
+#include <string>
+#include <memory>
+
+#include "html-parser.h"
+#include "ts.h"
+
+namespace ats
+{
+namespace inliner
+{
+ struct Handler : HtmlParser {
+ ats::io::IOSinkPointer ioSink_;
+ ats::io::SinkPointer sink_, sink2_;
+ const TSIOBufferReader reader_;
+ size_t counter_;
+
+ ~Handler()
+ {
+ assert(reader_ != nullptr);
+ const int64_t available = TSIOBufferReaderAvail(reader_);
+ if (available > 0) {
+ TSIOBufferReaderConsume(reader_, available);
+ }
+ TSIOBufferReaderFree(reader_);
+ }
+
+ Handler(const TSIOBufferReader, ats::io::IOSinkPointer &&);
+
+ Handler(const Handler &) = delete;
+ Handler &operator=(const Handler &) = delete;
+
+ void parse(void);
+
+ size_t bypass(const size_t, const size_t);
+ void handleImage(const Attributes &);
+
+ std::string generateId(void);
+
+ void abort(void);
+ };
+
+} // end of inliner namespace
+} // end of ats namespace
+
+#endif // INLINER_HANDLER_H
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/jpeg.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/jpeg.h b/plugins/experimental/inliner/jpeg.h
new file mode 100644
index 0000000..e86a5bc
--- /dev/null
+++ b/plugins/experimental/inliner/jpeg.h
@@ -0,0 +1,47 @@
+/** @file
+
+ Inlines base64 images from the ATS cache
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+#ifndef JPEG_H
+#define JPEG_H
+
+#include <algorithm>
+
+namespace ats
+{
+namespace inliner
+{
+ struct JPEG {
+ template <class C>
+ static bool
+ verifySignature(const C &content)
+ {
+ static const uint32_t SIGNATURE_SIZE = 3;
+
+ static const char SIGNATURE[SIGNATURE_SIZE] = {static_cast<char>(0xff), static_cast<char>(0xd8), static_cast<char>(0xff)};
+
+ return content.size() >= SIGNATURE_SIZE && std::equal(SIGNATURE, SIGNATURE + SIGNATURE_SIZE, content.begin());
+ }
+ };
+} // end of inliner namespace
+} // end of ats namespace
+
+#endif // JPEG_H
http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/png.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/png.h b/plugins/experimental/inliner/png.h
new file mode 100644
index 0000000..3285333
--- /dev/null
+++ b/plugins/experimental/inliner/png.h
@@ -0,0 +1,124 @@
+/** @file
+
+ Inlines base64 images from the ATS cache
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+#ifndef PNG_H
+#define PNG_H
+
+#include <algorithm>
+#include <exception>
+#include <string>
+#include <vector>
+
+namespace ats
+{
+namespace inliner
+{
+ struct PNG {
+ typedef std::vector<char> Content;
+ typedef Content::const_iterator Iterator;
+
+ static const uint32_t HEADER_SIZE = 8;
+
+ Content content;
+
+ template <class C>
+ static bool
+ verifySignature(const C &content)
+ {
+ const char SIGNATURE[] = {static_cast<char>(0x89), 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a};
+
+ return content.size() >= HEADER_SIZE && std::equal(SIGNATURE, SIGNATURE + HEADER_SIZE, content.begin());
+ }
+
+ PNG(const Content &c) : content(c)
+ {
+ if (!verifySignature(content)) {
+ throw std::exception();
+ }
+ }
+
+ class ChunkHeader
+ {
+ unsigned char length_[4];
+ char type_[4];
+
+ public:
+ uint32_t
+ length(void) const
+ {
+ return (length_[0] << 24) | (length_[1] << 16) | (length_[2] << 8) | length_[3];
+ }
+
+ std::string
+ type(void) const
+ {
+ return std::string(type_, 4);
+ }
+ };
+
+ // REFERENCE: http://en.wikipedia.org/wiki/Portable_Network_Graphics#.22Chunks.22_within_the_file
+ void
+ stripMetaData(Content &output) const
+ {
+ output.clear();
+ output.reserve(content.size());
+
+ // chunk length (4) + chunk type (4) + chunk crc (4)
+ const int32_t N = 12;
+
+ const char *iterator = content.data();
+ const char *const end = iterator + content.size();
+
+ if (std::distance(iterator, end) > HEADER_SIZE) {
+ std::copy(iterator, iterator + HEADER_SIZE, std::back_inserter(output));
+
+ iterator += HEADER_SIZE;
+
+ while (iterator < end) {
+ const ChunkHeader *const header = reinterpret_cast<const ChunkHeader *>(iterator);
+ const std::string type = header->type();
+ const uint32_t length = header->length();
+
+ // iterator cannot go backwards
+ if (iterator >= iterator + (length + N)) {
+ output.clear();
+ break;
+ }
+
+ if (type == "IDAT" || type == "IEND" || type == "IHDR" || type == "PLTE" || type == "tRNS") {
+ // skip chunk in case it is bigger than the whole image to prevent overflows
+ if (std::distance(iterator, end) >= length + N) {
+ std::copy(iterator, iterator + length + N, std::back_inserter(output));
+ }
+ }
+
+ iterator += length + N;
+ }
+ }
+ }
+ };
+
+ // const uint32_t PNG::HEADER_SIZE = 8;
+} // end of inliner namespace
+} // end of ats namespace
+
+#endif // PNG_H