]> git.ipfire.org Git - thirdparty/squid.git/blob - src/redirect.cc
Boilerplate: update copyright blurbs on src/
[thirdparty/squid.git] / src / redirect.cc
1 /*
2 * Copyright (C) 1996-2014 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 "client_side.h"
14 #include "client_side_reply.h"
15 #include "client_side_request.h"
16 #include "comm/Connection.h"
17 #include "fde.h"
18 #include "format/Format.h"
19 #include "globals.h"
20 #include "HttpRequest.h"
21 #include "mgr/Registration.h"
22 #include "redirect.h"
23 #include "rfc1738.h"
24 #include "SBuf.h"
25 #include "SquidConfig.h"
26 #include "Store.h"
27 #if USE_AUTH
28 #include "auth/UserRequest.h"
29 #endif
30 #if USE_OPENSSL
31 #include "ssl/support.h"
32 #endif
33
34 /// url maximum lengh + extra informations passed to redirector
35 #define MAX_REDIRECTOR_REQUEST_STRLEN (MAX_URL + 1024)
36
37 class RedirectStateData
38 {
39 public:
40 explicit RedirectStateData(const char *url);
41 ~RedirectStateData();
42
43 void *data;
44 SBuf orig_url;
45
46 HLPCB *handler;
47
48 private:
49 CBDATA_CLASS2(RedirectStateData);
50 };
51
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;
62
63 CBDATA_CLASS_INIT(RedirectStateData);
64
65 RedirectStateData::RedirectStateData(const char *url) :
66 data(NULL),
67 orig_url(url),
68 handler(NULL)
69 {
70 }
71
72 RedirectStateData::~RedirectStateData()
73 {
74 }
75
76 static void
77 redirectHandleReply(void *data, const HelperReply &reply)
78 {
79 RedirectStateData *r = static_cast<RedirectStateData *>(data);
80 debugs(61, 5, HERE << "reply=" << reply);
81
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
85
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.
90 //
91 // * trim all but the first word off the response.
92 // * warn once every 50 responses that this will stop being fixed-up soon.
93 //
94 if (const char * res = reply.other().content()) {
95 if (const char *t = strchr(res, ' ')) {
96 static int warn = 0;
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);
101 }
102 if (reply.other().hasContent() && *res == '\0')
103 reply.modifiableOther().clean(); // drop the whole buffer of garbage.
104
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.
111 */
112 char * result = reply.modifiableOther().content();
113
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);
121
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, '!')) {
128 *t = '\0';
129 newReply.notes.add("urlgroup", result+1);
130 result = t + 1;
131 }
132 }
133
134 const Http::StatusCode status = static_cast<Http::StatusCode>(atoi(result));
135
136 if (status == Http::scMovedPermanently
137 || status == Http::scFound
138 || status == Http::scSeeOther
139 || status == Http::scPermanentRedirect
140 || status == Http::scTemporaryRedirect) {
141
142 if (const char *t = strchr(result, ':')) {
143 char statusBuf[4];
144 snprintf(statusBuf, sizeof(statusBuf),"%3u",status);
145 newReply.notes.add("status", statusBuf);
146 ++t;
147 // TODO: validate the URL produced here is RFC 2616 compliant URI
148 newReply.notes.add("url", t);
149 } else {
150 debugs(85, DBG_CRITICAL, "ERROR: URL-rewrite produces invalid " << status << " redirect Location: " << result);
151 }
152 } else {
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
156 if (*result)
157 newReply.notes.add("rewrite-url", result);
158 }
159
160 void *cbdata;
161 if (cbdataReferenceValidDone(r->data, &cbdata))
162 r->handler(cbdata, newReply);
163
164 delete r;
165 return;
166 }
167 }
168 }
169
170 void *cbdata;
171 if (cbdataReferenceValidDone(r->data, &cbdata))
172 r->handler(cbdata, reply);
173
174 delete r;
175 }
176
177 static void
178 storeIdHandleReply(void *data, const HelperReply &reply)
179 {
180 RedirectStateData *r = static_cast<RedirectStateData *>(data);
181 debugs(61, 5,"StoreId helper: reply=" << reply);
182
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
186 void *cbdata;
187 if (cbdataReferenceValidDone(r->data, &cbdata))
188 r->handler(cbdata, reply);
189
190 delete r;
191 }
192
193 static void
194 redirectStats(StoreEntry * sentry)
195 {
196 if (redirectors == NULL) {
197 storeAppendPrintf(sentry, "No redirectors defined\n");
198 return;
199 }
200
201 helperStats(sentry, redirectors, "Redirector Statistics");
202
203 if (Config.onoff.redirector_bypass)
204 storeAppendPrintf(sentry, "\nNumber of requests bypassed "
205 "because all redirectors were busy: %d\n", redirectorBypassed);
206 }
207
208 static void
209 storeIdStats(StoreEntry * sentry)
210 {
211 if (storeIds == NULL) {
212 storeAppendPrintf(sentry, "No StoreId helpers defined\n");
213 return;
214 }
215
216 helperStats(sentry, storeIds, "StoreId helper Statistics");
217
218 if (Config.onoff.store_id_bypass)
219 storeAppendPrintf(sentry, "\nNumber of requests bypassed "
220 "because all StoreId helpers were busy: %d\n", storeIdBypassed);
221 }
222
223 static void
224 constructHelperQuery(const char *name, helper *hlp, HLPCB *replyHandler, ClientHttpRequest * http, HLPCB *handler, void *data, Format::Format *requestExtrasFmt)
225 {
226 char buf[MAX_REDIRECTOR_REQUEST_STRLEN];
227 int sz;
228 Http::StatusCode status;
229
230 /** TODO: create a standalone method to initialize
231 * the RedirectStateData for all the helpers.
232 */
233 RedirectStateData *r = new RedirectStateData(http->uri);
234 r->handler = handler;
235 r->data = cbdataReference(data);
236
237 static MemBuf requestExtras;
238 requestExtras.reset();
239 if (requestExtrasFmt)
240 requestExtrasFmt->assemble(requestExtras, http->al, 0);
241
242 sz = snprintf(buf, MAX_REDIRECTOR_REQUEST_STRLEN, "%s%s%s\n",
243 r->orig_url.c_str(),
244 requestExtras.hasContent() ? " " : "",
245 requestExtras.hasContent() ? requestExtras.content() : "");
246
247 if ((sz<=0) || (sz>=MAX_REDIRECTOR_REQUEST_STRLEN)) {
248 if (sz<=0) {
249 status = Http::scInternalServerError;
250 debugs(61, DBG_CRITICAL, "ERROR: Gateway Failure. Can not build request to be passed to " << name << ". Request ABORTED.");
251 } else {
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.");
254 }
255
256 clientStreamNode *node = (clientStreamNode *)http->client_stream.tail->prev->data;
257 clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
258 assert (repContext);
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,
265 http->request,
266 NULL,
267 #if USE_AUTH
268 http->getConn() != NULL && http->getConn()->getAuth() != NULL ?
269 http->getConn()->getAuth() : http->request->auth_user_request);
270 #else
271 NULL);
272 #endif
273
274 node = (clientStreamNode *)http->client_stream.tail->data;
275 clientStreamRead(node, http, node->readBuffer);
276 return;
277 }
278
279 debugs(61,6, HERE << "sending '" << buf << "' to the " << name << " helper");
280 helperSubmit(hlp, buf, replyHandler, r);
281 }
282
283 /**** PUBLIC FUNCTIONS ****/
284
285 void
286 redirectStart(ClientHttpRequest * http, HLPCB * handler, void *data)
287 {
288 assert(http);
289 assert(handler);
290 debugs(61, 5, "redirectStart: '" << http->uri << "'");
291
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);
299 return;
300 }
301
302 constructHelperQuery("redirector", redirectors, redirectHandleReply, http, handler, data, redirectorExtrasFmt);
303 }
304
305 /**
306 * Handles the StoreID feature helper starting.
307 * For now it cannot be done using the redirectStart method.
308 */
309 void
310 storeIdStart(ClientHttpRequest * http, HLPCB * handler, void *data)
311 {
312 assert(http);
313 assert(handler);
314 debugs(61, 5, "storeIdStart: '" << http->uri << "'");
315
316 if (Config.onoff.store_id_bypass && storeIds->stats.queue_size) {
317 /* Skip StoreID Helper if there is one request queued */
318 ++storeIdBypassed;
319 HelperReply bypassReply;
320
321 bypassReply.result = HelperReply::Okay;
322
323 bypassReply.notes.add("message","StoreId helper queue too long. Bypassed.");
324 handler(data, bypassReply);
325 return;
326 }
327
328 constructHelperQuery("storeId helper", storeIds, storeIdHandleReply, http, handler, data, storeIdExtrasFmt);
329 }
330
331 void
332 redirectInit(void)
333 {
334 static bool init = false;
335
336 if (!init) {
337 Mgr::RegisterAction("redirector", "URL Redirector Stats", redirectStats, 0, 1);
338 Mgr::RegisterAction("store_id", "StoreId helper Stats", storeIdStats, 0, 1);
339 }
340
341 if (Config.Program.redirect) {
342
343 if (redirectors == NULL)
344 redirectors = new helper("redirector");
345
346 redirectors->cmdline = Config.Program.redirect;
347
348 redirectors->childs.updateLimits(Config.redirectChildren);
349
350 redirectors->ipc_type = IPC_STREAM;
351
352 helperOpenServers(redirectors);
353 }
354
355 if (Config.Program.store_id) {
356
357 if (storeIds == NULL)
358 storeIds = new helper("store_id");
359
360 storeIds->cmdline = Config.Program.store_id;
361
362 storeIds->childs.updateLimits(Config.storeIdChildren);
363
364 storeIds->ipc_type = IPC_STREAM;
365
366 helperOpenServers(storeIds);
367 }
368
369 if (Config.redirector_extras) {
370 redirectorExtrasFmt = new ::Format::Format("url_rewrite_extras");
371 (void)redirectorExtrasFmt->parse(Config.redirector_extras);
372 }
373
374 if (Config.storeId_extras) {
375 storeIdExtrasFmt = new ::Format::Format("store_id_extras");
376 (void)storeIdExtrasFmt->parse(Config.storeId_extras);
377 }
378
379 init = true;
380 }
381
382 void
383 redirectShutdown(void)
384 {
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.
388 */
389 if (!storeIds && !redirectors)
390 return;
391
392 if (redirectors)
393 helperShutdown(redirectors);
394
395 if (storeIds)
396 helperShutdown(storeIds);
397
398 if (!shutting_down)
399 return;
400
401 delete redirectors;
402 redirectors = NULL;
403
404 delete storeIds;
405 storeIds = NULL;
406
407 delete redirectorExtrasFmt;
408 redirectorExtrasFmt = NULL;
409
410 delete storeIdExtrasFmt;
411 storeIdExtrasFmt = NULL;
412 }