blob: b917c2d20e20e83fd5f8fdd0d07138ecb3eb2ae1 [file]
/** @file
Cache Control Extensions Caching Utilities
@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 <strings.h>
#include <string>
#include <netinet/in.h>
#include <unistd.h>
#include "stale_response.h"
#include "BodyData.h"
#include "CacheUpdate.h"
#include "UrlComponents.h"
#include "NumberToString.h"
using namespace std;
// unique url parameter that shoud never leave ATS
static const char ASYNC_PARM[] = "swrasync=asyncmrl";
static const int ASYNC_PARM_LEN = sizeof(ASYNC_PARM) - 1;
// unique header that should never leave ATS
const char SERVER_INTERCEPT_HEADER[] = "X-CCExtensions-Intercept";
const int SERVER_INTERCEPT_HEADER_LEN = sizeof(SERVER_INTERCEPT_HEADER) - 1;
/*-----------------------------------------------------------------------------------------------*/
static char *
convert_mime_hdr_to_string(TSMLoc hdr_loc)
{
TSIOBuffer output_buffer;
TSIOBufferReader reader;
int64_t total_avail;
TSIOBufferBlock block;
const char *block_start;
int64_t block_avail;
char *output_string;
int output_len;
output_buffer = TSIOBufferCreate();
if (!output_buffer) {
SRDBG(TAG_BAD, "[%s] couldn't allocate IOBuffer", __FUNCTION__);
}
reader = TSIOBufferReaderAlloc(output_buffer);
/* This will print just MIMEFields and not
the http request line */
TSMimeHdrPrint(hdr_loc, output_buffer);
/* Find out how the big the complete header is by
seeing the total bytes in the buffer. We need to
look at the buffer rather than the first block to
see the size of the entire header */
total_avail = TSIOBufferReaderAvail(reader);
/* Allocate the string with an extra byte for the string
terminator */
output_string = static_cast<char *>(TSmalloc(total_avail + 1));
output_len = 0;
/* We need to loop over all the buffer blocks to make
sure we get the complete header since the header can
be in multiple blocks */
block = TSIOBufferReaderStart(reader);
while (block) {
block_start = TSIOBufferBlockReadStart(block, reader, &block_avail);
/* We'll get a block pointer back even if there is no data
left to read so check for this condition and break out of
the loop. A block with no data to read means we've exhausted
buffer of data since if there was more data on a later
block in the chain, this block would have been skipped over */
if (block_avail == 0) {
break;
}
memcpy(output_string + output_len, block_start, block_avail);
output_len += block_avail;
/* Consume the data so that we get to the next block */
TSIOBufferReaderConsume(reader, block_avail);
/* Get the next block now that we've consumed the
data off the last block */
block = TSIOBufferReaderStart(reader);
}
/* Terminate the string */
output_string[output_len] = '\0';
output_len++;
/* Free up the TSIOBuffer that we used to print out the header */
TSIOBufferReaderFree(reader);
TSIOBufferDestroy(output_buffer);
return output_string;
}
/*-----------------------------------------------------------------------------------------------*/
bool
has_trailing_parameter(TSMBuffer hdr_url_buf, TSMLoc hdr_url_loc)
{
bool bFound = false;
TSMLoc url_loc = TS_NULL_MLOC;
if (TSHttpHdrUrlGet(hdr_url_buf, hdr_url_loc, &url_loc) != TS_SUCCESS) {
SRDBG(TAG_BAD, "[%s] TSHttpHdrUrlGet failed", __FUNCTION__);
return bFound;
}
// create the new url
UrlComponents reqUrl;
reqUrl.populate(hdr_url_buf, url_loc);
string newQuery = reqUrl.getQuery();
string::size_type idx;
idx = newQuery.find(ASYNC_PARM);
if ((idx != string::npos) && (idx + ASYNC_PARM_LEN) == newQuery.length()) {
bFound = true;
}
TSHandleMLocRelease(hdr_url_buf, hdr_url_loc, url_loc);
SRDBG(TAG, "[%s] %d", __FUNCTION__, bFound);
return bFound;
}
/*-----------------------------------------------------------------------------------------------*/
void
add_trailing_parameter(TSMBuffer hdr_url_buf, TSMLoc hdr_url_loc)
{
TSMLoc url_loc = TS_NULL_MLOC;
if (TSHttpHdrUrlGet(hdr_url_buf, hdr_url_loc, &url_loc) != TS_SUCCESS) {
SRDBG(TAG_BAD, "[%s] TSHttpHdrUrlGet failed", __FUNCTION__);
return;
}
// create the new url
UrlComponents reqUrl;
reqUrl.populate(hdr_url_buf, url_loc);
string newQuery = reqUrl.getQuery();
if (newQuery.length()) {
newQuery += "&";
newQuery += ASYNC_PARM;
} else {
newQuery += ASYNC_PARM;
}
reqUrl.setQuery(newQuery);
string newUrl;
reqUrl.construct(newUrl);
// parse ans set the new url
const char *start = newUrl.c_str();
const char *end = newUrl.size() + start;
if (TSUrlParse(hdr_url_buf, url_loc, &start, end) != TS_PARSE_DONE) {
SRDBG(TAG_BAD, "[%s] TSUrlParse failed for [%s]", __FUNCTION__, newUrl.c_str());
TSHandleMLocRelease(hdr_url_buf, hdr_url_loc, url_loc);
return;
}
SRDBG(TAG, "[%s] [%s]", __FUNCTION__, newQuery.c_str());
TSHandleMLocRelease(hdr_url_buf, hdr_url_loc, url_loc);
}
/*-----------------------------------------------------------------------------------------------*/
bool
strip_trailing_parameter(TSMBuffer hdr_url_buf, TSMLoc hdr_url_loc)
{
bool stripped = false;
TSMLoc url_loc = TS_NULL_MLOC;
if (TSHttpHdrUrlGet(hdr_url_buf, hdr_url_loc, &url_loc) != TS_SUCCESS) {
SRDBG(TAG_BAD, "[%s] TSHttpHdrUrlGet failed", __FUNCTION__);
return stripped;
}
// create the new url
UrlComponents reqUrl;
reqUrl.populate(hdr_url_buf, url_loc);
string newQuery = reqUrl.getQuery();
string::size_type idx;
idx = newQuery.find(ASYNC_PARM);
if ((idx != string::npos) && (idx + ASYNC_PARM_LEN) == newQuery.length()) {
if (idx > 0) {
idx -= 1;
}
newQuery.erase(idx);
stripped = true;
}
if (stripped) {
// report only what actually happened: if the query could not be rewritten, nothing was stripped
if (TSUrlHttpQuerySet(hdr_url_buf, url_loc, newQuery.c_str(), newQuery.size()) != TS_SUCCESS) {
SRDBG(TAG_BAD, "[%s] TSUrlHttpQuerySet failed", __FUNCTION__);
TSError("stale_response [%s] failed to strip internal query parameter", __FUNCTION__);
stripped = false;
}
}
TSHandleMLocRelease(hdr_url_buf, hdr_url_loc, url_loc);
SRDBG(TAG, "[%s] stripped=%d [%s]", __FUNCTION__, stripped, newQuery.c_str());
return stripped;
}
/*-----------------------------------------------------------------------------------------------*/
void
fix_connection_close(StateInfo *state)
{
TSMLoc connection_hdr_loc, connection_hdr_dup_loc;
connection_hdr_loc = TSMimeHdrFieldFind(state->req_info->http_hdr_buf, state->req_info->http_hdr_loc, TS_MIME_FIELD_CONNECTION,
TS_MIME_LEN_CONNECTION);
while (connection_hdr_loc != TS_NULL_MLOC) {
SRDBG(TAG, "[%s] {%u} Found old Connection hdr", __FUNCTION__, state->req_info->key_hash);
connection_hdr_dup_loc =
TSMimeHdrFieldNextDup(state->req_info->http_hdr_buf, state->req_info->http_hdr_loc, connection_hdr_loc);
TSMimeHdrFieldRemove(state->req_info->http_hdr_buf, state->req_info->http_hdr_loc, connection_hdr_loc);
TSMimeHdrFieldDestroy(state->req_info->http_hdr_buf, state->req_info->http_hdr_loc, connection_hdr_loc);
TSHandleMLocRelease(state->req_info->http_hdr_buf, state->req_info->http_hdr_loc, connection_hdr_loc);
connection_hdr_loc = connection_hdr_dup_loc;
}
SRDBG(TAG, "[%s] {%u} Creating Connection:close hdr", __FUNCTION__, state->req_info->key_hash);
TSMimeHdrFieldCreateNamed(state->req_info->http_hdr_buf, state->req_info->http_hdr_loc, TS_MIME_FIELD_CONNECTION,
TS_MIME_LEN_CONNECTION, &connection_hdr_loc);
TSMimeHdrFieldValueStringInsert(state->req_info->http_hdr_buf, state->req_info->http_hdr_loc, connection_hdr_loc, -1,
TS_HTTP_VALUE_CLOSE, TS_HTTP_LEN_CLOSE);
TSMimeHdrFieldAppend(state->req_info->http_hdr_buf, state->req_info->http_hdr_loc, connection_hdr_loc);
TSHandleMLocRelease(state->req_info->http_hdr_buf, state->req_info->http_hdr_loc, connection_hdr_loc);
}
/*-----------------------------------------------------------------------------------------------*/
void
get_pristine_url(StateInfo *state)
{
TSHttpTxn txnp = state->txnp;
TSMBuffer hdr_url_buf;
TSMLoc url_loc;
// get the pristine url only works after remap state
if (TSHttpTxnPristineUrlGet(txnp, &hdr_url_buf, &url_loc) == TS_SUCCESS) {
int url_length;
char *url = TSUrlStringGet(hdr_url_buf, url_loc, &url_length);
state->pristine_url = TSstrndup(url, url_length);
TSfree(url);
// release the buffer and loc
TSHandleMLocRelease(hdr_url_buf, TS_NULL_MLOC, url_loc);
SRDBG(TAG, "[%s] {%u} pristine=[%s]", __FUNCTION__, state->req_info->key_hash, state->pristine_url);
} else {
SRDBG(TAG_BAD, "[%s] {%u} TSHttpTxnPristineUrlGet failed!", __FUNCTION__, state->req_info->key_hash);
}
}
/*-----------------------------------------------------------------------------------------------*/
bool
intercept_get_key(TSMBuffer bufp, TSMLoc hdr_loc, const char *name, int name_len, string &key)
{
bool retval = false;
TSMLoc field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, name, name_len);
if (field_loc) {
int value_len;
const char *value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, 0, &value_len);
key.append(value, value_len);
retval = true;
}
TSHandleMLocRelease(bufp, hdr_loc, field_loc);
// SRDBG(TAG, "[%s] key=[%s] found=%d",__FUNCTION__,name,key.c_str(),retval);
return retval;
}
/*-----------------------------------------------------------------------------------------------*/
BodyData *
intercept_check_request(StateInfo *state)
{
uint32_t newKey = 0;
BodyData *pBodyFound = nullptr;
TSHttpTxn txnp = state->txnp;
uint32_t oldKey = state->req_info->key_hash;
if (!TSHttpTxnIsInternal(txnp)) {
SRDBG(TAG, "[%s] Skipping external request", __FUNCTION__);
return pBodyFound;
}
TSMBuffer bufp;
TSMLoc hdr_loc;
if (TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
SRDBG(TAG_BAD, "[%s] TSHttpTxnClientReqGet failed!", __FUNCTION__);
return pBodyFound;
}
bool valid_request = false;
int method_len;
const char *method = TSHttpHdrMethodGet(bufp, hdr_loc, &method_len);
if (!method) {
SRDBG(TAG_BAD, "[%s] TSHttpHdrMethodGet failed!", __FUNCTION__);
} else {
if ((method_len == TS_HTTP_LEN_GET) && (strncasecmp(method, TS_HTTP_METHOD_GET, TS_HTTP_LEN_GET) == 0)) {
valid_request = true;
}
}
if (valid_request) {
string headerKey;
if (intercept_get_key(bufp, hdr_loc, SERVER_INTERCEPT_HEADER, SERVER_INTERCEPT_HEADER_LEN, headerKey)) {
base16_decode(reinterpret_cast<unsigned char *>(&newKey), headerKey.c_str(), headerKey.length());
pBodyFound = async_check_active(newKey, state->plugin_config);
if (pBodyFound) {
// header key can be differnt because of ATS port wierdness, so make state the same
state->req_info->key_hash = newKey;
} else {
SRDBG(TAG_BAD, "[%s] key miss %u this should not happen!", __FUNCTION__, newKey);
}
}
}
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
SRDBG(TAG, "[%s] {%u} oldKey=%u pBodyFound=%p", __FUNCTION__, newKey, oldKey, pBodyFound);
return pBodyFound;
}
/*-----------------------------------------------------------------------------------------------*/
bool
intercept_fetch_the_url(StateInfo *state)
{
bool bGood = false;
if (state->pristine_url == nullptr) {
SRDBG(TAG_BAD, "[%s] {%u} pristine url nullptr should not happen", __FUNCTION__, state->req_info->key_hash);
if (!async_remove_active(state->req_info->key_hash, state->plugin_config)) {
SRDBG(TAG_BAD, "[%s] didnt delete async active", __FUNCTION__);
}
return bGood;
}
// encode the key_hash should alwasy be 8 chars
char tmpStr[10];
base16_encode(tmpStr, reinterpret_cast<unsigned char const *>(&(state->req_info->key_hash)), 4);
// create the request as a string
string get_request("");
get_request.append(TS_HTTP_METHOD_GET);
get_request.append(" ");
get_request.append(state->pristine_url);
get_request.append(" HTTP/1.1\r\n");
get_request.append(SERVER_INTERCEPT_HEADER);
get_request.append(": ");
get_request.append(tmpStr, 8);
get_request.append("\r\n");
char *allReqHeaders = convert_mime_hdr_to_string(state->req_info->http_hdr_loc);
get_request.append(allReqHeaders);
TSfree(allReqHeaders);
get_request.append("\r\n");
// SRDBG(TAG, "[%s] req len %d ",__FUNCTION__,(int)get_request.length());
// SRDBG(TAG, "[%s] reg \r\n|%s|\r\n",__FUNCTION__,get_request.c_str());
BodyData *pBody = async_check_active(state->req_info->key_hash, state->plugin_config);
if (pBody) {
// SRDBG(TAG_BAD, "[%s] sleep 4",__FUNCTION__); sleep(4);
// This should be safe outside of locks
pBody->intercept_active = true;
TSFetchEvent event_ids = {0, 0, 0};
TSFetchUrl(get_request.data(), get_request.size(), state->req_info->client_addr, state->transaction_contp, NO_CALLBACK,
event_ids);
bGood = true;
SRDBG(TAG, "[%s] {%u} length=%d", __FUNCTION__, state->req_info->key_hash, (int)pBody->getSize());
} else {
SRDBG(TAG_BAD, "[%s] {%u} cant find body", __FUNCTION__, state->req_info->key_hash);
}
return bGood;
}
/*-----------------------------------------------------------------------------------------------*/