| /** @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; |
| } |
| |
| /*-----------------------------------------------------------------------------------------------*/ |