]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ESIInclude.cc
Adding a comment that tests/testUfs will fail when 'heap' is the only
[thirdparty/squid.git] / src / ESIInclude.cc
CommitLineData
924f73bc 1
2/*
f169ddc4 3 * $Id: ESIInclude.cc,v 1.11 2006/09/03 04:15:54 robertc Exp $
924f73bc 4 *
5 * DEBUG: section 86 ESI processing
6 * AUTHOR: Robert Collins
7 *
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
10 *
11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
19 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 ; but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
33 *
34 * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
35 */
36
37#include "squid.h"
38#include "ESIInclude.h"
39#include "ESIVarState.h"
40#include "client_side_request.h"
41#include "HttpReply.h"
42
43CBDATA_CLASS_INIT (ESIStreamContext);
44
924f73bc 45/* other */
46static CSCB esiBufferRecipient;
47static CSD esiBufferDetach;
48/* esiStreamContext */
49static ESIStreamContext *ESIStreamContextNew (ESIIncludePtr);
50
51/* ESI TO CONSIDER:
52 * 1. retry failed upstream requests
53 */
54
55/* Detach from a buffering stream
56 */
57void
59a1efb2 58esiBufferDetach (clientStreamNode *node, ClientHttpRequest *http)
924f73bc 59{
60 /* Detach ourselves */
61 clientStreamDetach (node, http);
62}
63
64/*
65 * Write a chunk of data to a client 'socket'.
66 * If the reply is present, send the reply headers down the wire too,
67 * and clean them up when finished.
68 * Pre-condition:
69 * The request is an internal ESI subrequest.
70 * data context is not NULL
71 * There are no more entries in the stream chain.
72 */
73void
59a1efb2 74esiBufferRecipient (clientStreamNode *node, ClientHttpRequest *http, HttpReply *rep, StoreIOBuffer recievedData)
924f73bc 75{
76 /* Test preconditions */
77 assert (node != NULL);
78 /* ESI TODO: handle thisNode rather than asserting
79 * - it should only ever happen if we cause an
80 * abort and the callback chain loops back to
81 * here, so we can simply return. However, that
82 * itself shouldn't happen, so it stays as an
83 * assert for now. */
84 assert (cbdataReferenceValid (node));
85 assert (node->node.next == NULL);
86 assert (http->getConn().getRaw() == NULL);
87
88 ESIStreamContext::Pointer esiStream = dynamic_cast<ESIStreamContext *>(node->data.getRaw());
89 assert (esiStream.getRaw() != NULL);
90 /* If segments become more flexible, ignore thisNode */
91 assert (recievedData.length <= sizeof(esiStream->localbuffer->buf));
92 assert (!esiStream->finished);
93
137a13ea 94 debugs (86,5, "esiBufferRecipient rep " << rep << " body " << recievedData.data << " len " << recievedData.length);
924f73bc 95 assert (node->readBuffer.offset == recievedData.offset || recievedData.length == 0);
96
97 /* trivial case */
98
99 if (http->out.offset != 0) {
100 assert(rep == NULL);
101 } else {
102 if (rep) {
103 if (rep->sline.status != HTTP_OK) {
06a5ae20 104 delete rep;
924f73bc 105 rep = NULL;
106 esiStream->include->fail (esiStream);
107 esiStream->finished = 1;
108 httpRequestFree (http);
109 return;
110 }
111
112#if HEADERS_LOG
113 /* should be done in the store rather than every recipient? */
114 headersLog(0, 0, http->request->method, rep);
115
116#endif
117
f169ddc4 118 /* delete rep; 2006/09/02: TS, #975
119 *
120 * This was causing double-deletes. Its possible that not deleting
121 * it here will cause memory leaks, but if so, this delete should
122 * not be reinstated or it will trigger bug #975 again - RBC
123 * 20060903
124 */
924f73bc 125
126 rep = NULL;
127 }
128 }
129
130 if (recievedData.data && recievedData.length) {
131 http->out.offset += recievedData.length;
132
133 if (recievedData.data >= esiStream->localbuffer->buf &&
134 recievedData.data < &esiStream->localbuffer->buf[sizeof(esiStream->localbuffer->buf)]) {
135 /* original static buffer */
136
137 if (recievedData.data != esiStream->localbuffer->buf) {
138 /* But not the start of it */
139 xmemmove (esiStream->localbuffer->buf, recievedData.data, recievedData.length);
140 }
141
142 esiStream->localbuffer->len = recievedData.length;
143 } else {
144 assert (esiStream->buffer.getRaw() != NULL);
145 esiStream->buffer->len = recievedData.length;
146 }
147 }
148
149 /* EOF / Read error / aborted entry */
150 if (rep == NULL && recievedData.data == NULL && recievedData.length == 0) {
151 /* TODO: get stream status to test the entry for aborts */
152 debug (86,5)("Finished reading upstream data in subrequest\n");
153 esiStream->include->subRequestDone (esiStream, true);
154 esiStream->finished = 1;
155 httpRequestFree (http);
156 return;
157 }
158
159
160 /* after the write to the user occurs, (ie here, or in a callback)
161 * we call */
162 if (clientHttpRequestStatus(-1, http)) {
163 /* TODO: Does thisNode if block leak htto ? */
164 /* XXX when reviewing ESI this is the first place to look */
165 node->data = NULL;
166 esiStream->finished = 1;
167 esiStream->include->fail (esiStream);
168 return;
169 };
170
171 switch (clientStreamStatus (node, http)) {
172
173 case STREAM_UNPLANNED_COMPLETE: /* fallthru ok */
174
175 case STREAM_COMPLETE: /* ok */
176 debug (86,3)("ESI subrequest finished OK\n");
177 esiStream->include->subRequestDone (esiStream, true);
178 esiStream->finished = 1;
179 httpRequestFree (http);
180 return;
181
182 case STREAM_FAILED:
183 debug (86,1)("ESI subrequest failed transfer\n");
184 esiStream->include->fail (esiStream);
185 esiStream->finished = 1;
186 httpRequestFree (http);
187 return;
188
189 case STREAM_NONE: {
190 StoreIOBuffer tempBuffer;
191
192 if (!esiStream->buffer.getRaw()) {
193 esiStream->buffer = esiStream->localbuffer;
194 }
195
196 esiStream->buffer = esiStream->buffer->tail();
197
198 if (esiStream->buffer->len) {
199 esiStream->buffer->next = new ESISegment;
200 esiStream->buffer = esiStream->buffer->next;
201 }
202
203 tempBuffer.offset = http->out.offset;
204 tempBuffer.length = sizeof (esiStream->buffer->buf);
205 tempBuffer.data = esiStream->buffer->buf;
206 /* now just read into 'buffer' */
207 clientStreamRead (node,
208 http, tempBuffer);
209 debug (86,5)("esiBufferRecipient: Requested more data for ESI subrequest\n");
210 }
211
212 break;
213
214 default:
215 fatal ("Hit unreachable code in esiBufferRecipient\n");
216 }
217
218}
219
220/* esiStream functions */
221ESIStreamContext::~ESIStreamContext()
222{
223 assert (this);
224 freeResources();
225}
226
227void
228ESIStreamContext::freeResources()
229{
230 debug (86,5)("Freeing stream context resources.\n");
231 buffer = NULL;
232 localbuffer = NULL;
233 include = NULL;
234}
235
236void *
237ESIStreamContext::operator new(size_t byteCount)
238{
239 assert (byteCount == sizeof (ESIStreamContext));
240 CBDATA_INIT_TYPE(ESIStreamContext);
241 ESIStreamContext *result = cbdataAlloc(ESIStreamContext);
924f73bc 242 return result;
243}
244
245void
246ESIStreamContext::operator delete (void *address)
247{
248 ESIStreamContext *t = static_cast<ESIStreamContext *>(address);
249 cbdataFree(t);
924f73bc 250}
251
924f73bc 252ESIStreamContext *
253ESIStreamContextNew (ESIIncludePtr include)
254{
255 ESIStreamContext *rv = new ESIStreamContext;
256 rv->include = include;
257 return rv;
258}
259
260
261
262/* ESIInclude */
263ESIInclude::~ESIInclude()
264{
265 debug (86,5)("ESIInclude::Free %p\n", this);
266 ESISegmentFreeList (srccontent);
267 ESISegmentFreeList (altcontent);
268 cbdataReferenceDone (varState);
269 safe_free (srcurl);
270 safe_free (alturl);
271}
272
273void
274ESIInclude::finish()
275{
276 parent = NULL;
277}
278
924f73bc 279ESIElement::Pointer
280ESIInclude::makeCacheable() const
281{
282 return new ESIInclude (*this);
283}
284
285ESIElement::Pointer
286ESIInclude::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
287{
288 ESIInclude *resultI = new ESIInclude (*this);
289 ESIElement::Pointer result = resultI;
290 resultI->parent = newParent;
291 resultI->varState = cbdataReference (&newVarState);
292
293 if (resultI->srcurl)
294 resultI->src = ESIStreamContextNew (resultI);
295
296 if (resultI->alturl)
297 resultI->alt = ESIStreamContextNew (resultI);
298
299 return result;
300}
301
302ESIInclude::ESIInclude(ESIInclude const &old) : parent (NULL), started (false), sent (false)
303{
304 varState = NULL;
305 flags.onerrorcontinue = old.flags.onerrorcontinue;
306
307 if (old.srcurl)
308 srcurl = xstrdup (old.srcurl);
309
310 if (old.alturl)
311 alturl = xstrdup (old.alturl);
312}
313
314void
315ESIInclude::prepareRequestHeaders(HttpHeader &tempheaders, ESIVarState *vars)
316{
924f73bc 317 tempheaders.update (&vars->header(), NULL);
318 tempheaders.removeConnectionHeaderEntries();
319}
320
321
322void
323ESIInclude::Start (ESIStreamContext::Pointer stream, char const *url, ESIVarState *vars)
324{
924f73bc 325 if (!stream.getRaw())
326 return;
327
818c6c9e 328 HttpHeader tempheaders(hoRequest);
329
924f73bc 330 prepareRequestHeaders(tempheaders, vars);
331
332 /* Ensure variable state is clean */
333 vars->feedData(url, strlen (url));
334
335 /* tempUrl is eaten by the request */
336 char const *tempUrl = vars->extractChar ();
337
338 debug (86,5)("ESIIncludeStart: Starting subrequest with url '%s'\n", tempUrl);
339
340 if (clientBeginRequest(METHOD_GET, tempUrl, esiBufferRecipient, esiBufferDetach, stream.getRaw(), &tempheaders, stream->localbuffer->buf, HTTP_REQBUF_SZ)) {
341 debug (86,0) ("starting new ESI subrequest failed\n");
342 }
343
519e0948 344 tempheaders.clean();
924f73bc 345}
346
347ESIInclude::ESIInclude (esiTreeParentPtr aParent, int attrcount, char const **attr, ESIContext *aContext) : parent (aParent), started (false), sent (false)
348{
349 int i;
350 assert (aContext);
351
352 for (i = 0; i < attrcount && attr[i]; i += 2) {
353 if (!strcmp(attr[i],"src")) {
354 /* Start a request for thisNode url */
355 debug (86,5)("ESIIncludeNew: Requesting source '%s'\n",attr[i+1]);
356 /* TODO: don't assert on thisNode, ignore the duplicate */
357 assert (src.getRaw() == NULL);
358 src = ESIStreamContextNew (this);
359 assert (src.getRaw() != NULL);
360 srcurl = xstrdup ( attr[i+1]);
361 } else if (!strcmp(attr[i],"alt")) {
362 /* Start a secondary request for thisNode url */
363 /* TODO: make a config parameter to wait on requesting alt's
364 * for the src to fail
365 */
366 debug (86,5)("ESIIncludeNew: Requesting alternate '%s'\n",attr[i+1]);
367 assert (alt.getRaw() == NULL); /* TODO: FIXME */
368 alt = ESIStreamContextNew (this);
369 assert (alt.getRaw() != NULL);
370 alturl = xstrdup (attr[i+1]);
371 } else if (!strcmp(attr[i],"onerror")) {
372 if (!strcmp(attr[i+1], "continue")) {
373 flags.onerrorcontinue = 1;
374 } else {
375 /* ignore mistyped attributes */
376 debug (86, 1)("invalid value for onerror='%s'\n", attr[i+1]);
377 }
378 } else {
379 /* ignore mistyped attributes. TODO:? error on these for user feedback - config parameter needed
380 */
381 }
382 }
383
384 varState = cbdataReference(aContext->varState);
385}
386
387void
388ESIInclude::start()
389{
390 /* prevent freeing ourselves */
391 ESIIncludePtr foo(this);
392
393 if (started)
394 return;
395
396 started = true;
397
398 if (src.getRaw()) {
399 Start (src, srcurl, varState);
400 Start (alt, alturl, varState);
401 } else {
402 alt = NULL;
403
404 debug (86,1)("ESIIncludeNew: esi:include with no src attributes\n");
405
406 flags.failed = 1;
407 }
408}
409
410void
411ESIInclude::render(ESISegment::Pointer output)
412{
413 if (sent)
414 return;
415
416 ESISegment::Pointer myout;
417
418 debug (86, 5)("ESIIncludeRender: Rendering include %p\n", this);
419
420 assert (flags.finished || (flags.failed && flags.onerrorcontinue));
421
422 if (flags.failed && flags.onerrorcontinue) {
423 return;
424 }
425
426 /* Render the content */
427 if (srccontent.getRaw()) {
428 myout = srccontent;
429 srccontent = NULL;
430 } else if (altcontent.getRaw()) {
431 myout = altcontent;
432 altcontent = NULL;
433 } else
434 fatal ("ESIIncludeRender called with no content, and no failure!\n");
435
436 assert (output->next == NULL);
437
438 output->next = myout;
439
440 sent = true;
441}
442
443esiProcessResult_t
444ESIInclude::process (int dovars)
445{
446 /* Prevent refcount race leading to free */
447 Pointer me (this);
448 start();
449 debug (86, 5)("ESIIncludeRender: Processing include %p\n", this);
450
451 if (flags.failed) {
452 if (flags.onerrorcontinue)
453 return ESI_PROCESS_COMPLETE;
454 else
455 return ESI_PROCESS_FAILED;
456 }
457
458 if (!flags.finished) {
459 if (flags.onerrorcontinue)
460 return ESI_PROCESS_PENDING_WONTFAIL;
461 else
462 return ESI_PROCESS_PENDING_MAYFAIL;
463 }
464
465 return ESI_PROCESS_COMPLETE;
466}
467
468void
469ESIInclude::fail (ESIStreamContext::Pointer stream)
470{
471 subRequestDone (stream, false);
472}
473
474bool
475ESIInclude::dataNeeded() const
476{
477 return !(flags.finished || flags.failed);
478}
479
480void
481ESIInclude::subRequestDone (ESIStreamContext::Pointer stream, bool success)
482{
483 assert (this);
484
485 if (!dataNeeded())
486 return;
487
488 if (stream == src) {
489 debug (86,3)("ESIInclude::subRequestDone: %s\n", srcurl);
490
491 if (success) {
492 /* copy the lead segment */
493 debug (86,3)("ESIIncludeSubRequestDone: Src OK - include PASSED.\n");
494 assert (!srccontent.getRaw());
495 ESISegment::ListTransfer (stream->localbuffer, srccontent);
496 /* we're done! */
497 flags.finished = 1;
498 } else {
499 /* Fail if there is no alt being retrieved */
500 debug (86,3)("ESIIncludeSubRequestDone: Src FAILED\n");
501
502 if (!(alt.getRaw() || altcontent.getRaw())) {
503 debug (86,3)("ESIIncludeSubRequestDone: Include FAILED - No ALT\n");
504 flags.failed = 1;
505 } else if (altcontent.getRaw()) {
506 debug (86,3)("ESIIncludeSubRequestDone: Include PASSED - ALT already Complete\n");
507 /* ALT was already retrieved, we are done */
508 flags.finished = 1;
509 }
510 }
511
512 src = NULL;
513 } else if (stream == alt) {
514 debug (86,3)("ESIInclude::subRequestDone: %s\n", alturl);
515
516 if (success) {
517 debug (86,3)("ESIIncludeSubRequestDone: ALT OK.\n");
518 /* copy the lead segment */
519 assert (!altcontent.getRaw());
520 ESISegment::ListTransfer (stream->localbuffer, altcontent);
521 /* we're done! */
522
523 if (!(src.getRaw() || srccontent.getRaw())) {
524 /* src already failed, kick ESI processor */
525 debug (86,3)("ESIIncludeSubRequestDone: Include PASSED - SRC already failed.\n");
526 flags.finished = 1;
527 }
528 } else {
529 if (!(src.getRaw() || srccontent.getRaw())) {
530 debug (86,3)("ESIIncludeSubRequestDone: ALT FAILED, Include FAILED - SRC already failed\n");
531 /* src already failed */
532 flags.failed = 1;
533 }
534 }
535
536 alt = NULL;
537 } else {
538 fatal ("ESIIncludeSubRequestDone: non-owned stream found!\n");
539 }
540
541 if (flags.finished || flags.failed) {
542 /* Kick ESI Processor */
2a2d937f 543 debugs (86, 5, "ESIInclude " << this <<
544 " SubRequest " << stream.getRaw() <<
545 " completed, kicking processor , status " <<
546 (flags.finished ? "OK" : "FAILED"));
547 /* There is a race condition - and we have no reproducible test case -
548 * during a subrequest the parent will get set to NULL, which is not
549 * meant to be possible. Rather than killing squid, we let it leak
550 * memory but complain in the log.
551 *
552 * Someone wanting to debug this could well start by running squid with
553 * a hardware breakpoint set to this location.
554 * Its probably due to parent being set to null - by a call to
555 * 'this.finish' while the subrequest is still not completed.
556 */
557 if (parent.getRaw() == NULL) {
558 debugs (86, 0, "ESIInclude::subRequestDone: Sub request completed "
559 "after finish() called and parent unlinked. Unable to "
560 "continue handling the request, and may be memory leaking. "
561 "See http://www.squid-cache.org/bugs/show_bug.cgi?id=951 - we "
562 "are looking for a reproducible test case. This will require "
563 "an ESI template with includes, probably with alt-options, "
564 "and we're likely to need traffic dumps to allow us to "
565 "reconstruct the exact tcp handling sequences to trigger this "
566 "rather elusive bug.");
567 return;
568 }
924f73bc 569 assert (parent.getRaw());
570
571 if (!flags.failed) {
572 sent = true;
573 parent->provideData (srccontent.getRaw() ? srccontent:altcontent,this);
574
575 if (srccontent.getRaw())
576 srccontent = NULL;
577 else
578 altcontent = NULL;
579 } else if (flags.onerrorcontinue) {
580 /* render nothing but inform of completion */
581
582 if (!sent) {
583 sent = true;
584 parent->provideData (new ESISegment, this);
585 } else
586 assert (0);
587 } else
588 parent->fail(this, "esi:include could not be completed.");
589 }
590}
591