2 * Copyright (C) 1996-2014 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"
13 #include "client_side.h"
14 #include "client_side_reply.h"
15 #include "client_side_request.h"
16 #include "comm/Connection.h"
18 #include "format/Format.h"
20 #include "HttpRequest.h"
21 #include "mgr/Registration.h"
25 #include "SquidConfig.h"
28 #include "auth/UserRequest.h"
31 #include "ssl/support.h"
34 /// url maximum lengh + extra informations passed to redirector
35 #define MAX_REDIRECTOR_REQUEST_STRLEN (MAX_URL + 1024)
37 class RedirectStateData
40 explicit RedirectStateData(const char *url
);
49 CBDATA_CLASS2(RedirectStateData
);
52 static HLPCB redirectHandleReply
;
53 static HLPCB storeIdHandleReply
;
54 static helper
*redirectors
= NULL
;
55 static helper
*storeIds
= NULL
;
56 static OBJH redirectStats
;
57 static OBJH storeIdStats
;
58 static int redirectorBypassed
= 0;
59 static int storeIdBypassed
= 0;
60 static Format::Format
*redirectorExtrasFmt
= NULL
;
61 static Format::Format
*storeIdExtrasFmt
= NULL
;
63 CBDATA_CLASS_INIT(RedirectStateData
);
65 RedirectStateData::RedirectStateData(const char *url
) :
72 RedirectStateData::~RedirectStateData()
77 redirectHandleReply(void *data
, const HelperReply
&reply
)
79 RedirectStateData
*r
= static_cast<RedirectStateData
*>(data
);
80 debugs(61, 5, HERE
<< "reply=" << reply
);
82 // XXX: This function is now kept only to check for and display the garbage use-case
83 // and to map the old helper response format(s) into new format result code and key=value pairs
84 // it can be removed when the helpers are all updated to the normalized "OK/ERR kv-pairs" format
86 if (reply
.result
== HelperReply::Unknown
) {
87 // BACKWARD COMPATIBILITY 2012-06-15:
88 // Some nasty old helpers send back the entire input line including extra format keys.
89 // This is especially bad for simple perl search-replace filter scripts.
91 // * trim all but the first word off the response.
92 // * warn once every 50 responses that this will stop being fixed-up soon.
94 if (const char * res
= reply
.other().content()) {
95 if (const char *t
= strchr(res
, ' ')) {
97 debugs(61, (!(warn
++%50)? DBG_CRITICAL
:2), "UPGRADE WARNING: URL rewriter reponded with garbage '" << t
<<
98 "'. Future Squid will treat this as part of the URL.");
99 const mb_size_t garbageLength
= reply
.other().contentSize() - (t
-res
);
100 reply
.modifiableOther().truncate(garbageLength
);
102 if (reply
.other().hasContent() && *res
== '\0')
103 reply
.modifiableOther().clean(); // drop the whole buffer of garbage.
105 // if we still have anything in other() after all that
106 // parse it into status=, url= and rewrite-url= keys
107 if (reply
.other().hasContent()) {
108 /* 2012-06-28: This cast is due to urlParse() truncating too-long URLs itself.
109 * At this point altering the helper buffer in that way is not harmful, but annoying.
110 * When Bug 1961 is resolved and urlParse has a const API, this needs to die.
112 char * result
= reply
.modifiableOther().content();
114 HelperReply newReply
;
115 // BACKWARD COMPATIBILITY 2012-06-15:
116 // We got HelperReply::Unknown reply result but new
117 // RedirectStateData handlers require HelperReply::Okay,
118 // else will drop the helper reply
119 newReply
.result
= HelperReply::Okay
;
120 newReply
.notes
.append(&reply
.notes
);
122 // check and parse for obsoleted Squid-2 urlgroup feature
123 if (*result
== '!') {
124 static int urlgroupWarning
= 0;
125 if (!urlgroupWarning
++)
126 debugs(85, DBG_IMPORTANT
, "UPGRADE WARNING: URL rewriter using obsolete Squid-2 urlgroup feature needs updating.");
127 if (char *t
= strchr(result
+1, '!')) {
129 newReply
.notes
.add("urlgroup", result
+1);
134 const Http::StatusCode status
= static_cast<Http::StatusCode
>(atoi(result
));
136 if (status
== Http::scMovedPermanently
137 || status
== Http::scFound
138 || status
== Http::scSeeOther
139 || status
== Http::scPermanentRedirect
140 || status
== Http::scTemporaryRedirect
) {
142 if (const char *t
= strchr(result
, ':')) {
144 snprintf(statusBuf
, sizeof(statusBuf
),"%3u",status
);
145 newReply
.notes
.add("status", statusBuf
);
147 // TODO: validate the URL produced here is RFC 2616 compliant URI
148 newReply
.notes
.add("url", t
);
150 debugs(85, DBG_CRITICAL
, "ERROR: URL-rewrite produces invalid " << status
<< " redirect Location: " << result
);
153 // status code is not a redirect code (or does not exist)
154 // treat as a re-write URL request
155 // TODO: validate the URL produced here is RFC 2616 compliant URI
157 newReply
.notes
.add("rewrite-url", result
);
161 if (cbdataReferenceValidDone(r
->data
, &cbdata
))
162 r
->handler(cbdata
, newReply
);
171 if (cbdataReferenceValidDone(r
->data
, &cbdata
))
172 r
->handler(cbdata
, reply
);
178 storeIdHandleReply(void *data
, const HelperReply
&reply
)
180 RedirectStateData
*r
= static_cast<RedirectStateData
*>(data
);
181 debugs(61, 5,"StoreId helper: reply=" << reply
);
183 // XXX: This function is now kept only to check for and display the garbage use-case
184 // and to map the old helper response format(s) into new format result code and key=value pairs
185 // it can be removed when the helpers are all updated to the normalized "OK/ERR kv-pairs" format
187 if (cbdataReferenceValidDone(r
->data
, &cbdata
))
188 r
->handler(cbdata
, reply
);
194 redirectStats(StoreEntry
* sentry
)
196 if (redirectors
== NULL
) {
197 storeAppendPrintf(sentry
, "No redirectors defined\n");
201 helperStats(sentry
, redirectors
, "Redirector Statistics");
203 if (Config
.onoff
.redirector_bypass
)
204 storeAppendPrintf(sentry
, "\nNumber of requests bypassed "
205 "because all redirectors were busy: %d\n", redirectorBypassed
);
209 storeIdStats(StoreEntry
* sentry
)
211 if (storeIds
== NULL
) {
212 storeAppendPrintf(sentry
, "No StoreId helpers defined\n");
216 helperStats(sentry
, storeIds
, "StoreId helper Statistics");
218 if (Config
.onoff
.store_id_bypass
)
219 storeAppendPrintf(sentry
, "\nNumber of requests bypassed "
220 "because all StoreId helpers were busy: %d\n", storeIdBypassed
);
224 constructHelperQuery(const char *name
, helper
*hlp
, HLPCB
*replyHandler
, ClientHttpRequest
* http
, HLPCB
*handler
, void *data
, Format::Format
*requestExtrasFmt
)
226 char buf
[MAX_REDIRECTOR_REQUEST_STRLEN
];
228 Http::StatusCode status
;
230 /** TODO: create a standalone method to initialize
231 * the RedirectStateData for all the helpers.
233 RedirectStateData
*r
= new RedirectStateData(http
->uri
);
234 r
->handler
= handler
;
235 r
->data
= cbdataReference(data
);
237 static MemBuf requestExtras
;
238 requestExtras
.reset();
239 if (requestExtrasFmt
)
240 requestExtrasFmt
->assemble(requestExtras
, http
->al
, 0);
242 sz
= snprintf(buf
, MAX_REDIRECTOR_REQUEST_STRLEN
, "%s%s%s\n",
244 requestExtras
.hasContent() ? " " : "",
245 requestExtras
.hasContent() ? requestExtras
.content() : "");
247 if ((sz
<=0) || (sz
>=MAX_REDIRECTOR_REQUEST_STRLEN
)) {
249 status
= Http::scInternalServerError
;
250 debugs(61, DBG_CRITICAL
, "ERROR: Gateway Failure. Can not build request to be passed to " << name
<< ". Request ABORTED.");
252 status
= Http::scUriTooLong
;
253 debugs(61, DBG_CRITICAL
, "ERROR: Gateway Failure. Request passed to " << name
<< " exceeds MAX_REDIRECTOR_REQUEST_STRLEN (" << MAX_REDIRECTOR_REQUEST_STRLEN
<< "). Request ABORTED.");
256 clientStreamNode
*node
= (clientStreamNode
*)http
->client_stream
.tail
->prev
->data
;
257 clientReplyContext
*repContext
= dynamic_cast<clientReplyContext
*>(node
->data
.getRaw());
259 Ip::Address tmpnoaddr
;
260 tmpnoaddr
.setNoAddr();
261 repContext
->setReplyToError(ERR_GATEWAY_FAILURE
, status
,
262 http
->request
->method
, NULL
,
263 http
->getConn() != NULL
&& http
->getConn()->clientConnection
!= NULL
?
264 http
->getConn()->clientConnection
->remote
: tmpnoaddr
,
268 http
->getConn() != NULL
&& http
->getConn()->getAuth() != NULL
?
269 http
->getConn()->getAuth() : http
->request
->auth_user_request
);
274 node
= (clientStreamNode
*)http
->client_stream
.tail
->data
;
275 clientStreamRead(node
, http
, node
->readBuffer
);
279 debugs(61,6, HERE
<< "sending '" << buf
<< "' to the " << name
<< " helper");
280 helperSubmit(hlp
, buf
, replyHandler
, r
);
283 /**** PUBLIC FUNCTIONS ****/
286 redirectStart(ClientHttpRequest
* http
, HLPCB
* handler
, void *data
)
290 debugs(61, 5, "redirectStart: '" << http
->uri
<< "'");
292 if (Config
.onoff
.redirector_bypass
&& redirectors
->stats
.queue_size
) {
293 /* Skip redirector if there is one request queued */
294 ++redirectorBypassed
;
295 HelperReply bypassReply
;
296 bypassReply
.result
= HelperReply::Okay
;
297 bypassReply
.notes
.add("message","URL rewrite/redirect queue too long. Bypassed.");
298 handler(data
, bypassReply
);
302 constructHelperQuery("redirector", redirectors
, redirectHandleReply
, http
, handler
, data
, redirectorExtrasFmt
);
306 * Handles the StoreID feature helper starting.
307 * For now it cannot be done using the redirectStart method.
310 storeIdStart(ClientHttpRequest
* http
, HLPCB
* handler
, void *data
)
314 debugs(61, 5, "storeIdStart: '" << http
->uri
<< "'");
316 if (Config
.onoff
.store_id_bypass
&& storeIds
->stats
.queue_size
) {
317 /* Skip StoreID Helper if there is one request queued */
319 HelperReply bypassReply
;
321 bypassReply
.result
= HelperReply::Okay
;
323 bypassReply
.notes
.add("message","StoreId helper queue too long. Bypassed.");
324 handler(data
, bypassReply
);
328 constructHelperQuery("storeId helper", storeIds
, storeIdHandleReply
, http
, handler
, data
, storeIdExtrasFmt
);
334 static bool init
= false;
337 Mgr::RegisterAction("redirector", "URL Redirector Stats", redirectStats
, 0, 1);
338 Mgr::RegisterAction("store_id", "StoreId helper Stats", storeIdStats
, 0, 1);
341 if (Config
.Program
.redirect
) {
343 if (redirectors
== NULL
)
344 redirectors
= new helper("redirector");
346 redirectors
->cmdline
= Config
.Program
.redirect
;
348 redirectors
->childs
.updateLimits(Config
.redirectChildren
);
350 redirectors
->ipc_type
= IPC_STREAM
;
352 helperOpenServers(redirectors
);
355 if (Config
.Program
.store_id
) {
357 if (storeIds
== NULL
)
358 storeIds
= new helper("store_id");
360 storeIds
->cmdline
= Config
.Program
.store_id
;
362 storeIds
->childs
.updateLimits(Config
.storeIdChildren
);
364 storeIds
->ipc_type
= IPC_STREAM
;
366 helperOpenServers(storeIds
);
369 if (Config
.redirector_extras
) {
370 redirectorExtrasFmt
= new ::Format::Format("url_rewrite_extras");
371 (void)redirectorExtrasFmt
->parse(Config
.redirector_extras
);
374 if (Config
.storeId_extras
) {
375 storeIdExtrasFmt
= new ::Format::Format("store_id_extras");
376 (void)storeIdExtrasFmt
->parse(Config
.storeId_extras
);
383 redirectShutdown(void)
385 /** FIXME: Temporary unified helpers Shutdown
386 * When and if needed for more helpers a separated shutdown
387 * method will be added for each of them.
389 if (!storeIds
&& !redirectors
)
393 helperShutdown(redirectors
);
396 helperShutdown(storeIds
);
407 delete redirectorExtrasFmt
;
408 redirectorExtrasFmt
= NULL
;
410 delete storeIdExtrasFmt
;
411 storeIdExtrasFmt
= NULL
;