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:06 UTC

[1/2] trafficserver git commit: TS-4132: Open source Yahoo's ats-inliner plug-in

Repository: trafficserver
Updated Branches:
  refs/heads/master 8c38c6c42 -> 7b8417cce


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/ts.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/ts.cc b/plugins/experimental/inliner/ts.cc
new file mode 100644
index 0000000..50d5aa8
--- /dev/null
+++ b/plugins/experimental/inliner/ts.cc
@@ -0,0 +1,471 @@
+/** @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 <cstring>
+#include <iostream>
+#include <limits>
+
+#include "ts.h"
+
+namespace ats
+{
+namespace io
+{
+  IO *
+  IO::read(TSVConn v, TSCont c, const int64_t s)
+  {
+    assert(s > 0);
+    IO *io = new IO();
+    io->vio = TSVConnRead(v, c, io->buffer, s);
+    return io;
+  }
+
+  IO *
+  IO::write(TSVConn v, TSCont c, const int64_t s)
+  {
+    assert(s > 0);
+    IO *io = new IO();
+    io->vio = TSVConnWrite(v, c, io->reader, s);
+    return io;
+  }
+
+  uint64_t
+  IO::copy(const std::string &s) const
+  {
+    assert(buffer != nullptr);
+    const uint64_t size = TSIOBufferWrite(buffer, s.data(), s.size());
+    assert(size == s.size());
+    return size;
+  }
+
+  int64_t
+  IO::consume(void) const
+  {
+    assert(reader != NULL);
+    const int64_t available = TSIOBufferReaderAvail(reader);
+    if (available > 0) {
+      TSIOBufferReaderConsume(reader, available);
+    }
+    return available;
+  }
+
+  int64_t
+  IO::done(void) const
+  {
+    assert(vio != NULL);
+    assert(reader != NULL);
+    const int64_t d = TSIOBufferReaderAvail(reader) + TSVIONDoneGet(vio);
+    TSVIONDoneSet(vio, d);
+    return d;
+  }
+
+  WriteOperation::~WriteOperation()
+  {
+    assert(mutex_ != nullptr);
+    const Lock lock(mutex_);
+    TSDebug(PLUGIN_TAG, "~WriteOperation");
+
+    vio_ = nullptr;
+
+    if (action_ != nullptr) {
+      TSActionCancel(action_);
+    }
+
+    assert(reader_ != nullptr);
+    TSIOBufferReaderFree(reader_);
+
+    assert(buffer_ != nullptr);
+    TSIOBufferDestroy(buffer_);
+
+    assert(continuation_ != nullptr);
+    TSContDestroy(continuation_);
+
+    assert(vconnection_ != nullptr);
+    TSVConnShutdown(vconnection_, 0, 1);
+  }
+
+  WriteOperation::WriteOperation(const TSVConn v, const TSMutex m, const size_t t)
+    : vconnection_(v), buffer_(TSIOBufferCreate()), reader_(TSIOBufferReaderAlloc(buffer_)),
+      mutex_(m != nullptr ? m : TSMutexCreate()), continuation_(TSContCreate(WriteOperation::Handle, mutex_)),
+      vio_(TSVConnWrite(v, continuation_, reader_, std::numeric_limits<int64_t>::max())), action_(nullptr), timeout_(t), bytes_(0),
+      reenable_(true)
+  {
+    assert(vconnection_ != nullptr);
+    assert(buffer_ != nullptr);
+    assert(reader_ != nullptr);
+    assert(mutex_ != nullptr);
+    assert(continuation_ != nullptr);
+    assert(vio_ != nullptr);
+
+    if (timeout_ > 0) {
+      action_ = TSContSchedule(continuation_, timeout_, TS_THREAD_POOL_DEFAULT);
+      assert(action_ != nullptr);
+    }
+  }
+
+  void
+  WriteOperation::process(const size_t b)
+  {
+    assert(mutex_);
+    const Lock lock(mutex_);
+    bytes_ += b;
+    if (vio_ != nullptr && TSVIOContGet(vio_) != nullptr) {
+      if (reenable_) {
+        TSVIOReenable(vio_);
+        reenable_ = false;
+      }
+    } else {
+      vio_ = nullptr;
+    }
+  }
+
+  int
+  WriteOperation::Handle(const TSCont c, const TSEvent e, void *d)
+  {
+    assert(c != nullptr);
+    WriteOperationPointer *const p = static_cast<WriteOperationPointer *>(TSContDataGet(c));
+
+    if (TS_EVENT_VCONN_WRITE_COMPLETE == e) {
+      TSDebug(PLUGIN_TAG, "TS_EVENT_VCONN_WRITE_COMPLETE");
+      if (p != nullptr) {
+        TSContDataSet(c, nullptr);
+        delete p;
+      }
+      return TS_SUCCESS;
+    }
+
+    assert(p != nullptr);
+    assert(*p);
+    WriteOperation &operation = **p;
+    assert(operation.continuation_ == c);
+    assert(operation.vconnection_ != nullptr);
+    assert(d != nullptr);
+    assert(TS_EVENT_ERROR == e || TS_EVENT_TIMEOUT == e || TS_EVENT_VCONN_WRITE_READY == e);
+
+    switch (e) {
+    case TS_EVENT_ERROR:
+      TSError("[" PLUGIN_TAG "] TS_EVENT_ERROR from producer");
+      goto handle_error; // handle errors as timeouts
+
+    case TS_EVENT_TIMEOUT:
+      TSError("[" PLUGIN_TAG "] TS_EVENT_TIMEOUT from producer");
+
+    handle_error:
+      operation.close();
+      assert(operation.action_ != nullptr);
+      TSActionDone(operation.action_);
+      operation.action_ = nullptr;
+      /*
+      TSContDataSet(c, nullptr);
+      delete p;
+      */
+      break;
+    case TS_EVENT_VCONN_WRITE_READY:
+      operation.reenable_ = true;
+      break;
+
+    default:
+      TSError("[" PLUGIN_TAG "] Unknown event: %i", e);
+      assert(false); // UNREACHEABLE
+      break;
+    }
+
+    return TS_SUCCESS;
+  }
+
+  WriteOperationWeakPointer
+  WriteOperation::Create(const TSVConn v, const TSMutex m, const size_t t)
+  {
+    WriteOperation *const operation = new WriteOperation(v, m, t);
+    assert(operation != nullptr);
+    WriteOperationPointer *const pointer = new WriteOperationPointer(operation);
+    assert(pointer != nullptr);
+    TSContDataSet(operation->continuation_, pointer);
+
+#ifndef NDEBUG
+    {
+      WriteOperationPointer *const p = static_cast<WriteOperationPointer *>(TSContDataGet(operation->continuation_));
+      assert(pointer == p);
+      assert((*p).get() == operation);
+    }
+#endif
+
+    return WriteOperationWeakPointer(*pointer);
+  }
+
+  WriteOperation &WriteOperation::operator<<(const TSIOBufferReader r)
+  {
+    assert(r != nullptr);
+    process(TSIOBufferCopy(buffer_, r, TSIOBufferReaderAvail(r), 0));
+    return *this;
+  }
+
+  WriteOperation &WriteOperation::operator<<(const ReaderSize &r)
+  {
+    assert(r.reader != nullptr);
+    process(TSIOBufferCopy(buffer_, r.reader, r.size, r.offset));
+    return *this;
+  }
+
+  WriteOperation &WriteOperation::operator<<(const ReaderOffset &r)
+  {
+    assert(r.reader != nullptr);
+    process(TSIOBufferCopy(buffer_, r.reader, TSIOBufferReaderAvail(r.reader), r.offset));
+    return *this;
+  }
+
+  WriteOperation &WriteOperation::operator<<(const char *const s)
+  {
+    assert(s != nullptr);
+    process(TSIOBufferWrite(buffer_, s, strlen(s)));
+    return *this;
+  }
+
+  WriteOperation &WriteOperation::operator<<(const std::string &s)
+  {
+    process(TSIOBufferWrite(buffer_, s.data(), s.size()));
+    return *this;
+  }
+
+  void
+  WriteOperation::close(void)
+  {
+    assert(mutex_ != nullptr);
+    const Lock lock(mutex_);
+    if (vio_ != nullptr && TSVIOContGet(vio_) != nullptr) {
+      TSVIONBytesSet(vio_, bytes_);
+      TSVIOReenable(vio_);
+    }
+    vio_ = nullptr;
+  }
+
+  void
+  WriteOperation::abort(void)
+  {
+    assert(mutex_ != nullptr);
+    const Lock lock(mutex_);
+    vio_ = nullptr;
+  }
+
+  IOSink::~IOSink()
+  {
+    // TSDebug(PLUGIN_TAG, "~IOSink %p", this);
+    const WriteOperationPointer operation = operation_.lock();
+    if (operation) {
+      operation_.reset();
+      operation->close();
+    }
+  }
+
+  void
+  IOSink::process(void)
+  {
+    const WriteOperationPointer operation = operation_.lock();
+
+    if (!data_ || !operation) {
+      return;
+    }
+
+    assert(operation->mutex_ != nullptr);
+    const Lock lock(operation->mutex_);
+
+    assert(operation->buffer_ != nullptr);
+    const Node::Result result = data_->process(operation->buffer_);
+    operation->bytes_ += result.first;
+    operation->process();
+    if (result.second && data_.unique()) {
+      data_.reset();
+    }
+  }
+
+  Lock
+  IOSink::lock(void)
+  {
+    const WriteOperationPointer operation = operation_.lock();
+
+    if (!operation) {
+      return Lock();
+    }
+
+    assert(operation != nullptr);
+    assert(operation->mutex_ != nullptr);
+
+    return Lock(operation->mutex_);
+  }
+
+  void
+  IOSink::abort(void)
+  {
+    const WriteOperationPointer operation = operation_.lock();
+    if (operation) {
+      operation->abort();
+    }
+  }
+
+  BufferNode &BufferNode::operator<<(const TSIOBufferReader r)
+  {
+    assert(r != nullptr);
+    TSIOBufferCopy(buffer_, r, TSIOBufferReaderAvail(r), 0);
+    return *this;
+  }
+
+  BufferNode &BufferNode::operator<<(const ReaderSize &r)
+  {
+    assert(r.reader != nullptr);
+    TSIOBufferCopy(buffer_, r.reader, r.size, r.offset);
+    return *this;
+  }
+
+  BufferNode &BufferNode::operator<<(const ReaderOffset &r)
+  {
+    assert(r.reader != nullptr);
+    TSIOBufferCopy(buffer_, r.reader, TSIOBufferReaderAvail(r.reader), r.offset);
+    return *this;
+  }
+
+  BufferNode &BufferNode::operator<<(const char *const s)
+  {
+    assert(s != nullptr);
+    TSIOBufferWrite(buffer_, s, strlen(s));
+    return *this;
+  }
+
+  BufferNode &BufferNode::operator<<(const std::string &s)
+  {
+    TSIOBufferWrite(buffer_, s.data(), s.size());
+    return *this;
+  }
+
+  Node::Result
+  BufferNode::process(const TSIOBuffer b)
+  {
+    assert(b != nullptr);
+    assert(buffer_ != nullptr);
+    assert(reader_ != nullptr);
+    const size_t available = TSIOBufferReaderAvail(reader_);
+    const size_t copied = TSIOBufferCopy(b, reader_, available, 0);
+    assert(copied == available);
+    TSIOBufferReaderConsume(reader_, copied);
+    // TSDebug(PLUGIN_TAG, "BufferNode::process %lu", copied);
+    return Node::Result(copied, TSIOBufferReaderAvail(reader_) == 0);
+  }
+
+  Node::Result
+  StringNode::process(const TSIOBuffer b)
+  {
+    assert(b != nullptr);
+    const size_t copied = TSIOBufferWrite(b, string_.data(), string_.size());
+    assert(copied == string_.size());
+    return Node::Result(copied, true);
+  }
+
+  SinkPointer
+  IOSink::branch(void)
+  {
+    if (!data_) {
+      data_.reset(new Data(shared_from_this()));
+      data_->first_ = true;
+    }
+    SinkPointer pointer(new Sink(data_));
+    // TSDebug(PLUGIN_TAG, "IOSink branch %p", pointer.get());
+    return pointer;
+  }
+
+  SinkPointer
+  Sink::branch(void)
+  {
+    DataPointer data;
+    if (data_) {
+      const bool first = data_->nodes_.empty();
+      data.reset(new Data(data_->root_));
+      assert(data);
+      data_->nodes_.push_back(data);
+      assert(!data_->nodes_.empty());
+      data->first_ = first;
+    }
+    SinkPointer pointer(new Sink(data));
+    // TSDebug(PLUGIN_TAG, "Sink branch %p", pointer.get());
+    return pointer;
+  }
+
+  Sink::~Sink()
+  {
+    // TSDebug(PLUGIN_TAG, "~Sink %p", this);
+    assert(data_);
+    assert(data_.use_count() >= 1);
+    assert(data_->root_);
+    const IOSinkPointer root(std::move(data_->root_));
+    data_.reset();
+    root->process();
+  }
+
+  Node::Result
+  Data::process(const TSIOBuffer b)
+  {
+    assert(b != nullptr);
+    int64_t length = 0;
+
+    const Nodes::iterator begin = nodes_.begin(), end = nodes_.end();
+
+    Nodes::iterator it = begin;
+
+    for (; it != end; ++it) {
+      assert(*it != nullptr);
+      const Node::Result result = (*it)->process(b);
+      length += result.first;
+      if (!result.second || !it->unique()) {
+        break;
+      }
+    }
+
+    // TSDebug(PLUGIN_TAG, "Data::process %li", length);
+
+    if (begin != it) {
+      // TSDebug(PLUGIN_TAG, "Data::process::erase");
+      nodes_.erase(begin, it);
+      if (it != end) {
+        Data *data = dynamic_cast<Data *>(it->get());
+        while (data != nullptr) {
+          // TSDebug(PLUGIN_TAG, "new first");
+          data->first_ = true;
+          if (data->nodes_.empty()) {
+            break;
+          }
+          assert(data->nodes_.front());
+          data = dynamic_cast<Data *>(data->nodes_.front().get());
+        }
+      }
+    }
+
+    return Node::Result(length, nodes_.empty());
+  }
+
+  Sink &Sink::operator<<(std::string &&s)
+  {
+    if (data_) {
+      data_->nodes_.emplace_back(new StringNode(std::move(s)));
+    }
+    return *this;
+  }
+
+} // end of io namespace
+} // end of ats namespace

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/ts.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/ts.h b/plugins/experimental/inliner/ts.h
new file mode 100644
index 0000000..704c0a5
--- /dev/null
+++ b/plugins/experimental/inliner/ts.h
@@ -0,0 +1,319 @@
+/** @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 TS_H
+#define TS_H
+
+#include <assert.h>
+#include <limits>
+#include <list>
+#include <memory>
+#include <ts/ts.h>
+
+#ifdef NDEBUG
+#define CHECK(X) X
+#else
+#define CHECK(X)                                         \
+  {                                                      \
+    const TSReturnCode r = static_cast<TSReturnCode>(X); \
+    assert(r == TS_SUCCESS);                             \
+  }
+#endif
+
+namespace ats
+{
+namespace io
+{
+  // TODO(dmorilha): dislike this
+  struct IO {
+    TSIOBuffer buffer;
+    TSIOBufferReader reader;
+    TSVIO vio;
+
+    ~IO()
+    {
+      consume();
+      assert(reader != NULL);
+      TSIOBufferReaderFree(reader);
+      assert(buffer != NULL);
+      TSIOBufferDestroy(buffer);
+    }
+
+    IO(void) : buffer(TSIOBufferCreate()), reader(TSIOBufferReaderAlloc(buffer)), vio(NULL) {}
+    IO(const TSIOBuffer &b) : buffer(b), reader(TSIOBufferReaderAlloc(buffer)), vio(NULL) { assert(buffer != NULL); }
+    static IO *read(TSVConn, TSCont, const int64_t);
+
+    static IO *
+    read(TSVConn v, TSCont c)
+    {
+      return IO::read(v, c, std::numeric_limits<int64_t>::max());
+    }
+
+    static IO *write(TSVConn, TSCont, const int64_t);
+
+    static IO *
+    write(TSVConn v, TSCont c)
+    {
+      return IO::write(v, c, std::numeric_limits<int64_t>::max());
+    }
+
+    uint64_t copy(const std::string &) const;
+
+    int64_t consume(void) const;
+
+    int64_t done(void) const;
+  };
+
+  struct ReaderSize {
+    const TSIOBufferReader reader;
+    const size_t offset;
+    const size_t size;
+
+    ReaderSize(const TSIOBufferReader r, const size_t s, const size_t o = 0) : reader(r), offset(o), size(s)
+    {
+      assert(reader != NULL);
+    }
+
+    ReaderSize(const ReaderSize &) = delete;
+    ReaderSize &operator=(const ReaderSize &) = delete;
+    void *operator new(const std::size_t) throw(std::bad_alloc) = delete;
+  };
+
+  struct ReaderOffset {
+    const TSIOBufferReader reader;
+    const size_t offset;
+
+    ReaderOffset(const TSIOBufferReader r, const size_t o) : reader(r), offset(o) { assert(reader != NULL); }
+    ReaderOffset(const ReaderOffset &) = delete;
+    ReaderOffset &operator=(const ReaderOffset &) = delete;
+    void *operator new(const std::size_t) throw(std::bad_alloc) = delete;
+  };
+
+  struct WriteOperation;
+
+  typedef std::shared_ptr<WriteOperation> WriteOperationPointer;
+  typedef std::weak_ptr<WriteOperation> WriteOperationWeakPointer;
+
+  struct Lock {
+    const TSMutex mutex_;
+
+    ~Lock()
+    {
+      if (mutex_ != nullptr) {
+        TSMutexUnlock(mutex_);
+      }
+    }
+
+    Lock(const TSMutex m) : mutex_(m)
+    {
+      if (mutex_ != nullptr) {
+        TSMutexLock(mutex_);
+      }
+    }
+
+    Lock(void) : mutex_(nullptr) {}
+    Lock(const Lock &) = delete;
+
+    Lock(Lock &&l) : mutex_(l.mutex_) { const_cast<TSMutex &>(l.mutex_) = nullptr; }
+    Lock &operator=(const Lock &) = delete;
+  };
+
+  struct WriteOperation : std::enable_shared_from_this<WriteOperation> {
+    TSVConn vconnection_;
+    TSIOBuffer buffer_;
+    TSIOBufferReader reader_;
+    TSMutex mutex_;
+    TSCont continuation_;
+    TSVIO vio_;
+    TSAction action_;
+    const size_t timeout_;
+    size_t bytes_;
+    bool reenable_;
+
+    static int Handle(TSCont, TSEvent, void *);
+    static WriteOperationWeakPointer Create(const TSVConn, const TSMutex mutex = nullptr, const size_t timeout = 0);
+
+    ~WriteOperation();
+
+    WriteOperation(const WriteOperation &) = delete;
+    WriteOperation &operator=(const WriteOperation &) = delete;
+
+    WriteOperation &operator<<(const TSIOBufferReader);
+    WriteOperation &operator<<(const ReaderSize &);
+    WriteOperation &operator<<(const ReaderOffset &);
+    WriteOperation &operator<<(const char *const);
+    WriteOperation &operator<<(const std::string &);
+
+    void process(const size_t b = 0);
+    void close(void);
+    void abort(void);
+
+  private:
+    WriteOperation(const TSVConn, const TSMutex, const size_t);
+  };
+
+  struct Node;
+  typedef std::shared_ptr<Node> NodePointer;
+  typedef std::list<NodePointer> Nodes;
+
+  struct IOSink;
+  typedef std::shared_ptr<IOSink> IOSinkPointer;
+
+  struct Sink;
+  typedef std::shared_ptr<Sink> SinkPointer;
+
+  struct Data;
+  typedef std::shared_ptr<Data> DataPointer;
+
+  struct IOSink : std::enable_shared_from_this<IOSink> {
+    WriteOperationWeakPointer operation_;
+    DataPointer data_;
+
+    ~IOSink();
+    IOSink(const IOSink &) = delete;
+
+    IOSink &operator=(const IOSink &) = delete;
+
+    template <class T> IOSink &operator<<(T &&t)
+    {
+      const WriteOperationPointer operation = operation_.lock();
+      if (operation) {
+        const Lock lock(operation->mutex_);
+        *operation << std::forward<T>(t);
+      }
+      return *this;
+    }
+
+    template <class... A>
+    static IOSinkPointer
+    Create(A &&... a)
+    {
+      return IOSinkPointer(new IOSink(WriteOperation::Create(std::forward<A>(a)...)));
+    }
+
+    void process(void);
+    SinkPointer branch(void);
+    Lock lock(void);
+    void abort(void);
+
+  private:
+    IOSink(WriteOperationWeakPointer &&p) : operation_(std::move(p)) {}
+  };
+
+  struct Node {
+    typedef std::pair<size_t, bool> Result;
+    IOSinkPointer ioSink_;
+    virtual ~Node() {}
+    virtual Node::Result process(const TSIOBuffer) = 0;
+  };
+
+  struct StringNode : Node {
+    std::string string_;
+    explicit StringNode(std::string &&s) : string_(std::move(s)) {}
+    Node::Result process(const TSIOBuffer);
+  };
+
+  struct BufferNode : Node {
+    const TSIOBuffer buffer_;
+    const TSIOBufferReader reader_;
+
+    ~BufferNode()
+    {
+      assert(reader_ != nullptr);
+      TSIOBufferReaderFree(reader_);
+      assert(buffer_ != nullptr);
+      TSIOBufferDestroy(buffer_);
+    }
+
+    BufferNode(void) : buffer_(TSIOBufferCreate()), reader_(TSIOBufferReaderAlloc(buffer_))
+    {
+      assert(buffer_ != nullptr);
+      assert(reader_ != nullptr);
+    }
+
+    BufferNode(const BufferNode &) = delete;
+    BufferNode &operator=(const BufferNode &) = delete;
+    BufferNode &operator<<(const TSIOBufferReader);
+    BufferNode &operator<<(const ReaderSize &);
+    BufferNode &operator<<(const ReaderOffset &);
+    BufferNode &operator<<(const char *const);
+    BufferNode &operator<<(const std::string &);
+    Node::Result process(const TSIOBuffer);
+  };
+
+  struct Data : Node {
+    Nodes nodes_;
+    IOSinkPointer root_;
+    bool first_;
+
+    template <class T> Data(T &&t) : root_(std::forward<T>(t)), first_(false) {}
+    Data(const Data &) = delete;
+    Data &operator=(const Data &) = delete;
+
+    Node::Result process(const TSIOBuffer);
+  };
+
+  struct Sink {
+    DataPointer data_;
+
+    ~Sink();
+
+    template <class... A> Sink(A &&... a) : data_(std::forward<A>(a)...) {}
+    Sink(const Sink &) = delete;
+    Sink &operator=(const Sink &) = delete;
+
+    SinkPointer branch(void);
+
+    Sink &operator<<(std::string &&);
+
+    template <class T> Sink &operator<<(T &&t)
+    {
+      if (data_) {
+        const Lock lock = data_->root_->lock();
+        assert(data_->root_ != nullptr);
+        const bool empty = data_->nodes_.empty();
+        if (data_->first_ && empty) {
+          // TSDebug(PLUGIN_TAG, "flushing");
+          assert(data_->root_);
+          *data_->root_ << std::forward<T>(t);
+        } else {
+          // TSDebug(PLUGIN_TAG, "buffering");
+          BufferNode *buffer = nullptr;
+          if (!empty) {
+            buffer = dynamic_cast<BufferNode *>(data_->nodes_.back().get());
+          }
+          if (buffer == nullptr) {
+            data_->nodes_.emplace_back(new BufferNode());
+            buffer = reinterpret_cast<BufferNode *>(data_->nodes_.back().get());
+          }
+          assert(buffer != nullptr);
+          *buffer << std::forward<T>(t);
+        }
+      }
+      return *this;
+    }
+  };
+
+} // end of io namespace
+} // end of ats namespace
+
+#endif // TS_H

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/util.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/util.h b/plugins/experimental/inliner/util.h
new file mode 100644
index 0000000..2fcc42e
--- /dev/null
+++ b/plugins/experimental/inliner/util.h
@@ -0,0 +1,43 @@
+/** @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 UTIL_H
+#define UTIL_H
+
+#include <vector>
+
+#define DISALLOW_COPY_AND_ASSIGN(T) \
+private:                            \
+  T(const T &);                     \
+  void operator=(const T &)
+
+#define DISALLOW_IMPLICIT_CONSTRUCTORS(T) \
+private:                                  \
+  T(void);                                \
+  DISALLOW_COPY_AND_ASSIGN(T)
+
+namespace util
+{
+typedef std::vector<char> Buffer;
+} // end of util namespace
+
+#endif // UTIL_H

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/7b8417cc/plugins/experimental/inliner/vconnection.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/inliner/vconnection.h b/plugins/experimental/inliner/vconnection.h
new file mode 100644
index 0000000..026c41f
--- /dev/null
+++ b/plugins/experimental/inliner/vconnection.h
@@ -0,0 +1,105 @@
+/** @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 VCONNECTION_H
+#define VCONNECTION_H
+
+#include <assert.h>
+
+#include "ts.h"
+
+namespace ats
+{
+namespace io
+{
+  namespace vconnection
+  {
+    template <class T> class Read
+    {
+      typedef Read<T> Self;
+      TSVConn vconnection_;
+      io::IO in_;
+      T t_;
+
+      Read(TSVConn v, T &&t, const int64_t s) : vconnection_(v), t_(std::forward<T>(t))
+      {
+        assert(vconnection_ != nullptr);
+        TSCont continuation = TSContCreate(Self::handleRead, nullptr);
+        assert(continuation != nullptr);
+        TSContDataSet(continuation, this);
+        in_.vio = TSVConnRead(vconnection_, continuation, in_.buffer, s);
+      }
+
+      static void
+      close(Self *const s)
+      {
+        assert(s != nullptr);
+        TSIOBufferReaderConsume(s->in_.reader, TSIOBufferReaderAvail(s->in_.reader));
+        assert(s->vconnection_ != nullptr);
+        TSVConnShutdown(s->vconnection_, 1, 1);
+        TSVConnClose(s->vconnection_);
+        delete s;
+      }
+
+      static int
+      handleRead(TSCont c, TSEvent e, void *)
+      {
+        Self *const self = static_cast<Self *const>(TSContDataGet(c));
+        assert(self != nullptr);
+        switch (e) {
+        case TS_EVENT_VCONN_EOS:
+        case TS_EVENT_VCONN_READ_COMPLETE:
+        case TS_EVENT_VCONN_READ_READY: {
+          const int64_t available = TSIOBufferReaderAvail(self->in_.reader);
+          if (available > 0) {
+            self->t_.data(self->in_.reader);
+            TSIOBufferReaderConsume(self->in_.reader, available);
+          }
+          if (e == TS_EVENT_VCONN_READ_COMPLETE || e == TS_EVENT_VCONN_EOS) {
+            self->t_.done();
+            close(self);
+            TSContDataSet(c, nullptr);
+            TSContDestroy(c);
+          }
+        } break;
+        default:
+          assert(false); // UNRECHEABLE.
+          break;
+        }
+        return TS_SUCCESS;
+      }
+
+      template <class U> friend void read(TSVConn, U &&, const int64_t);
+    };
+
+    template <class C>
+    void
+    read(TSVConn v, C &&c, const int64_t s)
+    {
+      new Read<C>(v, std::forward<C>(c), s);
+    }
+
+  } // end of vconnection namespace
+} // end of io namespace
+} // end of ats namespace
+
+#endif // VCONNECTION_H


[2/2] trafficserver git commit: TS-4132: Open source Yahoo's ats-inliner plug-in

Posted by bc...@apache.org.
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