]> git.ipfire.org Git - thirdparty/squid.git/blame - src/redirect.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / redirect.cc
CommitLineData
30a4f2a8 1/*
bde978a6 2 * Copyright (C) 1996-2015 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"
582c2af2 23#include "HttpRequest.h"
582c2af2 24#include "mgr/Registration.h"
c548327a 25#include "redirect.h"
1fa9b1a7 26#include "rfc1738.h"
dd1efef8 27#include "SBuf.h"
4d5904f7 28#include "SquidConfig.h"
582c2af2
FC
29#include "Store.h"
30#if USE_AUTH
31#include "auth/UserRequest.h"
32#endif
cb4f4424 33#if USE_OPENSSL
4db984be
CT
34#include "ssl/support.h"
35#endif
30a4f2a8 36
7561850e
AJ
37/// url maximum lengh + extra informations passed to redirector
38#define MAX_REDIRECTOR_REQUEST_STRLEN (MAX_URL + 1024)
39
dd1efef8
AJ
40class RedirectStateData
41{
5c2f68b7
AJ
42 CBDATA_CLASS(RedirectStateData);
43
dd1efef8
AJ
44public:
45 explicit RedirectStateData(const char *url);
46 ~RedirectStateData();
47
d2af9477 48 void *data;
dd1efef8 49 SBuf orig_url;
62e76326 50
e166785a 51 HLPCB *handler;
dd1efef8 52};
30a4f2a8 53
74addf6c 54static HLPCB redirectHandleReply;
a8a0b1c2 55static HLPCB storeIdHandleReply;
74addf6c 56static helper *redirectors = NULL;
a8a0b1c2 57static helper *storeIds = NULL;
74addf6c 58static OBJH redirectStats;
a8a0b1c2
EC
59static OBJH storeIdStats;
60static int redirectorBypassed = 0;
61static int storeIdBypassed = 0;
b11724bb
CT
62static Format::Format *redirectorExtrasFmt = NULL;
63static Format::Format *storeIdExtrasFmt = NULL;
dd1efef8
AJ
64
65CBDATA_CLASS_INIT(RedirectStateData);
66
67RedirectStateData::RedirectStateData(const char *url) :
f53969cc
SM
68 data(NULL),
69 orig_url(url),
70 handler(NULL)
dd1efef8
AJ
71{
72}
73
74RedirectStateData::~RedirectStateData()
75{
76}
30a4f2a8 77
582b6456 78static void
24438ec5 79redirectHandleReply(void *data, const Helper::Reply &reply)
30a4f2a8 80{
dd1efef8 81 RedirectStateData *r = static_cast<RedirectStateData *>(data);
e166785a
AJ
82 debugs(61, 5, HERE << "reply=" << reply);
83
d06e17ea
AJ
84 // XXX: This function is now kept only to check for and display the garbage use-case
85 // and to map the old helper response format(s) into new format result code and key=value pairs
fd7f26ea 86 // it can be removed when the helpers are all updated to the normalized "OK/ERR kv-pairs" format
e166785a 87
2428ce02 88 if (reply.result == Helper::Unknown) {
e166785a
AJ
89 // BACKWARD COMPATIBILITY 2012-06-15:
90 // Some nasty old helpers send back the entire input line including extra format keys.
91 // This is especially bad for simple perl search-replace filter scripts.
92 //
93 // * trim all but the first word off the response.
94 // * warn once every 50 responses that this will stop being fixed-up soon.
95 //
96 if (const char * res = reply.other().content()) {
97 if (const char *t = strchr(res, ' ')) {
98 static int warn = 0;
99 debugs(61, (!(warn++%50)? DBG_CRITICAL:2), "UPGRADE WARNING: URL rewriter reponded with garbage '" << t <<
dacb64b9 100 "'. Future Squid will treat this as part of the URL.");
e166785a
AJ
101 const mb_size_t garbageLength = reply.other().contentSize() - (t-res);
102 reply.modifiableOther().truncate(garbageLength);
103 }
104 if (reply.other().hasContent() && *res == '\0')
105 reply.modifiableOther().clean(); // drop the whole buffer of garbage.
d06e17ea
AJ
106
107 // if we still have anything in other() after all that
108 // parse it into status=, url= and rewrite-url= keys
109 if (reply.other().hasContent()) {
110 /* 2012-06-28: This cast is due to urlParse() truncating too-long URLs itself.
111 * At this point altering the helper buffer in that way is not harmful, but annoying.
112 * When Bug 1961 is resolved and urlParse has a const API, this needs to die.
113 */
d074f918 114 char * result = reply.modifiableOther().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++)
128 debugs(85, DBG_IMPORTANT, "UPGRADE WARNING: URL rewriter using obsolete Squid-2 urlgroup feature needs updating.");
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{
587609e9 198 if (redirectors == NULL) {
199 storeAppendPrintf(sentry, "No redirectors defined\n");
200 return;
201 }
202
9522b380 203 helperStats(sentry, redirectors, "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{
213 if (storeIds == NULL) {
214 storeAppendPrintf(sentry, "No StoreId helpers defined\n");
215 return;
216 }
d2af9477 217
a8a0b1c2
EC
218 helperStats(sentry, storeIds, "StoreId helper Statistics");
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
b11724bb 226constructHelperQuery(const char *name, helper *hlp, HLPCB *replyHandler, ClientHttpRequest * http, HLPCB *handler, void *data, Format::Format *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);
261 Ip::Address tmpnoaddr;
4dd643d5 262 tmpnoaddr.setNoAddr();
e9f8ec2d
A
263 repContext->setReplyToError(ERR_GATEWAY_FAILURE, status,
264 http->request->method, NULL,
1b76e6c1
AJ
265 http->getConn() != NULL && http->getConn()->clientConnection != NULL ?
266 http->getConn()->clientConnection->remote : tmpnoaddr,
e9f8ec2d
A
267 http->request,
268 NULL,
2f1431ea 269#if USE_AUTH
cc1e110a
AJ
270 http->getConn() != NULL && http->getConn()->getAuth() != NULL ?
271 http->getConn()->getAuth() : http->request->auth_user_request);
2f1431ea
AJ
272#else
273 NULL);
274#endif
e9f8ec2d
A
275
276 node = (clientStreamNode *)http->client_stream.tail->data;
277 clientStreamRead(node, http, node->readBuffer);
278 return;
7561850e 279 }
62e76326 280
a8a0b1c2
EC
281 debugs(61,6, HERE << "sending '" << buf << "' to the " << name << " helper");
282 helperSubmit(hlp, buf, replyHandler, r);
283}
284
285/**** PUBLIC FUNCTIONS ****/
286
287void
288redirectStart(ClientHttpRequest * http, HLPCB * handler, void *data)
289{
290 assert(http);
291 assert(handler);
292 debugs(61, 5, "redirectStart: '" << http->uri << "'");
293
6825b101
CT
294 if (Config.onoff.redirector_bypass && redirectors->queueFull()) {
295 /* Skip redirector if the queue is full */
a8a0b1c2 296 ++redirectorBypassed;
24438ec5 297 Helper::Reply bypassReply;
2428ce02 298 bypassReply.result = Helper::Okay;
a8a0b1c2
EC
299 bypassReply.notes.add("message","URL rewrite/redirect queue too long. Bypassed.");
300 handler(data, bypassReply);
301 return;
302 }
303
b11724bb 304 constructHelperQuery("redirector", redirectors, redirectHandleReply, http, handler, data, redirectorExtrasFmt);
a8a0b1c2
EC
305}
306
307/**
308 * Handles the StoreID feature helper starting.
309 * For now it cannot be done using the redirectStart method.
310 */
311void
312storeIdStart(ClientHttpRequest * http, HLPCB * handler, void *data)
313{
314 assert(http);
315 assert(handler);
316 debugs(61, 5, "storeIdStart: '" << http->uri << "'");
317
6825b101
CT
318 if (Config.onoff.store_id_bypass && storeIds->queueFull()) {
319 /* Skip StoreID Helper if the queue is full */
a8a0b1c2 320 ++storeIdBypassed;
24438ec5 321 Helper::Reply bypassReply;
a8a0b1c2 322
2428ce02 323 bypassReply.result = Helper::Okay;
a8a0b1c2
EC
324
325 bypassReply.notes.add("message","StoreId helper queue too long. Bypassed.");
326 handler(data, bypassReply);
327 return;
328 }
329
b11724bb 330 constructHelperQuery("storeId helper", storeIds, storeIdHandleReply, http, handler, data, storeIdExtrasFmt);
0a21bd84 331}
332
b8d8561b 333void
74addf6c 334redirectInit(void)
30a4f2a8 335{
dd1efef8 336 static bool init = false;
d120ed12 337
dd1efef8
AJ
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 }
62e76326 342
a8a0b1c2 343 if (Config.Program.redirect) {
62e76326 344
a8a0b1c2
EC
345 if (redirectors == NULL)
346 redirectors = new helper("redirector");
62e76326 347
a8a0b1c2 348 redirectors->cmdline = Config.Program.redirect;
07eca7e0 349
6825b101
CT
350 // BACKWARD COMPATIBILITY:
351 // if redirectot_bypass is set then use queue_size=0 as default size
352 if (Config.onoff.redirector_bypass && Config.redirectChildren.defaultQueueSize)
353 Config.redirectChildren.queue_size = 0;
354
a8a0b1c2 355 redirectors->childs.updateLimits(Config.redirectChildren);
62e76326 356
a8a0b1c2
EC
357 redirectors->ipc_type = IPC_STREAM;
358
32fd6d8a
CT
359 redirectors->timeout = Config.Timeout.urlRewrite;
360
361 redirectors->retryTimedOut = (Config.onUrlRewriteTimeout.action == toutActRetry);
362 redirectors->retryBrokenHelper = true; // XXX: make this configurable ?
363 redirectors->onTimedOutResponse.clear();
364 if (Config.onUrlRewriteTimeout.action == toutActUseConfiguredResponse)
365 redirectors->onTimedOutResponse.assign(Config.onUrlRewriteTimeout.response);
366
a8a0b1c2
EC
367 helperOpenServers(redirectors);
368 }
369
370 if (Config.Program.store_id) {
371
372 if (storeIds == NULL)
373 storeIds = new helper("store_id");
374
375 storeIds->cmdline = Config.Program.store_id;
376
6825b101
CT
377 // BACKWARD COMPATIBILITY:
378 // if store_id_bypass is set then use queue_size=0 as default size
379 if (Config.onoff.store_id_bypass && Config.storeIdChildren.defaultQueueSize)
380 Config.storeIdChildren.queue_size = 0;
381
a8a0b1c2
EC
382 storeIds->childs.updateLimits(Config.storeIdChildren);
383
384 storeIds->ipc_type = IPC_STREAM;
385
32fd6d8a
CT
386 storeIds->retryBrokenHelper = true; // XXX: make this configurable ?
387
a8a0b1c2
EC
388 helperOpenServers(storeIds);
389 }
62e76326 390
b11724bb 391 if (Config.redirector_extras) {
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) {
fe7966ec 397 storeIdExtrasFmt = new ::Format::Format("store_id_extras");
b11724bb
CT
398 (void)storeIdExtrasFmt->parse(Config.storeId_extras);
399 }
400
dd1efef8 401 init = true;
30a4f2a8 402}
403
16d3fe0d 404void
74addf6c 405redirectShutdown(void)
30a4f2a8 406{
a8a0b1c2
EC
407 /** FIXME: Temporary unified helpers Shutdown
408 * When and if needed for more helpers a separated shutdown
409 * method will be added for each of them.
410 */
411 if (!storeIds && !redirectors)
62e76326 412 return;
413
a8a0b1c2
EC
414 if (redirectors)
415 helperShutdown(redirectors);
416
417 if (storeIds)
418 helperShutdown(storeIds);
62e76326 419
838b993c 420 if (!shutting_down)
62e76326 421 return;
422
48d54e4d 423 delete redirectors;
1f5f60dd 424 redirectors = NULL;
a8a0b1c2
EC
425
426 delete storeIds;
427 storeIds = NULL;
428
b11724bb
CT
429 delete redirectorExtrasFmt;
430 redirectorExtrasFmt = NULL;
431
432 delete storeIdExtrasFmt;
433 storeIdExtrasFmt = NULL;
30a4f2a8 434}
f53969cc 435