blob: 0227e29e303b78229d35aabd44350aee85a3b4f6 [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 "pagespeed/apache/header_util.h"
#include <cstddef>
#include <cstdio>
#include <memory>
#include <utility>
#include "apr_strings.h"
#include "base/logging.h"
#include "http_core.h"
#include "http_protocol.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/callback.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/http/caching_headers.h"
#include "pagespeed/kernel/http/http_names.h"
#include "pagespeed/kernel/http/request_headers.h"
#include "pagespeed/kernel/http/response_headers.h"
namespace net_instaweb {
namespace {
typedef std::pair<RequestHeaders*, HeaderPredicateFn*> RequestPredicatePair;
int AddAttributeCallback(void* rec, const char* key, const char* value) {
RequestPredicatePair* rpp = static_cast<RequestPredicatePair*>(rec);
bool ok = true;
if (rpp->second != NULL) {
ok = false; // Default to false if the predicate doesn't set *ok.
rpp->second->Run(key, &ok);
}
if (ok) {
rpp->first->Add(key, value);
}
return 1;
}
int AddResponseAttributeCallback(void* rec, const char* key,
const char* value) {
ResponseHeaders* response_headers = static_cast<ResponseHeaders*>(rec);
response_headers->Add(key, value);
return 1;
}
template <typename T>
void FixUpH2Version(T* headers) {
// mod_h2 sets protocol version to 2.0 when h2 (or h2c) are in use.
// This conservatively sets it back to 1.1 in the header objects
// (with h2 support kept track of separately), for defensive reasons:
// The only place the major.minor protocol version shows up is in HTTP*1*
// messages, and talking about HTTP/2.0 in them makes no sense.
if (headers->major_version() == 2 && headers->minor_version() == 0) {
headers->set_major_version(1);
headers->set_minor_version(1);
}
}
} // namespace
void ApacheRequestToRequestHeaders(const request_rec& request,
RequestHeaders* request_headers,
HeaderPredicateFn* predicate) {
RequestPredicatePair rpp(request_headers, predicate);
if (request.proto_num >= 1000) {
// proto_num is the version number of protocol; 1.1 = 1001
request_headers->set_major_version(request.proto_num / 1000);
request_headers->set_minor_version(request.proto_num % 1000);
FixUpH2Version(request_headers);
}
apr_table_do(AddAttributeCallback, &rpp, request.headers_in, NULL);
}
void ApacheRequestToResponseHeaders(const request_rec& request,
ResponseHeaders* headers,
ResponseHeaders* err_headers) {
headers->set_status_code(request.status);
if (request.proto_num >= 1000) {
// proto_num is the version number of protocol; 1.1 = 1001
headers->set_major_version(request.proto_num / 1000);
headers->set_minor_version(request.proto_num % 1000);
FixUpH2Version(headers);
}
apr_table_do(AddResponseAttributeCallback, headers, request.headers_out,
NULL);
if (err_headers != nullptr) {
apr_table_do(AddResponseAttributeCallback, err_headers,
request.err_headers_out, NULL);
}
}
void AddResponseHeadersToRequestHelper(const ResponseHeaders& response_headers,
request_rec* request,
apr_table_t* table) {
for (int i = 0, n = response_headers.NumAttributes(); i < n; ++i) {
const GoogleString& name = response_headers.Name(i);
const GoogleString& value = response_headers.Value(i);
if (StringCaseEqual(name, HttpAttributes::kContentType)) {
// ap_set_content_type does not make a copy of the string, we need
// to duplicate it.
char* ptr = apr_pstrdup(request->pool, value.c_str());
ap_set_content_type(request, ptr);
} else {
// apr_table_add makes copies of both head key and value, so we do not
// have to duplicate them.
apr_table_add(table, name.c_str(), value.c_str());
}
}
}
void ResponseHeadersToApacheRequest(const ResponseHeaders& response_headers,
request_rec* request) {
AddResponseHeadersToRequestHelper(response_headers, request,
request->headers_out);
}
void ErrorHeadersToApacheRequest(const ResponseHeaders& err_response_headers,
request_rec* request) {
AddResponseHeadersToRequestHelper(err_response_headers, request,
request->err_headers_out);
}
void DisableDownstreamHeaderFilters(request_rec* request) {
// Prevent downstream filters from corrupting our headers.
ap_filter_t* filter = request->output_filters;
while (filter != nullptr) {
ap_filter_t* next = filter->next;
if ((StringCaseEqual(filter->frec->name, "MOD_EXPIRES")) ||
(StringCaseEqual(filter->frec->name, "FIXUP_HEADERS_OUT"))) {
ap_remove_output_filter(filter);
}
filter = next;
}
}
int PrintAttributeCallback(void* rec, const char* key, const char* value) {
fprintf(stdout, " %s: %s\n", key, value);
return 1;
}
// This routine is intended for debugging so fprintf to stdout is the way
// to get instant feedback.
void PrintHeaders(request_rec* request) {
puts("Input headers:");
apr_table_do(PrintAttributeCallback, nullptr, request->headers_in, NULL);
puts("Output headers:");
apr_table_do(PrintAttributeCallback, nullptr, request->headers_out, NULL);
puts("Err_Output headers:");
apr_table_do(PrintAttributeCallback, nullptr, request->err_headers_out, NULL);
fflush(stdout);
}
int StringAttributeCallback(void* rec, const char* key, const char* value) {
GoogleString* out = static_cast<GoogleString*>(rec);
out->append(key);
out->append(": ");
out->append(value);
out->append("\n");
return 1;
}
GoogleString HeadersOutToString(request_rec* request) {
GoogleString out;
apr_table_do(StringAttributeCallback, &out, request->headers_out, NULL);
return out;
}
GoogleString SubprocessEnvToString(request_rec* request) {
GoogleString out;
apr_table_do(StringAttributeCallback, &out, request->subprocess_env, NULL);
return out;
}
class ApacheCachingHeaders : public CachingHeaders {
public:
explicit ApacheCachingHeaders(request_rec* request)
: CachingHeaders(request->status), request_(request) {}
bool Lookup(const StringPiece& key, StringPieceVector* values) override {
const char* value =
apr_table_get(request_->headers_out, key.as_string().c_str());
if (value == nullptr) {
return false;
}
SplitStringPieceToVector(value, ",", values, true);
for (int i = 0, n = values->size(); i < n; ++i) {
TrimWhitespace(&((*values)[i]));
}
return true;
}
bool IsLikelyStaticResourceType() const override {
DCHECK(false); // not called in our use-case.
return false;
}
bool IsCacheableResourceStatusCode() const override {
DCHECK(false); // not called in our use-case.
return false;
}
private:
request_rec* request_;
DISALLOW_COPY_AND_ASSIGN(ApacheCachingHeaders);
};
void DisableCacheControlHeader(request_rec* request) {
// Turn off Cache-Control header for the HTTP requests.
ApacheCachingHeaders headers(request);
apr_table_set(request->headers_out, HttpAttributes::kCacheControl,
headers.GenerateDisabledCacheControl().c_str());
}
void DisableCachingRelatedHeaders(request_rec* request) {
// Turn off headers related to caching (but not Cache-Control) for the
// HTTP requests.
StringPieceVector response_headers_to_remove =
HttpAttributes::CachingHeadersToBeRemoved();
for (int i = 0, n = response_headers_to_remove.size(); i < n; ++i) {
apr_table_unset(request->headers_out,
response_headers_to_remove[i].as_string().c_str());
}
}
} // namespace net_instaweb