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