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