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