Squid Web Cache master
Loading...
Searching...
No Matches
redirect.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2025 The Squid Software Foundation and contributors
3 *
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.
7 */
8
9/* DEBUG: section 61 Redirector */
10
11#include "squid.h"
12#include "acl/Checklist.h"
13#include "cache_cf.h"
14#include "client_side.h"
15#include "client_side_reply.h"
16#include "client_side_request.h"
17#include "clientStream.h"
18#include "comm/Connection.h"
19#include "fde.h"
21#include "globals.h"
22#include "helper.h"
23#include "helper/Reply.h"
24#include "http/Stream.h"
25#include "HttpRequest.h"
26#include "mgr/Registration.h"
27#include "redirect.h"
28#include "rfc1738.h"
29#include "sbuf/SBuf.h"
30#include "SquidConfig.h"
31#include "Store.h"
32#if USE_AUTH
33#include "auth/UserRequest.h"
34#endif
35#if USE_OPENSSL
36#include "ssl/support.h"
37#endif
38
40#define MAX_REDIRECTOR_REQUEST_STRLEN (MAX_URL + 1024)
41
43{
45
46public:
47 explicit RedirectStateData(const char *url);
49
50 void *data;
52
54};
55
62static int redirectorBypassed = 0;
63static int storeIdBypassed = 0;
66
68
70 data(nullptr),
71 orig_url(url),
72 handler(nullptr)
73{
74}
75
79
80static void
81redirectHandleReply(void *data, const Helper::Reply &reply)
82{
83 RedirectStateData *r = static_cast<RedirectStateData *>(data);
84 debugs(61, 5, "reply=" << reply);
85
86 // XXX: This function is now kept only to check for and display the garbage use-case
87 // and to map the old helper response format(s) into new format result code and key=value pairs
88 // it can be removed when the helpers are all updated to the normalized "OK/ERR kv-pairs" format
89
90 if (reply.result == Helper::Unknown) {
91 // BACKWARD COMPATIBILITY 2012-06-15:
92 // Some nasty old helpers send back the entire input line including extra format keys.
93 // This is especially bad for simple perl search-replace filter scripts.
94 //
95 // * trim all but the first word off the response.
96 // * warn once every 50 responses that this will stop being fixed-up soon.
97 //
98 if (reply.other().hasContent()) {
99 const char * res = reply.other().content();
100 size_t replySize = 0;
101 if (const char *t = strchr(res, ' ')) {
102 static int warn = 0;
103 debugs(61, (!(warn++%50)? DBG_CRITICAL:2), "WARNING: UPGRADE: URL rewriter responded with garbage '" << t <<
104 "'. Future Squid will treat this as part of the URL.");
105 replySize = t - res;
106 } else
107 replySize = reply.other().contentSize();
108
109 // if we still have anything in other() after all that
110 // parse it into status=, url= and rewrite-url= keys
111 if (replySize) {
112 MemBuf replyBuffer;
113 replyBuffer.init(replySize, replySize);
114 replyBuffer.append(reply.other().content(), reply.other().contentSize());
115 char * result = replyBuffer.content();
116
117 Helper::Reply newReply;
118 // BACKWARD COMPATIBILITY 2012-06-15:
119 // We got Helper::Unknown reply result but new
120 // RedirectStateData handlers require Helper::Okay,
121 // else will drop the helper reply
122 newReply.result = Helper::Okay;
123 newReply.notes.append(&reply.notes);
124
125 // check and parse for obsoleted Squid-2 urlgroup feature
126 if (*result == '!') {
127 static int urlgroupWarning = 0;
128 if (!urlgroupWarning++)
129 debugs(85, DBG_IMPORTANT, "WARNING: UPGRADE: URL rewriter using obsolete Squid-2 urlgroup feature needs updating.");
130 if (char *t = strchr(result+1, '!')) {
131 *t = '\0';
132 newReply.notes.add("urlgroup", result+1);
133 result = t + 1;
134 }
135 }
136
137 const Http::StatusCode status = static_cast<Http::StatusCode>(atoi(result));
138
139 if (status == Http::scMovedPermanently
140 || status == Http::scFound
141 || status == Http::scSeeOther
142 || status == Http::scPermanentRedirect
143 || status == Http::scTemporaryRedirect) {
144
145 if (const char *t = strchr(result, ':')) {
146 char statusBuf[4];
147 snprintf(statusBuf, sizeof(statusBuf),"%3u",status);
148 newReply.notes.add("status", statusBuf);
149 ++t;
150 // TODO: validate the URL produced here is RFC 2616 compliant URI
151 newReply.notes.add("url", t);
152 } else {
153 debugs(85, DBG_CRITICAL, "ERROR: URL-rewrite produces invalid " << status << " redirect Location: " << result);
154 }
155 } else {
156 // status code is not a redirect code (or does not exist)
157 // treat as a re-write URL request
158 // TODO: validate the URL produced here is RFC 2616 compliant URI
159 if (*result)
160 newReply.notes.add("rewrite-url", result);
161 }
162
163 void *cbdata;
165 r->handler(cbdata, newReply);
166
167 delete r;
168 return;
169 }
170 }
171 }
172
173 void *cbdata;
175 r->handler(cbdata, reply);
176
177 delete r;
178}
179
180static void
181storeIdHandleReply(void *data, const Helper::Reply &reply)
182{
183 RedirectStateData *r = static_cast<RedirectStateData *>(data);
184 debugs(61, 5,"StoreId helper: reply=" << reply);
185
186 // XXX: This function is now kept only to check for and display the garbage use-case
187 // and to map the old helper response format(s) into new format result code and key=value pairs
188 // it can be removed when the helpers are all updated to the normalized "OK/ERR kv-pairs" format
189 void *cbdata;
191 r->handler(cbdata, reply);
192
193 delete r;
194}
195
196static void
198{
199 if (redirectors == nullptr) {
200 storeAppendPrintf(sentry, "No redirectors defined\n");
201 return;
202 }
203
204 redirectors->packStatsInto(sentry, "Redirector Statistics");
205
207 storeAppendPrintf(sentry, "\nNumber of requests bypassed "
208 "because all redirectors were busy: %d\n", redirectorBypassed);
209}
210
211static void
213{
214 if (storeIds == nullptr) {
215 storeAppendPrintf(sentry, "No StoreId helpers defined\n");
216 return;
217 }
218
219 storeIds->packStatsInto(sentry, "StoreId helper Statistics");
220
222 storeAppendPrintf(sentry, "\nNumber of requests bypassed "
223 "because all StoreId helpers were busy: %d\n", storeIdBypassed);
224}
225
226static void
227constructHelperQuery(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)
228{
230 int sz;
231 Http::StatusCode status;
232
233 static MemBuf requestExtras;
234 requestExtras.reset();
235 if (requestExtrasFmt)
236 requestExtrasFmt->assemble(requestExtras, http->al, 0);
237
238 sz = snprintf(buf, MAX_REDIRECTOR_REQUEST_STRLEN, "%s%s%s\n",
239 http->uri,
240 requestExtras.hasContent() ? " " : "",
241 requestExtras.hasContent() ? requestExtras.content() : "");
242
243 if ((sz<=0) || (sz>=MAX_REDIRECTOR_REQUEST_STRLEN)) {
244 if (sz<=0) {
246 debugs(61, DBG_CRITICAL, "ERROR: Gateway Failure. Can not build request to be passed to " << name << ". Request ABORTED.");
247 } else {
248 status = Http::scUriTooLong;
249 debugs(61, DBG_CRITICAL, "ERROR: Gateway Failure. Request passed to " << name << " exceeds MAX_REDIRECTOR_REQUEST_STRLEN (" << MAX_REDIRECTOR_REQUEST_STRLEN << "). Request ABORTED.");
250 }
251
253 clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
254 assert (repContext);
255 repContext->setReplyToError(ERR_GATEWAY_FAILURE, status,
256 nullptr,
257 http->getConn(),
258 http->request,
259 nullptr,
260#if USE_AUTH
261 http->getConn() != nullptr && http->getConn()->getAuth() != nullptr ?
262 http->getConn()->getAuth() : http->request->auth_user_request);
263#else
264 nullptr);
265#endif
266
268 clientStreamRead(node, http, node->readBuffer);
269 return;
270 }
271
275 const auto r = new RedirectStateData(http->uri);
276 r->handler = handler;
277 r->data = cbdataReference(data);
278
279 debugs(61,6, "sending '" << buf << "' to the " << name << " helper");
280 helperSubmit(hlp, buf, replyHandler, r);
281}
282
283/**** PUBLIC FUNCTIONS ****/
284
285void
286redirectStart(ClientHttpRequest * http, HLPCB * handler, void *data)
287{
288 assert(http);
289 assert(handler);
290 debugs(61, 5, "redirectStart: '" << http->uri << "'");
291
292 // TODO: Deprecate Config.onoff.redirector_bypass in favor of either
293 // onPersistentOverload or a new onOverload option that applies to all helpers.
294 if (Config.onoff.redirector_bypass && redirectors->willOverload()) {
295 /* Skip redirector if the queue is full */
297 Helper::Reply bypassReply;
298 bypassReply.result = Helper::Okay;
299 bypassReply.notes.add("message","URL rewrite/redirect queue too long. Bypassed.");
300 handler(data, bypassReply);
301 return;
302 }
303
304 constructHelperQuery("redirector", redirectors, redirectHandleReply, http, handler, data, redirectorExtrasFmt);
305}
306
311void
312storeIdStart(ClientHttpRequest * http, HLPCB * handler, void *data)
313{
314 assert(http);
315 assert(handler);
316 debugs(61, 5, "storeIdStart: '" << http->uri << "'");
317
318 if (Config.onoff.store_id_bypass && storeIds->willOverload()) {
319 /* Skip StoreID Helper if the queue is full */
321 Helper::Reply bypassReply;
322
323 bypassReply.result = Helper::Okay;
324
325 bypassReply.notes.add("message","StoreId helper queue too long. Bypassed.");
326 handler(data, bypassReply);
327 return;
328 }
329
330 constructHelperQuery("storeId helper", storeIds, storeIdHandleReply, http, handler, data, storeIdExtrasFmt);
331}
332
333void
335{
336 static bool init = false;
337
338 if (!init) {
339 Mgr::RegisterAction("redirector", "URL Redirector Stats", redirectStats, 0, 1);
340 Mgr::RegisterAction("store_id", "StoreId helper Stats", storeIdStats, 0, 1);
341 }
342
343 if (Config.Program.redirect) {
344
345 if (redirectors == nullptr)
346 redirectors = Helper::Client::Make("redirector");
347
349
350 // BACKWARD COMPATIBILITY:
351 // if redirectot_bypass is set then use queue_size=0 as default size
354
355 redirectors->childs.updateLimits(Config.redirectChildren);
356
357 redirectors->ipc_type = IPC_STREAM;
358
360
362 redirectors->retryBrokenHelper = true; // XXX: make this configurable ?
363 redirectors->onTimedOutResponse.clear();
365 redirectors->onTimedOutResponse.assign(Config.onUrlRewriteTimeout.response);
366
367 redirectors->openSessions();
368 }
369
370 if (Config.Program.store_id) {
371
372 if (storeIds == nullptr)
373 storeIds = Helper::Client::Make("store_id");
374
375 storeIds->cmdline = Config.Program.store_id;
376
377 // BACKWARD COMPATIBILITY:
378 // if store_id_bypass is set then use queue_size=0 as default size
381
382 storeIds->childs.updateLimits(Config.storeIdChildren);
383
384 storeIds->ipc_type = IPC_STREAM;
385
386 storeIds->retryBrokenHelper = true; // XXX: make this configurable ?
387
388 storeIds->openSessions();
389 }
390
392 delete redirectorExtrasFmt;
393 redirectorExtrasFmt = new ::Format::Format("url_rewrite_extras");
395 }
396
398 delete storeIdExtrasFmt;
399 storeIdExtrasFmt = new ::Format::Format("store_id_extras");
401 }
402
403 init = true;
404}
405
406void
408{
413 if (redirectors)
415
416 if (storeIds)
418
419 if (!shutting_down)
420 return;
421
422 redirectors = nullptr;
423
424 storeIds = nullptr;
425
426 delete redirectorExtrasFmt;
427 redirectorExtrasFmt = nullptr;
428
429 delete storeIdExtrasFmt;
430 storeIdExtrasFmt = nullptr;
431}
432
433void
439
class SquidConfig Config
void warn(char *format,...)
#define assert(EX)
Definition assert.h:17
#define USE_AUTH
Definition autoconf.h:1502
#define cbdataReference(var)
Definition cbdata.h:348
#define CBDATA_CLASS_INIT(type)
Definition cbdata.h:325
#define cbdataReferenceValidDone(var, ptr)
Definition cbdata.h:239
#define CBDATA_CLASS(type)
Definition cbdata.h:289
HttpRequest *const request
ConnStateData * getConn() const
const AccessLogEntry::Pointer al
access.log entry
const Auth::UserRequest::Pointer & getAuth() const
bool parse(const char *def)
Definition Format.cc:66
void assemble(MemBuf &mb, const AccessLogEntryPointer &al, int logSequenceNumber) const
assemble the state information into a formatted line.
Definition Format.cc:377
unsigned int queue_size
Definition ChildConfig.h:91
static Pointer Make(const char *name)
Definition helper.cc:758
NotePairs notes
Definition Reply.h:62
Helper::ResultCode result
The helper response 'result' field.
Definition Reply.h:59
const MemBuf & other() const
Definition Reply.h:42
Auth::UserRequest::Pointer auth_user_request
void append(const char *c, int sz) override
Definition MemBuf.cc:209
void init(mb_size_t szInit, mb_size_t szMax)
Definition MemBuf.cc:93
char * content()
start of the added data
Definition MemBuf.h:41
mb_size_t contentSize() const
available data size
Definition MemBuf.h:47
void reset()
Definition MemBuf.cc:129
bool hasContent() const
Definition MemBuf.h:54
void append(const NotePairs *src)
Append the entries of the src NotePairs list to our list.
Definition Notes.cc:384
void add(const SBuf &key, const SBuf &value)
Definition Notes.cc:322
RedirectStateData(const char *url)
Definition redirect.cc:69
Definition SBuf.h:94
Helper::ChildConfig redirectChildren
wordlist * store_id
Helper::ChildConfig storeIdChildren
int store_id_bypass
char * storeId_extras
struct SquidConfig::@83 Program
int redirector_bypass
wordlist * redirect
struct SquidConfig::@77 Timeout
time_t urlRewrite
struct SquidConfig::@90 onoff
struct SquidConfig::UrlHelperTimeout onUrlRewriteTimeout
char * redirector_extras
void setReplyToError(err_type, Http::StatusCode, char const *, const ConnStateData *, HttpRequest *, const char *, Auth::UserRequest::Pointer)
builds error using clientBuildError() and calls setReplyToError() below
#define DBG_IMPORTANT
Definition Stream.h:38
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
#define DBG_CRITICAL
Definition Stream.h:37
#define IPC_STREAM
Definition defines.h:104
@ ERR_GATEWAY_FAILURE
Definition forward.h:67
int shutting_down
void clientStreamRead(clientStreamNode *thisObject, ClientHttpRequest *http, StoreIOBuffer readBuffer)
void HLPCB(void *, const Helper::Reply &)
Definition forward.h:33
void helperShutdown(const Helper::Client::Pointer &hlp)
Definition helper.cc:770
void helperSubmit(const Helper::Client::Pointer &hlp, const char *const buf, HLPCB *const callback, void *const data)
Definition helper.cc:481
void OBJH(StoreEntry *)
Definition forward.h:44
@ Unknown
Definition ResultCode.h:17
StatusCode
Definition StatusCode.h:20
@ scUriTooLong
Definition StatusCode.h:59
@ scFound
Definition StatusCode.h:39
@ scInternalServerError
Definition StatusCode.h:73
@ scPermanentRedirect
Definition StatusCode.h:44
@ scSeeOther
Definition StatusCode.h:40
@ scTemporaryRedirect
Definition StatusCode.h:43
@ scMovedPermanently
Definition StatusCode.h:38
void RegisterAction(char const *action, char const *desc, OBJH *handler, Protected, Atomic, Format)
static void constructHelperQuery(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)
Definition redirect.cc:227
static Format::Format * redirectorExtrasFmt
Definition redirect.cc:64
static HLPCB redirectHandleReply
Definition redirect.cc:56
void redirectInit(void)
Definition redirect.cc:334
static OBJH storeIdStats
Definition redirect.cc:61
static Helper::Client::Pointer storeIds
Definition redirect.cc:59
static HLPCB storeIdHandleReply
Definition redirect.cc:57
void storeIdStart(ClientHttpRequest *http, HLPCB *handler, void *data)
Definition redirect.cc:312
static Format::Format * storeIdExtrasFmt
Definition redirect.cc:65
static Helper::Client::Pointer redirectors
Definition redirect.cc:58
#define MAX_REDIRECTOR_REQUEST_STRLEN
url maximum length + extra information passed to redirector
Definition redirect.cc:40
void redirectReconfigure()
Definition redirect.cc:434
void redirectStart(ClientHttpRequest *http, HLPCB *handler, void *data)
Definition redirect.cc:286
static int storeIdBypassed
Definition redirect.cc:63
static int redirectorBypassed
Definition redirect.cc:62
void redirectShutdown(void)
Definition redirect.cc:407
static OBJH redirectStats
Definition redirect.cc:60
@ toutActUseConfiguredResponse
Definition redirect.h:16
@ toutActRetry
Definition redirect.h:16
void storeAppendPrintf(StoreEntry *e, const char *fmt,...)
Definition store.cc:855
Definition parse.c:104