]> git.ipfire.org Git - thirdparty/squid.git/blame - src/redirect.cc
Maintenance: automate header guards 2/3 (#1655)
[thirdparty/squid.git] / src / redirect.cc
CommitLineData
30a4f2a8 1/*
b8ae064d 2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
e25c139f 3 *
bbc27441
AJ
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
30a4f2a8 7 */
8
bbc27441
AJ
9/* DEBUG: section 61 Redirector */
10
582c2af2 11#include "squid.h"
3ad63615 12#include "acl/Checklist.h"
32fd6d8a 13#include "cache_cf.h"
a46d2c0e 14#include "client_side.h"
7561850e 15#include "client_side_reply.h"
582c2af2
FC
16#include "client_side_request.h"
17#include "comm/Connection.h"
18#include "fde.h"
b11724bb 19#include "format/Format.h"
582c2af2 20#include "globals.h"
24438ec5
AJ
21#include "helper.h"
22#include "helper/Reply.h"
d3dddfb5 23#include "http/Stream.h"
582c2af2 24#include "HttpRequest.h"
582c2af2 25#include "mgr/Registration.h"
c548327a 26#include "redirect.h"
1fa9b1a7 27#include "rfc1738.h"
65e41a45 28#include "sbuf/SBuf.h"
4d5904f7 29#include "SquidConfig.h"
582c2af2
FC
30#include "Store.h"
31#if USE_AUTH
32#include "auth/UserRequest.h"
33#endif
cb4f4424 34#if USE_OPENSSL
4db984be
CT
35#include "ssl/support.h"
36#endif
30a4f2a8 37
2f8abb64 38/// url maximum length + extra information passed to redirector
7561850e
AJ
39#define MAX_REDIRECTOR_REQUEST_STRLEN (MAX_URL + 1024)
40
dd1efef8
AJ
41class RedirectStateData
42{
5c2f68b7
AJ
43 CBDATA_CLASS(RedirectStateData);
44
dd1efef8
AJ
45public:
46 explicit RedirectStateData(const char *url);
47 ~RedirectStateData();
48
d2af9477 49 void *data;
dd1efef8 50 SBuf orig_url;
62e76326 51
e166785a 52 HLPCB *handler;
dd1efef8 53};
30a4f2a8 54
74addf6c 55static HLPCB redirectHandleReply;
a8a0b1c2 56static HLPCB storeIdHandleReply;
e05a9d51
EB
57static Helper::Client::Pointer redirectors;
58static Helper::Client::Pointer storeIds;
74addf6c 59static OBJH redirectStats;
a8a0b1c2
EC
60static OBJH storeIdStats;
61static int redirectorBypassed = 0;
62static int storeIdBypassed = 0;
aee3523a
AR
63static Format::Format *redirectorExtrasFmt = nullptr;
64static Format::Format *storeIdExtrasFmt = nullptr;
dd1efef8
AJ
65
66CBDATA_CLASS_INIT(RedirectStateData);
67
68RedirectStateData::RedirectStateData(const char *url) :
aee3523a 69 data(nullptr),
f53969cc 70 orig_url(url),
aee3523a 71 handler(nullptr)
dd1efef8
AJ
72{
73}
74
75RedirectStateData::~RedirectStateData()
76{
77}
30a4f2a8 78
582b6456 79static void
24438ec5 80redirectHandleReply(void *data, const Helper::Reply &reply)
30a4f2a8 81{
dd1efef8 82 RedirectStateData *r = static_cast<RedirectStateData *>(data);
bf95c10a 83 debugs(61, 5, "reply=" << reply);
e166785a 84
d06e17ea
AJ
85 // XXX: This function is now kept only to check for and display the garbage use-case
86 // and to map the old helper response format(s) into new format result code and key=value pairs
fd7f26ea 87 // it can be removed when the helpers are all updated to the normalized "OK/ERR kv-pairs" format
e166785a 88
2428ce02 89 if (reply.result == Helper::Unknown) {
e166785a
AJ
90 // BACKWARD COMPATIBILITY 2012-06-15:
91 // Some nasty old helpers send back the entire input line including extra format keys.
92 // This is especially bad for simple perl search-replace filter scripts.
93 //
94 // * trim all but the first word off the response.
95 // * warn once every 50 responses that this will stop being fixed-up soon.
96 //
ddc77a2e
CT
97 if (reply.other().hasContent()) {
98 const char * res = reply.other().content();
99 size_t replySize = 0;
e166785a
AJ
100 if (const char *t = strchr(res, ' ')) {
101 static int warn = 0;
d816f28d 102 debugs(61, (!(warn++%50)? DBG_CRITICAL:2), "WARNING: UPGRADE: URL rewriter reponded with garbage '" << t <<
dacb64b9 103 "'. Future Squid will treat this as part of the URL.");
ddc77a2e
CT
104 replySize = t - res;
105 } else
106 replySize = reply.other().contentSize();
d06e17ea
AJ
107
108 // if we still have anything in other() after all that
109 // parse it into status=, url= and rewrite-url= keys
ddc77a2e 110 if (replySize) {
ddc77a2e
CT
111 MemBuf replyBuffer;
112 replyBuffer.init(replySize, replySize);
113 replyBuffer.append(reply.other().content(), reply.other().contentSize());
114 char * result = replyBuffer.content();
d06e17ea 115
24438ec5 116 Helper::Reply newReply;
95c0dbfc 117 // BACKWARD COMPATIBILITY 2012-06-15:
2428ce02
AJ
118 // We got Helper::Unknown reply result but new
119 // RedirectStateData handlers require Helper::Okay,
d74740bd 120 // else will drop the helper reply
2428ce02 121 newReply.result = Helper::Okay;
95c0dbfc 122 newReply.notes.append(&reply.notes);
d06e17ea 123
d074f918
TT
124 // check and parse for obsoleted Squid-2 urlgroup feature
125 if (*result == '!') {
75e71988
A
126 static int urlgroupWarning = 0;
127 if (!urlgroupWarning++)
d816f28d 128 debugs(85, DBG_IMPORTANT, "WARNING: UPGRADE: URL rewriter using obsolete Squid-2 urlgroup feature needs updating.");
75e71988
A
129 if (char *t = strchr(result+1, '!')) {
130 *t = '\0';
131 newReply.notes.add("urlgroup", result+1);
132 result = t + 1;
133 }
d074f918
TT
134 }
135
136 const Http::StatusCode status = static_cast<Http::StatusCode>(atoi(result));
137
955394ce 138 if (status == Http::scMovedPermanently
f11c8e2f 139 || status == Http::scFound
955394ce
AJ
140 || status == Http::scSeeOther
141 || status == Http::scPermanentRedirect
142 || status == Http::scTemporaryRedirect) {
d06e17ea
AJ
143
144 if (const char *t = strchr(result, ':')) {
145 char statusBuf[4];
146 snprintf(statusBuf, sizeof(statusBuf),"%3u",status);
147 newReply.notes.add("status", statusBuf);
148 ++t;
149 // TODO: validate the URL produced here is RFC 2616 compliant URI
150 newReply.notes.add("url", t);
151 } else {
152 debugs(85, DBG_CRITICAL, "ERROR: URL-rewrite produces invalid " << status << " redirect Location: " << result);
153 }
154 } else {
155 // status code is not a redirect code (or does not exist)
156 // treat as a re-write URL request
157 // TODO: validate the URL produced here is RFC 2616 compliant URI
d074f918
TT
158 if (*result)
159 newReply.notes.add("rewrite-url", result);
d06e17ea
AJ
160 }
161
162 void *cbdata;
163 if (cbdataReferenceValidDone(r->data, &cbdata))
164 r->handler(cbdata, newReply);
165
dd1efef8 166 delete r;
d06e17ea
AJ
167 return;
168 }
e166785a 169 }
c68e9c6b 170 }
62e76326 171
e166785a 172 void *cbdata;
fa80a8ef 173 if (cbdataReferenceValidDone(r->data, &cbdata))
62e76326 174 r->handler(cbdata, reply);
175
dd1efef8 176 delete r;
30a4f2a8 177}
178
a8a0b1c2 179static void
24438ec5 180storeIdHandleReply(void *data, const Helper::Reply &reply)
a8a0b1c2 181{
dd1efef8 182 RedirectStateData *r = static_cast<RedirectStateData *>(data);
a8a0b1c2
EC
183 debugs(61, 5,"StoreId helper: reply=" << reply);
184
185 // XXX: This function is now kept only to check for and display the garbage use-case
186 // and to map the old helper response format(s) into new format result code and key=value pairs
187 // it can be removed when the helpers are all updated to the normalized "OK/ERR kv-pairs" format
188 void *cbdata;
189 if (cbdataReferenceValidDone(r->data, &cbdata))
190 r->handler(cbdata, reply);
191
dd1efef8 192 delete r;
e7a22b88 193}
194
b8d8561b 195static void
74addf6c 196redirectStats(StoreEntry * sentry)
30a4f2a8 197{
aee3523a 198 if (redirectors == nullptr) {
587609e9 199 storeAppendPrintf(sentry, "No redirectors defined\n");
200 return;
201 }
202
bf3e8d5a 203 redirectors->packStatsInto(sentry, "Redirector Statistics");
62e76326 204
07476a7f 205 if (Config.onoff.redirector_bypass)
62e76326 206 storeAppendPrintf(sentry, "\nNumber of requests bypassed "
a8a0b1c2 207 "because all redirectors were busy: %d\n", redirectorBypassed);
30a4f2a8 208}
209
a8a0b1c2
EC
210static void
211storeIdStats(StoreEntry * sentry)
212{
aee3523a 213 if (storeIds == nullptr) {
a8a0b1c2
EC
214 storeAppendPrintf(sentry, "No StoreId helpers defined\n");
215 return;
216 }
d2af9477 217
bf3e8d5a 218 storeIds->packStatsInto(sentry, "StoreId helper Statistics");
a8a0b1c2
EC
219
220 if (Config.onoff.store_id_bypass)
221 storeAppendPrintf(sentry, "\nNumber of requests bypassed "
222 "because all StoreId helpers were busy: %d\n", storeIdBypassed);
223}
224
225static void
e05a9d51 226constructHelperQuery(const char * const name, const Helper::Client::Pointer &hlp, HLPCB * const replyHandler, ClientHttpRequest * const http, HLPCB * const handler, void * const data, Format::Format * const requestExtrasFmt)
d2af9477 227{
7561850e
AJ
228 char buf[MAX_REDIRECTOR_REQUEST_STRLEN];
229 int sz;
955394ce 230 Http::StatusCode status;
62e76326 231
a8a0b1c2 232 /** TODO: create a standalone method to initialize
dd1efef8 233 * the RedirectStateData for all the helpers.
a8a0b1c2 234 */
dd1efef8 235 RedirectStateData *r = new RedirectStateData(http->uri);
d2af9477 236 r->handler = handler;
fa80a8ef 237 r->data = cbdataReference(data);
62e76326 238
b11724bb
CT
239 static MemBuf requestExtras;
240 requestExtras.reset();
241 if (requestExtrasFmt)
242 requestExtrasFmt->assemble(requestExtras, http->al, 0);
243
244 sz = snprintf(buf, MAX_REDIRECTOR_REQUEST_STRLEN, "%s%s%s\n",
dd1efef8 245 r->orig_url.c_str(),
b11724bb
CT
246 requestExtras.hasContent() ? " " : "",
247 requestExtras.hasContent() ? requestExtras.content() : "");
7561850e
AJ
248
249 if ((sz<=0) || (sz>=MAX_REDIRECTOR_REQUEST_STRLEN)) {
e9f8ec2d 250 if (sz<=0) {
955394ce 251 status = Http::scInternalServerError;
a8a0b1c2 252 debugs(61, DBG_CRITICAL, "ERROR: Gateway Failure. Can not build request to be passed to " << name << ". Request ABORTED.");
e9f8ec2d 253 } else {
f11c8e2f 254 status = Http::scUriTooLong;
a8a0b1c2 255 debugs(61, DBG_CRITICAL, "ERROR: Gateway Failure. Request passed to " << name << " exceeds MAX_REDIRECTOR_REQUEST_STRLEN (" << MAX_REDIRECTOR_REQUEST_STRLEN << "). Request ABORTED.");
e9f8ec2d
A
256 }
257
258 clientStreamNode *node = (clientStreamNode *)http->client_stream.tail->prev->data;
259 clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
260 assert (repContext);
e9f8ec2d 261 repContext->setReplyToError(ERR_GATEWAY_FAILURE, status,
eb026889 262 nullptr,
7976fed3 263 http->getConn(),
e9f8ec2d 264 http->request,
aee3523a 265 nullptr,
2f1431ea 266#if USE_AUTH
aee3523a 267 http->getConn() != nullptr && http->getConn()->getAuth() != nullptr ?
cc1e110a 268 http->getConn()->getAuth() : http->request->auth_user_request);
2f1431ea 269#else
a1b1756c 270 nullptr);
2f1431ea 271#endif
e9f8ec2d
A
272
273 node = (clientStreamNode *)http->client_stream.tail->data;
274 clientStreamRead(node, http, node->readBuffer);
275 return;
7561850e 276 }
62e76326 277
bf95c10a 278 debugs(61,6, "sending '" << buf << "' to the " << name << " helper");
a8a0b1c2
EC
279 helperSubmit(hlp, buf, replyHandler, r);
280}
281
282/**** PUBLIC FUNCTIONS ****/
283
284void
285redirectStart(ClientHttpRequest * http, HLPCB * handler, void *data)
286{
287 assert(http);
288 assert(handler);
289 debugs(61, 5, "redirectStart: '" << http->uri << "'");
290
6082a0e2
EB
291 // TODO: Deprecate Config.onoff.redirector_bypass in favor of either
292 // onPersistentOverload or a new onOverload option that applies to all helpers.
293 if (Config.onoff.redirector_bypass && redirectors->willOverload()) {
6825b101 294 /* Skip redirector if the queue is full */
a8a0b1c2 295 ++redirectorBypassed;
24438ec5 296 Helper::Reply bypassReply;
2428ce02 297 bypassReply.result = Helper::Okay;
a8a0b1c2
EC
298 bypassReply.notes.add("message","URL rewrite/redirect queue too long. Bypassed.");
299 handler(data, bypassReply);
300 return;
301 }
302
b11724bb 303 constructHelperQuery("redirector", redirectors, redirectHandleReply, http, handler, data, redirectorExtrasFmt);
a8a0b1c2
EC
304}
305
306/**
307 * Handles the StoreID feature helper starting.
308 * For now it cannot be done using the redirectStart method.
309 */
310void
311storeIdStart(ClientHttpRequest * http, HLPCB * handler, void *data)
312{
313 assert(http);
314 assert(handler);
315 debugs(61, 5, "storeIdStart: '" << http->uri << "'");
316
6082a0e2 317 if (Config.onoff.store_id_bypass && storeIds->willOverload()) {
6825b101 318 /* Skip StoreID Helper if the queue is full */
a8a0b1c2 319 ++storeIdBypassed;
24438ec5 320 Helper::Reply bypassReply;
a8a0b1c2 321
2428ce02 322 bypassReply.result = Helper::Okay;
a8a0b1c2
EC
323
324 bypassReply.notes.add("message","StoreId helper queue too long. Bypassed.");
325 handler(data, bypassReply);
326 return;
327 }
328
b11724bb 329 constructHelperQuery("storeId helper", storeIds, storeIdHandleReply, http, handler, data, storeIdExtrasFmt);
0a21bd84 330}
331
b8d8561b 332void
74addf6c 333redirectInit(void)
30a4f2a8 334{
dd1efef8 335 static bool init = false;
d120ed12 336
dd1efef8
AJ
337 if (!init) {
338 Mgr::RegisterAction("redirector", "URL Redirector Stats", redirectStats, 0, 1);
339 Mgr::RegisterAction("store_id", "StoreId helper Stats", storeIdStats, 0, 1);
340 }
62e76326 341
a8a0b1c2 342 if (Config.Program.redirect) {
62e76326 343
aee3523a 344 if (redirectors == nullptr)
e05a9d51 345 redirectors = Helper::Client::Make("redirector");
62e76326 346
a8a0b1c2 347 redirectors->cmdline = Config.Program.redirect;
07eca7e0 348
6825b101
CT
349 // BACKWARD COMPATIBILITY:
350 // if redirectot_bypass is set then use queue_size=0 as default size
351 if (Config.onoff.redirector_bypass && Config.redirectChildren.defaultQueueSize)
352 Config.redirectChildren.queue_size = 0;
353
a8a0b1c2 354 redirectors->childs.updateLimits(Config.redirectChildren);
62e76326 355
a8a0b1c2
EC
356 redirectors->ipc_type = IPC_STREAM;
357
32fd6d8a
CT
358 redirectors->timeout = Config.Timeout.urlRewrite;
359
360 redirectors->retryTimedOut = (Config.onUrlRewriteTimeout.action == toutActRetry);
361 redirectors->retryBrokenHelper = true; // XXX: make this configurable ?
362 redirectors->onTimedOutResponse.clear();
363 if (Config.onUrlRewriteTimeout.action == toutActUseConfiguredResponse)
364 redirectors->onTimedOutResponse.assign(Config.onUrlRewriteTimeout.response);
365
bd71920d 366 redirectors->openSessions();
a8a0b1c2
EC
367 }
368
369 if (Config.Program.store_id) {
370
aee3523a 371 if (storeIds == nullptr)
e05a9d51 372 storeIds = Helper::Client::Make("store_id");
a8a0b1c2
EC
373
374 storeIds->cmdline = Config.Program.store_id;
375
6825b101
CT
376 // BACKWARD COMPATIBILITY:
377 // if store_id_bypass is set then use queue_size=0 as default size
378 if (Config.onoff.store_id_bypass && Config.storeIdChildren.defaultQueueSize)
379 Config.storeIdChildren.queue_size = 0;
380
a8a0b1c2
EC
381 storeIds->childs.updateLimits(Config.storeIdChildren);
382
383 storeIds->ipc_type = IPC_STREAM;
384
32fd6d8a
CT
385 storeIds->retryBrokenHelper = true; // XXX: make this configurable ?
386
bd71920d 387 storeIds->openSessions();
a8a0b1c2 388 }
62e76326 389
b11724bb 390 if (Config.redirector_extras) {
78338384 391 delete redirectorExtrasFmt;
fe7966ec 392 redirectorExtrasFmt = new ::Format::Format("url_rewrite_extras");
b11724bb
CT
393 (void)redirectorExtrasFmt->parse(Config.redirector_extras);
394 }
395
396 if (Config.storeId_extras) {
78338384 397 delete storeIdExtrasFmt;
fe7966ec 398 storeIdExtrasFmt = new ::Format::Format("store_id_extras");
b11724bb
CT
399 (void)storeIdExtrasFmt->parse(Config.storeId_extras);
400 }
401
dd1efef8 402 init = true;
30a4f2a8 403}
404
16d3fe0d 405void
74addf6c 406redirectShutdown(void)
30a4f2a8 407{
9837567d 408 /** TODO: Temporary unified helpers Shutdown
a8a0b1c2
EC
409 * When and if needed for more helpers a separated shutdown
410 * method will be added for each of them.
411 */
a8a0b1c2
EC
412 if (redirectors)
413 helperShutdown(redirectors);
414
415 if (storeIds)
416 helperShutdown(storeIds);
62e76326 417
838b993c 418 if (!shutting_down)
62e76326 419 return;
420
aee3523a 421 redirectors = nullptr;
a8a0b1c2 422
aee3523a 423 storeIds = nullptr;
a8a0b1c2 424
b11724bb 425 delete redirectorExtrasFmt;
aee3523a 426 redirectorExtrasFmt = nullptr;
b11724bb
CT
427
428 delete storeIdExtrasFmt;
aee3523a 429 storeIdExtrasFmt = nullptr;
30a4f2a8 430}
f53969cc 431
23da195f
CT
432void
433redirectReconfigure()
434{
435 redirectShutdown();
436 redirectInit();
437}
45e49ce3 438