2 * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 61 Redirector */
12 #include "acl/Checklist.h"
14 #include "client_side.h"
15 #include "client_side_reply.h"
16 #include "client_side_request.h"
17 #include "comm/Connection.h"
19 #include "format/Format.h"
22 #include "helper/Reply.h"
23 #include "http/Stream.h"
24 #include "HttpRequest.h"
25 #include "mgr/Registration.h"
28 #include "sbuf/SBuf.h"
29 #include "SquidConfig.h"
32 #include "auth/UserRequest.h"
35 #include "ssl/support.h"
38 /// url maximum lengh + extra informations passed to redirector
39 #define MAX_REDIRECTOR_REQUEST_STRLEN (MAX_URL + 1024)
41 class RedirectStateData
43 CBDATA_CLASS(RedirectStateData
);
46 explicit RedirectStateData(const char *url
);
55 static HLPCB redirectHandleReply
;
56 static HLPCB storeIdHandleReply
;
57 static helper
*redirectors
= NULL
;
58 static helper
*storeIds
= NULL
;
59 static OBJH redirectStats
;
60 static OBJH storeIdStats
;
61 static int redirectorBypassed
= 0;
62 static int storeIdBypassed
= 0;
63 static Format::Format
*redirectorExtrasFmt
= NULL
;
64 static Format::Format
*storeIdExtrasFmt
= NULL
;
66 CBDATA_CLASS_INIT(RedirectStateData
);
68 RedirectStateData::RedirectStateData(const char *url
) :
75 RedirectStateData::~RedirectStateData()
80 redirectHandleReply(void *data
, const Helper::Reply
&reply
)
82 RedirectStateData
*r
= static_cast<RedirectStateData
*>(data
);
83 debugs(61, 5, HERE
<< "reply=" << reply
);
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
87 // it can be removed when the helpers are all updated to the normalized "OK/ERR kv-pairs" format
89 if (reply
.result
== Helper::Unknown
) {
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.
94 // * trim all but the first word off the response.
95 // * warn once every 50 responses that this will stop being fixed-up soon.
97 if (reply
.other().hasContent()) {
98 const char * res
= reply
.other().content();
100 if (const char *t
= strchr(res
, ' ')) {
102 debugs(61, (!(warn
++%50)? DBG_CRITICAL
:2), "UPGRADE WARNING: URL rewriter reponded with garbage '" << t
<<
103 "'. Future Squid will treat this as part of the URL.");
106 replySize
= reply
.other().contentSize();
108 // if we still have anything in other() after all that
109 // parse it into status=, url= and rewrite-url= keys
111 /* 2012-06-28: This cast is due to urlParse() truncating too-long URLs itself.
112 * At this point altering the helper buffer in that way is not harmful, but annoying.
113 * When Bug 1961 is resolved and urlParse has a const API, this needs to die.
116 replyBuffer
.init(replySize
, replySize
);
117 replyBuffer
.append(reply
.other().content(), reply
.other().contentSize());
118 char * result
= replyBuffer
.content();
120 Helper::Reply newReply
;
121 // BACKWARD COMPATIBILITY 2012-06-15:
122 // We got Helper::Unknown reply result but new
123 // RedirectStateData handlers require Helper::Okay,
124 // else will drop the helper reply
125 newReply
.result
= Helper::Okay
;
126 newReply
.notes
.append(&reply
.notes
);
128 // check and parse for obsoleted Squid-2 urlgroup feature
129 if (*result
== '!') {
130 static int urlgroupWarning
= 0;
131 if (!urlgroupWarning
++)
132 debugs(85, DBG_IMPORTANT
, "UPGRADE WARNING: URL rewriter using obsolete Squid-2 urlgroup feature needs updating.");
133 if (char *t
= strchr(result
+1, '!')) {
135 newReply
.notes
.add("urlgroup", result
+1);
140 const Http::StatusCode status
= static_cast<Http::StatusCode
>(atoi(result
));
142 if (status
== Http::scMovedPermanently
143 || status
== Http::scFound
144 || status
== Http::scSeeOther
145 || status
== Http::scPermanentRedirect
146 || status
== Http::scTemporaryRedirect
) {
148 if (const char *t
= strchr(result
, ':')) {
150 snprintf(statusBuf
, sizeof(statusBuf
),"%3u",status
);
151 newReply
.notes
.add("status", statusBuf
);
153 // TODO: validate the URL produced here is RFC 2616 compliant URI
154 newReply
.notes
.add("url", t
);
156 debugs(85, DBG_CRITICAL
, "ERROR: URL-rewrite produces invalid " << status
<< " redirect Location: " << result
);
159 // status code is not a redirect code (or does not exist)
160 // treat as a re-write URL request
161 // TODO: validate the URL produced here is RFC 2616 compliant URI
163 newReply
.notes
.add("rewrite-url", result
);
167 if (cbdataReferenceValidDone(r
->data
, &cbdata
))
168 r
->handler(cbdata
, newReply
);
177 if (cbdataReferenceValidDone(r
->data
, &cbdata
))
178 r
->handler(cbdata
, reply
);
184 storeIdHandleReply(void *data
, const Helper::Reply
&reply
)
186 RedirectStateData
*r
= static_cast<RedirectStateData
*>(data
);
187 debugs(61, 5,"StoreId helper: reply=" << reply
);
189 // XXX: This function is now kept only to check for and display the garbage use-case
190 // and to map the old helper response format(s) into new format result code and key=value pairs
191 // it can be removed when the helpers are all updated to the normalized "OK/ERR kv-pairs" format
193 if (cbdataReferenceValidDone(r
->data
, &cbdata
))
194 r
->handler(cbdata
, reply
);
200 redirectStats(StoreEntry
* sentry
)
202 if (redirectors
== NULL
) {
203 storeAppendPrintf(sentry
, "No redirectors defined\n");
207 redirectors
->packStatsInto(sentry
, "Redirector Statistics");
209 if (Config
.onoff
.redirector_bypass
)
210 storeAppendPrintf(sentry
, "\nNumber of requests bypassed "
211 "because all redirectors were busy: %d\n", redirectorBypassed
);
215 storeIdStats(StoreEntry
* sentry
)
217 if (storeIds
== NULL
) {
218 storeAppendPrintf(sentry
, "No StoreId helpers defined\n");
222 storeIds
->packStatsInto(sentry
, "StoreId helper Statistics");
224 if (Config
.onoff
.store_id_bypass
)
225 storeAppendPrintf(sentry
, "\nNumber of requests bypassed "
226 "because all StoreId helpers were busy: %d\n", storeIdBypassed
);
230 constructHelperQuery(const char *name
, helper
*hlp
, HLPCB
*replyHandler
, ClientHttpRequest
* http
, HLPCB
*handler
, void *data
, Format::Format
*requestExtrasFmt
)
232 char buf
[MAX_REDIRECTOR_REQUEST_STRLEN
];
234 Http::StatusCode status
;
236 /** TODO: create a standalone method to initialize
237 * the RedirectStateData for all the helpers.
239 RedirectStateData
*r
= new RedirectStateData(http
->uri
);
240 r
->handler
= handler
;
241 r
->data
= cbdataReference(data
);
243 static MemBuf requestExtras
;
244 requestExtras
.reset();
245 if (requestExtrasFmt
)
246 requestExtrasFmt
->assemble(requestExtras
, http
->al
, 0);
248 sz
= snprintf(buf
, MAX_REDIRECTOR_REQUEST_STRLEN
, "%s%s%s\n",
250 requestExtras
.hasContent() ? " " : "",
251 requestExtras
.hasContent() ? requestExtras
.content() : "");
253 if ((sz
<=0) || (sz
>=MAX_REDIRECTOR_REQUEST_STRLEN
)) {
255 status
= Http::scInternalServerError
;
256 debugs(61, DBG_CRITICAL
, "ERROR: Gateway Failure. Can not build request to be passed to " << name
<< ". Request ABORTED.");
258 status
= Http::scUriTooLong
;
259 debugs(61, DBG_CRITICAL
, "ERROR: Gateway Failure. Request passed to " << name
<< " exceeds MAX_REDIRECTOR_REQUEST_STRLEN (" << MAX_REDIRECTOR_REQUEST_STRLEN
<< "). Request ABORTED.");
262 clientStreamNode
*node
= (clientStreamNode
*)http
->client_stream
.tail
->prev
->data
;
263 clientReplyContext
*repContext
= dynamic_cast<clientReplyContext
*>(node
->data
.getRaw());
265 Ip::Address tmpnoaddr
;
266 tmpnoaddr
.setNoAddr();
267 repContext
->setReplyToError(ERR_GATEWAY_FAILURE
, status
,
268 http
->request
->method
, NULL
,
269 http
->getConn() != NULL
&& http
->getConn()->clientConnection
!= NULL
?
270 http
->getConn()->clientConnection
->remote
: tmpnoaddr
,
274 http
->getConn() != NULL
&& http
->getConn()->getAuth() != NULL
?
275 http
->getConn()->getAuth() : http
->request
->auth_user_request
);
280 node
= (clientStreamNode
*)http
->client_stream
.tail
->data
;
281 clientStreamRead(node
, http
, node
->readBuffer
);
285 debugs(61,6, HERE
<< "sending '" << buf
<< "' to the " << name
<< " helper");
286 helperSubmit(hlp
, buf
, replyHandler
, r
);
289 /**** PUBLIC FUNCTIONS ****/
292 redirectStart(ClientHttpRequest
* http
, HLPCB
* handler
, void *data
)
296 debugs(61, 5, "redirectStart: '" << http
->uri
<< "'");
298 // TODO: Deprecate Config.onoff.redirector_bypass in favor of either
299 // onPersistentOverload or a new onOverload option that applies to all helpers.
300 if (Config
.onoff
.redirector_bypass
&& redirectors
->willOverload()) {
301 /* Skip redirector if the queue is full */
302 ++redirectorBypassed
;
303 Helper::Reply bypassReply
;
304 bypassReply
.result
= Helper::Okay
;
305 bypassReply
.notes
.add("message","URL rewrite/redirect queue too long. Bypassed.");
306 handler(data
, bypassReply
);
310 constructHelperQuery("redirector", redirectors
, redirectHandleReply
, http
, handler
, data
, redirectorExtrasFmt
);
314 * Handles the StoreID feature helper starting.
315 * For now it cannot be done using the redirectStart method.
318 storeIdStart(ClientHttpRequest
* http
, HLPCB
* handler
, void *data
)
322 debugs(61, 5, "storeIdStart: '" << http
->uri
<< "'");
324 if (Config
.onoff
.store_id_bypass
&& storeIds
->willOverload()) {
325 /* Skip StoreID Helper if the queue is full */
327 Helper::Reply bypassReply
;
329 bypassReply
.result
= Helper::Okay
;
331 bypassReply
.notes
.add("message","StoreId helper queue too long. Bypassed.");
332 handler(data
, bypassReply
);
336 constructHelperQuery("storeId helper", storeIds
, storeIdHandleReply
, http
, handler
, data
, storeIdExtrasFmt
);
342 static bool init
= false;
345 Mgr::RegisterAction("redirector", "URL Redirector Stats", redirectStats
, 0, 1);
346 Mgr::RegisterAction("store_id", "StoreId helper Stats", storeIdStats
, 0, 1);
349 if (Config
.Program
.redirect
) {
351 if (redirectors
== NULL
)
352 redirectors
= new helper("redirector");
354 redirectors
->cmdline
= Config
.Program
.redirect
;
356 // BACKWARD COMPATIBILITY:
357 // if redirectot_bypass is set then use queue_size=0 as default size
358 if (Config
.onoff
.redirector_bypass
&& Config
.redirectChildren
.defaultQueueSize
)
359 Config
.redirectChildren
.queue_size
= 0;
361 redirectors
->childs
.updateLimits(Config
.redirectChildren
);
363 redirectors
->ipc_type
= IPC_STREAM
;
365 redirectors
->timeout
= Config
.Timeout
.urlRewrite
;
367 redirectors
->retryTimedOut
= (Config
.onUrlRewriteTimeout
.action
== toutActRetry
);
368 redirectors
->retryBrokenHelper
= true; // XXX: make this configurable ?
369 redirectors
->onTimedOutResponse
.clear();
370 if (Config
.onUrlRewriteTimeout
.action
== toutActUseConfiguredResponse
)
371 redirectors
->onTimedOutResponse
.assign(Config
.onUrlRewriteTimeout
.response
);
373 helperOpenServers(redirectors
);
376 if (Config
.Program
.store_id
) {
378 if (storeIds
== NULL
)
379 storeIds
= new helper("store_id");
381 storeIds
->cmdline
= Config
.Program
.store_id
;
383 // BACKWARD COMPATIBILITY:
384 // if store_id_bypass is set then use queue_size=0 as default size
385 if (Config
.onoff
.store_id_bypass
&& Config
.storeIdChildren
.defaultQueueSize
)
386 Config
.storeIdChildren
.queue_size
= 0;
388 storeIds
->childs
.updateLimits(Config
.storeIdChildren
);
390 storeIds
->ipc_type
= IPC_STREAM
;
392 storeIds
->retryBrokenHelper
= true; // XXX: make this configurable ?
394 helperOpenServers(storeIds
);
397 if (Config
.redirector_extras
) {
398 delete redirectorExtrasFmt
;
399 redirectorExtrasFmt
= new ::Format::Format("url_rewrite_extras");
400 (void)redirectorExtrasFmt
->parse(Config
.redirector_extras
);
403 if (Config
.storeId_extras
) {
404 delete storeIdExtrasFmt
;
405 storeIdExtrasFmt
= new ::Format::Format("store_id_extras");
406 (void)storeIdExtrasFmt
->parse(Config
.storeId_extras
);
413 redirectShutdown(void)
415 /** FIXME: Temporary unified helpers Shutdown
416 * When and if needed for more helpers a separated shutdown
417 * method will be added for each of them.
420 helperShutdown(redirectors
);
423 helperShutdown(storeIds
);
434 delete redirectorExtrasFmt
;
435 redirectorExtrasFmt
= NULL
;
437 delete storeIdExtrasFmt
;
438 storeIdExtrasFmt
= NULL
;