blob: 22589c006561511dff2f88a2b180fe5dad841967 [file]
/** @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.
*/
#pragma once
#include <algorithm>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <cassert>
#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 inliner_ns
{
extern DbgCtl dbg_ctl;
}
using namespace inliner_ns;
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()
{
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.
Dbg(dbg_ctl, "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);
}
Dbg(dbg_ctl, "%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;
Dbg(dbg_ctl, "Content-Length: %i", contentLength);
content_.reserve(contentLength);
}
}
}
void
timeout() const
{
Dbg(dbg_ctl, "Fetch timeout");
}
void
error() const
{
Dbg(dbg_ctl, "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()
{
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);
Dbg(dbg_ctl, "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()
{
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";
auto io{std::make_unique<io::IO>()};
Dbg(dbg_ctl, "request:\n%s", request.c_str());
auto size{io->copy(request)};
ats::get(std::move(io), size, AnotherClass(src_));
}
};
} // namespace inliner
} // namespace ats
#undef ONE_PIXEL