blob: 30a1d108c585a3717368ec5e6d5220eda97bd4dc [file] [log] [blame]
/*
* 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 <cstdlib>
#include <cstdio>
#include <cstdio>
#include <strings.h>
#include <sstream>
#include <cstring>
#include <atomic>
#include <memory>
#include <getopt.h>
#include <cstdint>
#include <cinttypes>
#include <string_view>
#include <unistd.h>
#include <ts/ts.h>
#include "tscore/ink_defs.h"
#include "tscpp/util/PostScript.h"
#include "tscpp/util/TextView.h"
#include "Cleanup.h"
namespace
{
struct BodyBuilder {
atscppapi::TSContUniqPtr transform_connp;
atscppapi::TSIOBufferUniqPtr output_buffer;
// It's important that output_reader comes after output_buffer so it will be deleted first.
atscppapi::TSIOBufferReaderUniqPtr output_reader;
TSVIO output_vio = nullptr;
bool wrote_prebody = false;
bool wrote_body = false;
bool hdr_ready = false;
std::atomic_flag wrote_postbody;
int64_t nbytes = 0;
};
struct XDebugTxnAuxData {
std::unique_ptr<BodyBuilder> body_builder;
unsigned xheaders = 0;
};
atscppapi::TxnAuxMgrData mgrData;
using AuxDataMgr = atscppapi::TxnAuxDataMgr<XDebugTxnAuxData, mgrData>;
} // end anonymous namespace
#include "xdebug_headers.cc"
#include "xdebug_transforms.cc"
static struct {
const char *str;
int len;
} xDebugHeader = {nullptr, 0};
enum {
XHEADER_X_CACHE_KEY = 1u << 2,
XHEADER_X_MILESTONES = 1u << 3,
XHEADER_X_CACHE = 1u << 4,
XHEADER_X_GENERATION = 1u << 5,
XHEADER_X_TRANSACTION_ID = 1u << 6,
XHEADER_X_DUMP_HEADERS = 1u << 7,
XHEADER_X_REMAP = 1u << 8,
XHEADER_X_PROBE_HEADERS = 1u << 9,
XHEADER_X_PSELECT_KEY = 1u << 10,
};
static TSCont XInjectHeadersCont = nullptr;
static TSCont XDeleteDebugHdrCont = nullptr;
// Return the length of a string literal.
template <int N>
unsigned
lengthof(const char (&)[N])
{
return N - 1;
}
static TSMLoc
FindOrMakeHdrField(TSMBuffer buffer, TSMLoc hdr, const char *name, unsigned len)
{
TSMLoc field;
field = TSMimeHdrFieldFind(buffer, hdr, name, len);
if (field == TS_NULL_MLOC) {
if (TSMimeHdrFieldCreateNamed(buffer, hdr, name, len, &field) == TS_SUCCESS) {
TSReleaseAssert(TSMimeHdrFieldAppend(buffer, hdr, field) == TS_SUCCESS);
}
}
return field;
}
static void
InjectGenerationHeader(TSHttpTxn txn, TSMBuffer buffer, TSMLoc hdr)
{
TSMgmtInt value;
TSMLoc dst = TS_NULL_MLOC;
if (TSHttpTxnConfigIntGet(txn, TS_CONFIG_HTTP_CACHE_GENERATION, &value) == TS_SUCCESS) {
dst = FindOrMakeHdrField(buffer, hdr, "X-Cache-Generation", lengthof("X-Cache-Generation"));
if (dst != TS_NULL_MLOC) {
TSReleaseAssert(TSMimeHdrFieldValueInt64Set(buffer, hdr, dst, -1 /* idx */, value) == TS_SUCCESS);
}
}
if (dst != TS_NULL_MLOC) {
TSHandleMLocRelease(buffer, hdr, dst);
}
}
static void
InjectCacheKeyHeader(TSHttpTxn txn, TSMBuffer buffer, TSMLoc hdr)
{
TSMLoc url = TS_NULL_MLOC;
TSMLoc dst = TS_NULL_MLOC;
struct {
char *ptr;
int len;
} strval = {nullptr, 0};
TSDebug("xdebug", "attempting to inject X-Cache-Key header");
if (TSUrlCreate(buffer, &url) != TS_SUCCESS) {
goto done;
}
if (TSHttpTxnCacheLookupUrlGet(txn, buffer, url) != TS_SUCCESS) {
goto done;
}
strval.ptr = TSUrlStringGet(buffer, url, &strval.len);
if (strval.ptr == nullptr || strval.len == 0) {
goto done;
}
// Create a new response header field.
dst = FindOrMakeHdrField(buffer, hdr, "X-Cache-Key", lengthof("X-Cache-Key"));
if (dst == TS_NULL_MLOC) {
goto done;
}
// Now copy the cache lookup URL into the response header.
TSReleaseAssert(TSMimeHdrFieldValueStringInsert(buffer, hdr, dst, -1 /* idx */, strval.ptr, strval.len) == TS_SUCCESS);
done:
if (dst != TS_NULL_MLOC) {
TSHandleMLocRelease(buffer, hdr, dst);
}
if (url != TS_NULL_MLOC) {
TSHandleMLocRelease(buffer, TS_NULL_MLOC, url);
}
TSfree(strval.ptr);
}
static void
InjectCacheHeader(TSHttpTxn txn, TSMBuffer buffer, TSMLoc hdr)
{
TSMLoc dst = TS_NULL_MLOC;
int status;
static const char *names[] = {
"miss", // TS_CACHE_LOOKUP_MISS,
"hit-stale", // TS_CACHE_LOOKUP_HIT_STALE,
"hit-fresh", // TS_CACHE_LOOKUP_HIT_FRESH,
"skipped" // TS_CACHE_LOOKUP_SKIPPED
};
TSDebug("xdebug", "attempting to inject X-Cache header");
// Create a new response header field.
dst = FindOrMakeHdrField(buffer, hdr, "X-Cache", lengthof("X-Cache"));
if (dst == TS_NULL_MLOC) {
goto done;
}
if (TSHttpTxnCacheLookupStatusGet(txn, &status) == TS_ERROR) {
// If the cache lookup hasn't happened yes, TSHttpTxnCacheLookupStatusGet will fail.
TSReleaseAssert(TSMimeHdrFieldValueStringInsert(buffer, hdr, dst, -1 /* idx */, "none", 4) == TS_SUCCESS);
} else {
const char *msg = (status < 0 || status >= static_cast<int>(countof(names))) ? "unknown" : names[status];
TSReleaseAssert(TSMimeHdrFieldValueStringInsert(buffer, hdr, dst, -1 /* idx */, msg, -1) == TS_SUCCESS);
}
done:
if (dst != TS_NULL_MLOC) {
TSHandleMLocRelease(buffer, hdr, dst);
}
}
struct milestone {
TSMilestonesType mstype;
const char *msname;
};
static void
InjectMilestonesHeader(TSHttpTxn txn, TSMBuffer buffer, TSMLoc hdr)
{
// The set of milestones we can publish. Some milestones happen after
// this hook, so we skip those ...
static const milestone milestones[] = {
{TS_MILESTONE_UA_BEGIN, "UA-BEGIN"},
{TS_MILESTONE_UA_FIRST_READ, "UA-FIRST-READ"},
{TS_MILESTONE_UA_READ_HEADER_DONE, "UA-READ-HEADER-DONE"},
{TS_MILESTONE_UA_BEGIN_WRITE, "UA-BEGIN-WRITE"},
{TS_MILESTONE_UA_CLOSE, "UA-CLOSE"},
{TS_MILESTONE_SERVER_FIRST_CONNECT, "SERVER-FIRST-CONNECT"},
{TS_MILESTONE_SERVER_CONNECT, "SERVER-CONNECT"},
{TS_MILESTONE_SERVER_CONNECT_END, "SERVER-CONNECT-END"},
{TS_MILESTONE_SERVER_BEGIN_WRITE, "SERVER-BEGIN-WRITE"},
{TS_MILESTONE_SERVER_FIRST_READ, "SERVER-FIRST-READ"},
{TS_MILESTONE_SERVER_READ_HEADER_DONE, "SERVER-READ-HEADER-DONE"},
{TS_MILESTONE_SERVER_CLOSE, "SERVER-CLOSE"},
{TS_MILESTONE_CACHE_OPEN_READ_BEGIN, "CACHE-OPEN-READ-BEGIN"},
{TS_MILESTONE_CACHE_OPEN_READ_END, "CACHE-OPEN-READ-END"},
{TS_MILESTONE_CACHE_OPEN_WRITE_BEGIN, "CACHE-OPEN-WRITE-BEGIN"},
{TS_MILESTONE_CACHE_OPEN_WRITE_END, "CACHE-OPEN-WRITE-END"},
{TS_MILESTONE_DNS_LOOKUP_BEGIN, "DNS-LOOKUP-BEGIN"},
{TS_MILESTONE_DNS_LOOKUP_END, "DNS-LOOKUP-END"},
// SM_START is deliberately excluded because as all the times are printed relative to it
// it would always be zero.
{TS_MILESTONE_SM_FINISH, "SM-FINISH"},
{TS_MILESTONE_PLUGIN_ACTIVE, "PLUGIN-ACTIVE"},
{TS_MILESTONE_PLUGIN_TOTAL, "PLUGIN-TOTAL"},
};
TSMLoc dst = TS_NULL_MLOC;
TSHRTime epoch;
// TS_MILESTONE_SM_START is stamped when the HTTP transaction is born. The slow
// log feature publishes the other times as seconds relative to this epoch. Let's
// do the same.
TSHttpTxnMilestoneGet(txn, TS_MILESTONE_SM_START, &epoch);
// Create a new response header field.
dst = FindOrMakeHdrField(buffer, hdr, "X-Milestones", lengthof("X-Milestones"));
if (dst == TS_NULL_MLOC) {
goto done;
}
for (unsigned i = 0; i < countof(milestones); ++i) {
TSHRTime time = 0;
char hdrval[64];
// If we got a milestone (it's in nanoseconds), convert it to seconds relative to
// the start of the transaction. We don't get milestone values for portions of the
// state machine the request doesn't traverse.
TSHttpTxnMilestoneGet(txn, milestones[i].mstype, &time);
if (time > 0) {
double elapsed = static_cast<double>(time - epoch) / 1000000000.0;
int len = snprintf(hdrval, sizeof(hdrval), "%s=%1.9lf", milestones[i].msname, elapsed);
TSReleaseAssert(TSMimeHdrFieldValueStringInsert(buffer, hdr, dst, -1 /* idx */, hdrval, len) == TS_SUCCESS);
}
}
done:
if (dst != TS_NULL_MLOC) {
TSHandleMLocRelease(buffer, hdr, dst);
}
}
static const char NotFound[] = "Not-Found";
// The returned string must be freed with TSfree() unless it is equal to the constant NotFound.
//
static const char *
getRemapUrlStr(TSHttpTxn txnp, TSReturnCode (*remapUrlGetFunc)(TSHttpTxn, TSMLoc *), int &urlStrLen)
{
TSMLoc urlLoc;
TSReturnCode rc = remapUrlGetFunc(txnp, &urlLoc);
if (rc != TS_SUCCESS) {
urlStrLen = sizeof(NotFound) - 1;
return NotFound;
}
char *urlStr = TSUrlStringGet(nullptr, urlLoc, &urlStrLen);
// Be defensive.
if ((urlStrLen == 0) and urlStr) {
TSError("[xdebug] non-null remap URL string with zero length");
TSfree(urlStr);
urlStr = nullptr;
}
if (!urlStr) {
urlStrLen = sizeof(NotFound) - 1;
return NotFound;
}
return urlStr;
}
static void
InjectRemapHeader(TSHttpTxn txn, TSMBuffer buffer, TSMLoc hdr)
{
TSMLoc dst = FindOrMakeHdrField(buffer, hdr, "X-Remap", lengthof("X-Remap"));
if (TS_NULL_MLOC != dst) {
int fromUrlStrLen, toUrlStrLen;
const char *fromUrlStr = getRemapUrlStr(txn, TSRemapFromUrlGet, fromUrlStrLen);
const char *toUrlStr = getRemapUrlStr(txn, TSRemapToUrlGet, toUrlStrLen);
char buf[2048];
int len = snprintf(buf, sizeof(buf), "from=%*s, to=%*s", fromUrlStrLen, fromUrlStr, toUrlStrLen, toUrlStr);
if (fromUrlStr != NotFound) {
TSfree(const_cast<char *>(fromUrlStr));
}
if (toUrlStr != NotFound) {
TSfree(const_cast<char *>(toUrlStr));
}
TSReleaseAssert(TSMimeHdrFieldValueStringInsert(buffer, hdr, dst, -1 /* idx */, buf, len) == TS_SUCCESS);
TSHandleMLocRelease(buffer, hdr, dst);
}
}
static void
InjectTxnUuidHeader(TSHttpTxn txn, TSMBuffer buffer, TSMLoc hdr)
{
TSMLoc dst = FindOrMakeHdrField(buffer, hdr, "X-Transaction-ID", lengthof("X-Transaction-ID"));
if (TS_NULL_MLOC != dst) {
char buf[TS_UUID_STRING_LEN + 22]; // Padded for int64_t (20) + 1 ('-') + 1 ('\0')
TSUuid uuid = TSProcessUuidGet();
int len = snprintf(buf, sizeof(buf), "%s-%" PRIu64 "", TSUuidStringGet(uuid), TSHttpTxnIdGet(txn));
TSReleaseAssert(TSMimeHdrFieldValueStringInsert(buffer, hdr, dst, -1 /* idx */, buf, len) == TS_SUCCESS);
TSHandleMLocRelease(buffer, hdr, dst);
}
}
static void
InjectParentSelectionKeyHeader(TSHttpTxn txn, TSMBuffer buffer, TSMLoc hdr)
{
TSMLoc url = TS_NULL_MLOC;
TSMLoc dst = TS_NULL_MLOC;
struct {
char *ptr;
int len;
} strval = {nullptr, 0};
TSDebug("xdebug", "attempting to inject X-ParentSelection-Key header");
if (TSUrlCreate(buffer, &url) != TS_SUCCESS) {
goto done;
}
if (TSHttpTxnParentSelectionUrlGet(txn, buffer, url) != TS_SUCCESS) {
goto done;
}
strval.ptr = TSUrlStringGet(buffer, url, &strval.len);
if (strval.ptr == nullptr || strval.len == 0) {
goto done;
}
// Create a new response header field.
dst = FindOrMakeHdrField(buffer, hdr, "X-ParentSelection-Key", lengthof("X-ParentSelection-Key"));
if (dst == TS_NULL_MLOC) {
goto done;
}
// Now copy the parent selection lookup URL into the response header.
TSReleaseAssert(TSMimeHdrFieldValueStringInsert(buffer, hdr, dst, -1 /* idx */, strval.ptr, strval.len) == TS_SUCCESS);
done:
if (dst != TS_NULL_MLOC) {
TSHandleMLocRelease(buffer, hdr, dst);
}
if (url != TS_NULL_MLOC) {
TSHandleMLocRelease(buffer, TS_NULL_MLOC, url);
}
TSfree(strval.ptr);
}
static int
XInjectResponseHeaders(TSCont /* contp */, TSEvent event, void *edata)
{
TSHttpTxn txn = static_cast<TSHttpTxn>(edata);
TSMBuffer buffer;
TSMLoc hdr;
TSReleaseAssert(event == TS_EVENT_HTTP_SEND_RESPONSE_HDR);
unsigned xheaders = AuxDataMgr::data(txn).xheaders;
if (xheaders == 0) {
goto done;
}
if (TSHttpTxnClientRespGet(txn, &buffer, &hdr) == TS_ERROR) {
goto done;
}
if (xheaders & XHEADER_X_CACHE_KEY) {
InjectCacheKeyHeader(txn, buffer, hdr);
}
if (xheaders & XHEADER_X_CACHE) {
InjectCacheHeader(txn, buffer, hdr);
}
if (xheaders & XHEADER_X_MILESTONES) {
InjectMilestonesHeader(txn, buffer, hdr);
}
if (xheaders & XHEADER_X_GENERATION) {
InjectGenerationHeader(txn, buffer, hdr);
}
if (xheaders & XHEADER_X_TRANSACTION_ID) {
InjectTxnUuidHeader(txn, buffer, hdr);
}
if (xheaders & XHEADER_X_REMAP) {
InjectRemapHeader(txn, buffer, hdr);
}
// intentionally placed after all injected headers.
if (xheaders & XHEADER_X_DUMP_HEADERS) {
log_headers(txn, buffer, hdr, "ClientResponse");
}
if (xheaders & XHEADER_X_PROBE_HEADERS) {
BodyBuilder *data = AuxDataMgr::data(txn).body_builder.get();
TSDebug("xdebug_transform", "XInjectResponseHeaders(): client resp header ready");
if (data == nullptr) {
TSHttpTxnReenable(txn, TS_EVENT_HTTP_ERROR);
return TS_ERROR;
}
data->hdr_ready = true;
writePostBody(txn, data);
}
if (xheaders & XHEADER_X_PSELECT_KEY) {
InjectParentSelectionKeyHeader(txn, buffer, hdr);
}
done:
TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
return TS_EVENT_NONE;
}
static bool
isFwdFieldValue(std::string_view value, intmax_t &fwdCnt)
{
static const ts::TextView paramName("fwd");
if (value.size() < paramName.size()) {
return false;
}
ts::TextView tvVal(value);
if (strcasecmp(paramName, tvVal.prefix(paramName.size())) != 0) {
return false;
}
tvVal.remove_prefix(paramName.size());
if (tvVal.size() == 0) {
// Value is 'fwd' with no '=<count>'.
fwdCnt = -1;
return true;
}
const char httpSpace[] = " \t";
tvVal.ltrim(httpSpace);
if (tvVal[0] != '=') {
return false;
}
tvVal.remove_prefix(1);
tvVal.ltrim(httpSpace);
size_t sz = tvVal.size();
intmax_t i = ts::svtoi(tvVal, &tvVal);
if ((tvVal.size() != sz) or (i < 0)) {
// There were crud characters after the number, or the number was negative.
return false;
}
fwdCnt = i;
return true;
}
// Scan the client request headers and determine which debug headers they
// want in the response.
static int
XScanRequestHeaders(TSCont /* contp */, TSEvent event, void *edata)
{
TSHttpTxn txn = static_cast<TSHttpTxn>(edata);
unsigned xheaders = 0;
intmax_t fwdCnt = 0;
TSMLoc field, next;
TSMBuffer buffer;
TSMLoc hdr;
// Make sure TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE) is called before exiting function.
//
ts::PostScript ps([=]() -> void { TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); });
TSReleaseAssert(event == TS_EVENT_HTTP_READ_REQUEST_HDR);
if (TSHttpTxnClientReqGet(txn, &buffer, &hdr) == TS_ERROR) {
return TS_EVENT_NONE;
}
TSDebug("xdebug", "scanning for %s header values", xDebugHeader.str);
// Walk the X-Debug header values and determine what to inject into the response.
field = TSMimeHdrFieldFind(buffer, hdr, xDebugHeader.str, xDebugHeader.len);
while (field != TS_NULL_MLOC) {
int count = TSMimeHdrFieldValuesCount(buffer, hdr, field);
for (int i = 0; i < count; ++i) {
const char *value;
int vsize;
value = TSMimeHdrFieldValueStringGet(buffer, hdr, field, i, &vsize);
if (value == nullptr || vsize == 0) {
continue;
}
#define header_field_eq(name, vptr, vlen) (((int)lengthof(name) == vlen) && (strncasecmp(name, vptr, vlen) == 0))
if (header_field_eq("x-cache-key", value, vsize)) {
xheaders |= XHEADER_X_CACHE_KEY;
} else if (header_field_eq("x-milestones", value, vsize)) {
xheaders |= XHEADER_X_MILESTONES;
} else if (header_field_eq("x-cache", value, vsize)) {
xheaders |= XHEADER_X_CACHE;
} else if (header_field_eq("x-cache-generation", value, vsize)) {
xheaders |= XHEADER_X_GENERATION;
} else if (header_field_eq("x-transaction-id", value, vsize)) {
xheaders |= XHEADER_X_TRANSACTION_ID;
} else if (header_field_eq("x-remap", value, vsize)) {
xheaders |= XHEADER_X_REMAP;
} else if (header_field_eq("via", value, vsize)) {
// If the client requests the Via header, enable verbose Via debugging for this transaction.
TSHttpTxnConfigIntSet(txn, TS_CONFIG_HTTP_INSERT_RESPONSE_VIA_STR, 3);
} else if (header_field_eq("diags", value, vsize)) {
// Enable diagnostics for DebugTxn()'s only
TSHttpTxnDebugSet(txn, 1);
} else if (header_field_eq("probe", value, vsize)) {
xheaders |= XHEADER_X_PROBE_HEADERS;
auto &auxData = AuxDataMgr::data(txn);
// prefix request headers and postfix response headers
BodyBuilder *data = new BodyBuilder();
auxData.body_builder.reset(data);
TSVConn connp = TSTransformCreate(body_transform, txn);
data->transform_connp.reset(connp);
TSContDataSet(connp, txn);
TSHttpTxnHookAdd(txn, TS_HTTP_RESPONSE_TRANSFORM_HOOK, connp);
// disable writing to cache because we are injecting data into the body.
TSHttpTxnReqCacheableSet(txn, 0);
TSHttpTxnRespCacheableSet(txn, 0);
TSHttpTxnServerRespNoStoreSet(txn, 1);
TSHttpTxnTransformedRespCache(txn, 0);
TSHttpTxnUntransformedRespCache(txn, 0);
} else if (header_field_eq("x-parentselection-key", value, vsize)) {
xheaders |= XHEADER_X_PSELECT_KEY;
} else if (isFwdFieldValue(std::string_view(value, vsize), fwdCnt)) {
if (fwdCnt > 0) {
// Decrement forward count in X-Debug header.
char newVal[128];
snprintf(newVal, sizeof(newVal), "fwd=%" PRIiMAX, fwdCnt - 1);
TSMimeHdrFieldValueStringSet(buffer, hdr, field, i, newVal, std::strlen(newVal));
}
} else {
TSDebug("xdebug", "ignoring unrecognized debug tag '%.*s'", vsize, value);
}
}
#undef header_field_eq
// Get the next duplicate.
next = TSMimeHdrFieldNextDup(buffer, hdr, field);
// Now release our reference.
TSHandleMLocRelease(buffer, hdr, field);
// And go to the next field.
field = next;
}
if (xheaders) {
TSDebug("xdebug", "adding response hook for header mask %p and forward count %" PRIiMAX, reinterpret_cast<void *>(xheaders),
fwdCnt);
TSHttpTxnHookAdd(txn, TS_HTTP_SEND_RESPONSE_HDR_HOOK, XInjectHeadersCont);
AuxDataMgr::data(txn).xheaders = xheaders;
if (fwdCnt == 0) {
// X-Debug header has to be deleted, but not too soon for other plugins to see it.
TSHttpTxnHookAdd(txn, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, XDeleteDebugHdrCont);
}
}
return TS_EVENT_NONE;
}
// Continuation function to delete the x-debug header.
//
static int
XDeleteDebugHdr(TSCont /* contp */, TSEvent event, void *edata)
{
TSHttpTxn txn = static_cast<TSHttpTxn>(edata);
TSMLoc hdr, field;
TSMBuffer buffer;
// Make sure TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE) is called before exiting function.
//
ts::PostScript ps([=]() -> void { TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); });
TSReleaseAssert(event == TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE);
if (TSHttpTxnClientReqGet(txn, &buffer, &hdr) == TS_ERROR) {
return TS_EVENT_NONE;
}
field = TSMimeHdrFieldFind(buffer, hdr, xDebugHeader.str, xDebugHeader.len);
if (field == TS_NULL_MLOC) {
return TS_EVENT_NONE;
}
if (TSMimeHdrFieldDestroy(buffer, hdr, field) == TS_ERROR) {
TSError("Failure destroying %s header", xDebugHeader.str);
}
TSHandleMLocRelease(buffer, hdr, field);
return TS_EVENT_NONE;
}
void
TSPluginInit(int argc, const char *argv[])
{
static const struct option longopt[] = {{const_cast<char *>("header"), required_argument, nullptr, 'h'},
{nullptr, no_argument, nullptr, '\0'}};
TSPluginRegistrationInfo info;
info.plugin_name = (char *)"xdebug";
info.vendor_name = (char *)"Apache Software Foundation";
info.support_email = (char *)"dev@trafficserver.apache.org";
if (TSPluginRegister(&info) != TS_SUCCESS) {
TSError("[xdebug] Plugin registration failed");
}
// Parse the arguments
while (true) {
int opt = getopt_long(argc, const_cast<char *const *>(argv), "", longopt, nullptr);
switch (opt) {
case 'h':
xDebugHeader.str = TSstrdup(optarg);
break;
}
if (opt == -1) {
break;
}
}
if (nullptr == xDebugHeader.str) {
xDebugHeader.str = TSstrdup("X-Debug"); // We malloc this, for consistency for future plugin unload events
}
xDebugHeader.len = strlen(xDebugHeader.str);
// Make xDebugHeader available to other plugins, as a C-style string.
//
int idx = -1;
TSReleaseAssert(TSUserArgIndexReserve(TS_USER_ARGS_GLB, "XDebugHeader", "XDebug header name", &idx) == TS_SUCCESS);
TSReleaseAssert(idx >= 0);
TSUserArgSet(nullptr, idx, const_cast<char *>(xDebugHeader.str));
AuxDataMgr::init("xdebug");
// Setup the global hook
TSReleaseAssert(XInjectHeadersCont = TSContCreate(XInjectResponseHeaders, nullptr));
TSReleaseAssert(XDeleteDebugHdrCont = TSContCreate(XDeleteDebugHdr, nullptr));
TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, TSContCreate(XScanRequestHeaders, nullptr));
gethostname(Hostname, 1024);
}