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