]> git.ipfire.org Git - thirdparty/squid.git/blame - src/esi/Esi.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / esi / Esi.cc
CommitLineData
43ae1d95 1/*
f70aedc4 2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
43ae1d95 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.
43ae1d95 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
582c2af2 18#include "client_side.h"
602d9612 19#include "client_side_request.h"
582c2af2 20#include "clientStream.h"
1b76e6c1 21#include "comm/Connection.h"
aa839030 22#include "errorpage.h"
582c2af2
FC
23#include "esi/Assign.h"
24#include "esi/Attempt.h"
f99c2cfe 25#include "esi/Context.h"
582c2af2
FC
26#include "esi/Element.h"
27#include "esi/Esi.h"
28#include "esi/Except.h"
29#include "esi/Expression.h"
30#include "esi/Segment.h"
31#include "esi/VarState.h"
f66e113e 32#include "FadingCounter.h"
8c41d298 33#include "fatal.h"
d3dddfb5 34#include "http/Stream.h"
b19dd748 35#include "HttpHdrSc.h"
36#include "HttpHdrScTarget.h"
43ae1d95 37#include "HttpReply.h"
924f73bc 38#include "HttpRequest.h"
96d89ea0 39#include "ip/Address.h"
582c2af2
FC
40#include "MemBuf.h"
41#include "profiler/Profiler.h"
4d5904f7 42#include "SquidConfig.h"
43ae1d95 43
44/* quick reference on behaviour here.
26ac0430
AJ
45 * The ESI specification 1.0 requires the ESI processor to be able to
46 * return an error code at any point in the processing. To that end
47 * we buffer the incoming esi body until we know we will be able to
43ae1d95 48 * satisfy the request. At that point we start streaming the queued
49 * data downstream.
50 *
51 */
52
0655fa4d 53class ESIStreamContext;
54
43ae1d95 55/* TODO: split this out into separate files ? */
56/* Parsing: quick and dirty. ESI files are not valid XML, so a generic
26ac0430 57 * XML parser is not much use. Also we need a push parser not a pull
43ae1d95 58 * parser, so LibXML is out.
59 *
26ac0430 60 * Interpreter methods:
43ae1d95 61 * Render: May only ever be called after Process returns PROCESS_COMPLETE.
62 * Renders the resulting content into a ESISegment chain.
63 * Process: returns the status of the node.
64 * COMPLETE - processing is complete, rendering may staret
65 * PENDING_WONTFAIL - process is incomplete, but the element *will*
66 * be able to be rendered given time.
67 * PENDING_MAYFAIL - processing is incomplete, and the element *may*
68 * fail to be able to rendered.
69 * FAILED - processing failed, return an error to the client.
70 */
71
72/*
73 * NOT TODO: esi:inline - out of scope.
74 */
75
76/* make comparisons with refcount pointers easy */
77bool operator == (ESIElement const *lhs, ESIElement::Pointer const &rhs)
78{
79 return lhs == rhs.getRaw();
80}
81
43ae1d95 82typedef ESIContext::esiKick_t esiKick_t;
83
43ae1d95 84/* some core operators */
85
741c2986
AJ
86class esiComment : public ESIElement
87{
b001e822 88 MEMPROXY_CLASS(esiComment);
741c2986
AJ
89
90public:
43ae1d95 91 ~esiComment();
92 esiComment();
93 Pointer makeCacheable() const;
924f73bc 94 Pointer makeUsable(esiTreeParentPtr, ESIVarState &) const;
43ae1d95 95
96 void render(ESISegment::Pointer);
97 void finish();
43ae1d95 98};
99
f99c2cfe 100#include "esi/Literal.h"
43ae1d95 101
f99c2cfe 102#include "esi/Sequence.h"
43ae1d95 103
f99c2cfe 104#include "esi/Include.h"
43ae1d95 105
106/* esiRemove */
107
108class esiRemove : public ESIElement
109{
8c41d298 110 MEMPROXY_CLASS(esiRemove);
43ae1d95 111
112public:
8c41d298
AJ
113 esiRemove() : ESIElement() {}
114 virtual ~esiRemove() {}
115
116 virtual void render(ESISegment::Pointer);
117 virtual bool addElement (ESIElement::Pointer);
118 virtual Pointer makeCacheable() const;
119 virtual Pointer makeUsable(esiTreeParentPtr, ESIVarState &) const;
120 virtual void finish() {}
43ae1d95 121};
122
741c2986
AJ
123class esiTry : public ESIElement
124{
b001e822 125 MEMPROXY_CLASS(esiTry);
43ae1d95 126
741c2986 127public:
43ae1d95 128 esiTry(esiTreeParentPtr aParent);
129 ~esiTry();
130
131 void render(ESISegment::Pointer);
132 bool addElement (ESIElement::Pointer);
924f73bc 133 void fail(ESIElement *, char const * = NULL);
43ae1d95 134 esiProcessResult_t process (int dovars);
135 void provideData (ESISegment::Pointer data, ESIElement * source);
136 Pointer makeCacheable() const;
924f73bc 137 Pointer makeUsable(esiTreeParentPtr, ESIVarState &) const;
43ae1d95 138
139 ESIElement::Pointer attempt;
140 ESIElement::Pointer except;
141
26ac0430 142 struct {
3d0ac046
HN
143 int attemptok:1; /* the attempt branch process correctly */
144 int exceptok:1; /* likewise */
145 int attemptfailed:1; /* The attempt branch failed */
146 int exceptfailed:1; /* the except branch failed */
2fadd50d 147 } flags;
43ae1d95 148 void finish();
149
150private:
43ae1d95 151 void notifyParent();
152 esiTreeParentPtr parent;
153 ESISegment::Pointer exceptbuffer;
154 esiTry (esiTry const &);
155 esiProcessResult_t bestAttemptRV() const;
156};
157
f99c2cfe 158#include "esi/Var.h"
43ae1d95 159
741c2986
AJ
160class esiChoose : public ESIElement
161{
b001e822 162 MEMPROXY_CLASS(esiChoose);
43ae1d95 163
741c2986 164public:
43ae1d95 165 esiChoose(esiTreeParentPtr);
166 ~esiChoose();
167
168 void render(ESISegment::Pointer);
169 bool addElement (ESIElement::Pointer);
924f73bc 170 void fail(ESIElement *, char const * = NULL);
43ae1d95 171 esiProcessResult_t process (int dovars);
172
173 void provideData (ESISegment::Pointer data, ESIElement *source);
174 void makeCachableElements(esiChoose const &old);
924f73bc 175 void makeUsableElements(esiChoose const &old, ESIVarState &);
43ae1d95 176 Pointer makeCacheable() const;
924f73bc 177 Pointer makeUsable(esiTreeParentPtr, ESIVarState &) const;
43ae1d95 178 void NULLUnChosen();
179
b56b37cf 180 Esi::Elements elements;
43ae1d95 181 int chosenelement;
182 ESIElement::Pointer otherwise;
183 void finish();
184
185private:
43ae1d95 186 esiChoose(esiChoose const &);
187 esiTreeParentPtr parent;
188 void checkValidSource (ESIElement::Pointer source) const;
189 void selectElement();
190};
191
741c2986
AJ
192class esiWhen : public esiSequence
193{
b001e822 194 MEMPROXY_CLASS(esiWhen);
741c2986
AJ
195
196public:
924f73bc 197 esiWhen(esiTreeParentPtr aParent, int attributes, const char **attr, ESIVarState *);
43ae1d95 198 ~esiWhen();
199 Pointer makeCacheable() const;
924f73bc 200 Pointer makeUsable(esiTreeParentPtr, ESIVarState &) const;
43ae1d95 201
202 bool testsTrue() const { return testValue;}
203
204 void setTestResult(bool aBool) {testValue = aBool;}
205
206private:
43ae1d95 207 esiWhen (esiWhen const &);
208 bool testValue;
209 char const *unevaluatedExpression;
924f73bc 210 ESIVarState *varState;
43ae1d95 211 void evaluate();
212};
213
26ac0430 214struct esiOtherwise : public esiSequence {
3d0ac046
HN
215 esiOtherwise(esiTreeParentPtr aParent) : esiSequence (aParent) {}
216};
43ae1d95 217
218CBDATA_CLASS_INIT(ESIContext);
219
220void ESIContext::startRead()
221{
222 assert (!reading_);
223 reading_ = true;
224}
225
226void ESIContext::finishRead()
227{
228 assert (reading_);
229 reading_ = false;
230}
231
232bool ESIContext::reading() const
233{
234 return reading_;
235}
236
0655fa4d 237ESIStreamContext::ESIStreamContext() : finished(false), include (NULL), localbuffer (new ESISegment), buffer (NULL)
43ae1d95 238{}
239
240/* Local functions */
241/* ESIContext */
59a1efb2 242static ESIContext *ESIContextNew(HttpReply *, clientStreamNode *, ClientHttpRequest *);
43ae1d95 243
43ae1d95 244void
245ESIContext::setError()
246{
247 errorpage = ERR_ESI;
955394ce 248 errorstatus = Http::scInternalServerError;
43ae1d95 249 flags.error = 1;
250}
251
252void
253ESIContext::appendOutboundData(ESISegment::Pointer theData)
254{
255 if (!outbound.getRaw()) {
256 outbound = theData;
257 outboundtail = outbound;
258 } else {
259 assert (outboundtail->next.getRaw() == NULL);
260 outboundtail->next = theData;
261 }
262
263 fixupOutboundTail();
bf8fe701 264 debugs(86, 9, "ESIContext::appendOutboundData: outbound " << outbound.getRaw());
43ae1d95 265}
266
267void
268ESIContext::provideData (ESISegment::Pointer theData, ESIElement * source)
269{
bf8fe701 270 debugs(86, 5, "ESIContext::provideData: " << this << " " << theData.getRaw() << " " << source);
43ae1d95 271 /* No callbacks permitted after finish() called on the tree */
272 assert (tree.getRaw());
273 assert (source == tree);
274 appendOutboundData(theData);
275 trimBlanks();
276
277 if (!processing)
278 send();
279}
280
281void
924f73bc 282ESIContext::fail (ESIElement * source, char const *anError)
43ae1d95 283{
284 setError();
924f73bc 285 setErrorMessage (anError);
43ae1d95 286 fail ();
287 send ();
288}
289
290void
291ESIContext::fixupOutboundTail()
292{
293 /* TODO: fixup thisNode outboundtail dross a little */
294
295 if (outboundtail.getRaw())
296 outboundtail = outboundtail->tail();
297}
298
299esiKick_t
300ESIContext::kick ()
301{
43ae1d95 302 if (flags.kicked) {
bf8fe701 303 debugs(86, 5, "esiKick: Re-entered whilst in progress");
43ae1d95 304 // return ESI_KICK_INPROGRESS;
305 } else
306 ++flags.kicked;
307
308 if (flags.detached)
309 /* we've been detached from - we can't do anything more */
310 return ESI_KICK_FAILED;
311
61beade2 312 /* Something has occurred. Process any remaining nodes */
43ae1d95 313 if (!flags.finished)
314 /* Process some of our data */
315 switch (process ()) {
316
317 case ESI_PROCESS_COMPLETE:
bf8fe701 318 debugs(86, 5, "esiKick: esiProcess OK");
43ae1d95 319 break;
320
321 case ESI_PROCESS_PENDING_WONTFAIL:
bf8fe701 322 debugs(86, 5, "esiKick: esiProcess PENDING OK");
43ae1d95 323 break;
324
325 case ESI_PROCESS_PENDING_MAYFAIL:
bf8fe701 326 debugs(86, 5, "esiKick: esiProcess PENDING UNKNOWN");
43ae1d95 327 break;
328
329 case ESI_PROCESS_FAILED:
bf8fe701 330 debugs(86, 2, "esiKick: esiProcess " << this << " FAILED");
43ae1d95 331 /* this can not happen - processing can't fail until we have data,
332 * and when we come here we have sent data to the client
333 */
334
335 if (pos == 0)
336 fail ();
337
338 --flags.kicked;
339
340 return ESI_KICK_FAILED;
341 }
342
343 /* Render if we can to get maximal sent data */
344 assert (tree.getRaw() || flags.error);
345
346 if (!flags.finished && !outbound.getRaw()) {
347 outboundtail = new ESISegment;
348 outbound = outboundtail;
349 }
350
351 if (!flags.error && !flags.finished)
352 tree->render(outboundtail);
353
354 if (!flags.finished)
355 fixupOutboundTail();
356
357 /* Is there data to send? */
358 if (send ()) {
359 /* some data was sent. we're finished until the next read */
360 --flags.kicked;
361 return ESI_KICK_SENT;
362 }
363
364 --flags.kicked;
365 /* nothing to send */
366 return flags.error ? ESI_KICK_FAILED : ESI_KICK_PENDING;
367}
368
369/* request from downstream for more data
370 */
371void
59a1efb2 372esiStreamRead (clientStreamNode *thisNode, ClientHttpRequest *http)
43ae1d95 373{
374 clientStreamNode *next;
43ae1d95 375 /* Test preconditions */
376 assert (thisNode != NULL);
377 assert (cbdataReferenceValid (thisNode));
378 /* we are not in the chain until ESI is detected on a data callback */
43ae1d95 379 assert (thisNode->node.prev != NULL);
380 assert (thisNode->node.next != NULL);
381
0655fa4d 382 ESIContext::Pointer context = dynamic_cast<ESIContext *>(thisNode->data.getRaw());
383 assert (context.getRaw() != NULL);
43ae1d95 384
385 if (context->flags.passthrough) {
386 /* passthru mode - read into supplied buffers */
387 next = thisNode->next();
388 clientStreamRead (thisNode, http, next->readBuffer);
43ae1d95 389 return;
390 }
391
392 context->flags.clientwantsdata = 1;
bf8fe701 393 debugs(86, 5, "esiStreamRead: Client now wants data");
43ae1d95 394
395 /* Ok, not passing through */
396
397 switch (context->kick ()) {
398
399 case ESIContext::ESI_KICK_FAILED:
f53969cc
SM
400 /* this can not happen - processing can't fail until we have data,
401 * and when we come here we have sent data to the client
402 */
43ae1d95 403
404 case ESIContext::ESI_KICK_SENT:
405
406 case ESIContext::ESI_KICK_INPROGRESS:
43ae1d95 407 return;
408
409 case ESIContext::ESI_KICK_PENDING:
410 break;
411 }
412
413 /* Nothing to send */
414
415 if (context->flags.oktosend && (context->flags.finishedtemplate
416 || context->cachedASTInUse) &&
417 ! context->flags.finished) {
418 /* we've started sending, finished reading, but not finished
419 * processing. stop here, a callback will resume the stream
420 * flow
421 */
bf8fe701 422 debugs(86, 5, "esiStreamRead: Waiting for async resume of esi processing");
43ae1d95 423 return;
424 }
425
426 if (context->flags.oktosend && context->flags.finished && context->outbound.getRaw()) {
bf8fe701 427 debugs(86, 5, "all processing complete, but outbound data still buffered");
43ae1d95 428 assert (!context->flags.clientwantsdata);
429 /* client MUST be processing the last reply */
43ae1d95 430 return;
431 }
432
43ae1d95 433 if (context->flags.oktosend && context->flags.finished) {
434 StoreIOBuffer tempBuffer;
435 assert (!context->outbound.getRaw());
436 /* We've finished processing, and there is no more data buffered */
bf8fe701 437 debugs(86, 5, "Telling recipient EOF on READ");
43ae1d95 438 clientStreamCallback (thisNode, http, NULL, tempBuffer);
43ae1d95 439 return;
440 }
441
0655fa4d 442 if (context->reading())
43ae1d95 443 return;
43ae1d95 444
445 /* no data that is ready to send, and still reading? well, lets get some */
446 /* secure a buffer */
447 if (!context->incoming.getRaw()) {
448 /* create a new buffer segment */
449 context->buffered = new ESISegment;
450 context->incoming = context->buffered;
451 }
452
453 assert (context->incoming.getRaw() && context->incoming->len != HTTP_REQBUF_SZ);
454 {
455 StoreIOBuffer tempBuffer;
456 tempBuffer.offset = context->readpos;
457 tempBuffer.length = context->incoming->len - HTTP_REQBUF_SZ;
458 tempBuffer.data = &context->incoming->buf[context->incoming->len];
459 context->startRead();
460 clientStreamRead (thisNode, http, tempBuffer);
461 }
43ae1d95 462}
463
464clientStream_status_t
59a1efb2 465esiStreamStatus (clientStreamNode *thisNode, ClientHttpRequest *http)
43ae1d95 466{
467 /* Test preconditions */
468 assert (thisNode != NULL);
469 assert (cbdataReferenceValid (thisNode));
470 /* we are not in the chain until ESI is detected on a data callback */
43ae1d95 471 assert (thisNode->node.prev != NULL);
472 assert (thisNode->node.next != NULL);
473
0655fa4d 474 ESIContext::Pointer context = dynamic_cast<ESIContext *>(thisNode->data.getRaw());
475 assert (context.getRaw() != NULL);
43ae1d95 476
0655fa4d 477 if (context->flags.passthrough)
43ae1d95 478 return clientStreamStatus (thisNode, http);
43ae1d95 479
480 if (context->flags.oktosend && context->flags.finished &&
481 !(context->outbound.getRaw() && context->outbound_offset < context->outbound->len)) {
bf8fe701 482 debugs(86, 5, "Telling recipient EOF on STATUS");
43ae1d95 483 return STREAM_UNPLANNED_COMPLETE; /* we don't know lengths in advance */
484 }
485
486 /* ?? RC: we can't be aborted / fail ? */
43ae1d95 487 return STREAM_NONE;
488}
489
490static int
955394ce 491esiAlwaysPassthrough(Http::StatusCode sline)
43ae1d95 492{
1f140227 493 int result;
494
43ae1d95 495 switch (sline) {
496
2f8abb64 497 case Http::scContinue: /* Should never reach us... but squid needs to alter to accommodate this */
43ae1d95 498
955394ce 499 case Http::scSwitchingProtocols: /* Ditto */
43ae1d95 500
955394ce 501 case Http::scProcessing: /* Unknown - some extension */
43ae1d95 502
955394ce 503 case Http::scNoContent: /* no body, no esi */
43ae1d95 504
955394ce 505 case Http::scNotModified: /* ESI does not affect assembled page headers, so 304s are valid */
1f140227 506 result = 1;
43ae1d95 507 /* unreached */
508 break;
509
510 default:
1f140227 511 result = 0;
43ae1d95 512 }
1f140227 513
514 return result;
43ae1d95 515}
516
517void
518ESIContext::trimBlanks()
519{
520 /* trim leading empty buffers ? */
521
522 while (outbound.getRaw() && outbound->next.getRaw() && !outbound->len) {
bf8fe701 523 debugs(86, 5, "ESIContext::trimBlanks: " << this <<
524 " skipping segment " << outbound.getRaw());
43ae1d95 525 outbound = outbound->next;
526 }
527
528 if (outboundtail.getRaw())
529 assert (outbound.getRaw());
530}
531
532/* Send data downstream
533 * Returns 0 if nothing was sent. Non-zero if data was sent.
534 */
535size_t
536ESIContext::send ()
537{
bf8fe701 538 debugs(86, 5, "ESIContext::send: this=" << this);
43ae1d95 539 /* send any processed data */
540
541 trimBlanks();
542
543 if (!flags.clientwantsdata) {
bf8fe701 544 debugs(86, 5, "ESIContext::send: Client does not want data - not sending anything");
43ae1d95 545 return 0;
546 }
547
548 if (tree.getRaw() && tree->mayFail()) {
bf8fe701 549 debugs(86, 5, "ESIContext::send: Tree may fail. Not sending.");
43ae1d95 550 return 0;
551 } else
552 flags.oktosend = 1;
553
554#if 0
555
556 if (!flags.oktosend) {
557
558 fatal("ESIContext::send: Not OK to send.\n");
559 return 0;
560 }
561
562#endif
563
564 if (!(rep || (outbound.getRaw() &&
565 outbound->len && (outbound_offset <= outbound->len)))) {
bf8fe701 566 debugs(86, 5, "ESIContext::send: Nothing to send.");
43ae1d95 567 return 0;
568 }
569
bf8fe701 570 debugs(86, 5, "ESIContext::send: Sending something...");
43ae1d95 571 /* Yes! Send it without asking for more upstream */
572 /* memcopying because the client provided the buffer */
573 /* TODO: skip data until pos == next->readoff; */
574 assert (thisNode->data == this);
575 clientStreamNode *next = thisNode->next();
aa625860 576 ESIContext *templock = cbdataReference (this);
43ae1d95 577 size_t len = 0;
578
579 if (outbound.getRaw())
580 len = min (next->readBuffer.length, outbound->len - outbound_offset);
581
582 /* prevent corruption on range requests, even though we don't support them yet */
583 assert (pos == next->readBuffer.offset);
584
585 /* We must send data or a reply */
586 assert (len != 0 || rep != NULL);
587
588 if (len) {
41d00cd3 589 memcpy(next->readBuffer.data, &outbound->buf[outbound_offset], len);
43ae1d95 590
591 if (len + outbound_offset == outbound->len) {
592 ESISegment::Pointer temp = outbound->next;
593 /* remove the used buffer */
594 outbound_offset = 0;
595 outbound = temp;
596 }
597
598 pos += len;
599
600 if (!outbound.getRaw())
601 outboundtail = NULL;
602
603 trimBlanks();
604 }
605
606 flags.clientwantsdata = 0;
bf8fe701 607 debugs(86, 5, "ESIContext::send: this=" << this << " Client no longer wants data ");
43ae1d95 608 /* Deal with re-entrancy */
f522f9b5 609 HttpReplyPointer temprep = rep;
43ae1d95 610 rep = NULL; /* freed downstream */
611
612 if (temprep && varState)
f522f9b5 613 varState->buildVary(temprep.getRaw());
43ae1d95 614
615 {
616 StoreIOBuffer tempBuffer;
617 tempBuffer.length = len;
618 tempBuffer.offset = pos - len;
619 tempBuffer.data = next->readBuffer.data;
f522f9b5 620 clientStreamCallback (thisNode, http, temprep.getRaw(), tempBuffer);
43ae1d95 621 }
622
623 if (len == 0)
624 len = 1; /* tell the caller we sent something (because we sent headers */
625
aa625860 626 cbdataReferenceDone (templock);
43ae1d95 627
137a13ea 628 debugs (86,5,"ESIContext::send: this=" << this << " sent " << len);
43ae1d95 629
630 return len;
631}
632
633void
634ESIContext::finishChildren()
635{
636 if (tree.getRaw())
637 tree->finish();
638
639 tree = NULL;
640}
641
642/* Detach event from a client Stream */
643void
59a1efb2 644esiStreamDetach (clientStreamNode *thisNode, ClientHttpRequest *http)
43ae1d95 645{
646 /* if we have pending callbacks, tell them we're done. */
43ae1d95 647 /* test preconditions */
648 assert (thisNode != NULL);
649 assert (cbdataReferenceValid (thisNode));
0655fa4d 650 ESIContext::Pointer context = dynamic_cast<ESIContext *>(thisNode->data.getRaw());
651 assert (context.getRaw() != NULL);
43ae1d95 652 /* detach from the stream */
653 clientStreamDetach (thisNode,http);
654 /* if we have pending callbacks (from subincludes), tell them we're done. */
655 context->thisNode = NULL;
656 context->flags.detached = 1;
657 context->finishChildren();
658 /* HACK for parser stack not being emptied */
659 context->parserState.stack[0] = NULL;
660 /* allow refcount logic to trigger */
661 context->cbdataLocker = NULL;
43ae1d95 662}
663
664/* Process incoming data for ESI tags */
665/* ESI TODO: Long term: we should have a framework to parse html/xml and
666 * callback to a set of processors like thisNode, to prevent multiple parsing
667 * overhead. More thoughts on thisNode: We have to parse multiple times, because
668 * the output of one processor may create a very different tree. What we could
26ac0430 669 * do is something like DOM and pass that down to a final renderer. This is
43ae1d95 670 * getting into web server territory though...
26ac0430 671 *
43ae1d95 672 * Preconditions:
673 * This is not the last node in the stream.
674 * ESI processing has been enabled.
675 * There is context data or a reply structure
676 */
677void
2324cda2 678esiProcessStream (clientStreamNode *thisNode, ClientHttpRequest *http, HttpReply *rep, StoreIOBuffer receivedData)
43ae1d95 679{
43ae1d95 680 /* test preconditions */
681 assert (thisNode != NULL);
682 /* ESI TODO: handle thisNode rather than asserting - it should only ever
26ac0430 683 * happen if we cause an abort and the callback chain
43ae1d95 684 * loops back to here, so we can simply return. However, that itself
685 * shouldn't happen, so it stays as an assert for now. */
686 assert (cbdataReferenceValid (thisNode));
687 /*
688 * if data is NULL thisNode is the first entrance. If rep is also NULL,
689 * something is wrong.
690 * */
0655fa4d 691 assert (thisNode->data.getRaw() != NULL || rep);
43ae1d95 692 assert (thisNode->node.next != NULL);
693
0655fa4d 694 if (!thisNode->data.getRaw())
43ae1d95 695 /* setup ESI context from reply headers */
696 thisNode->data = ESIContextNew(rep, thisNode, http);
697
0655fa4d 698 ESIContext::Pointer context = dynamic_cast<ESIContext *>(thisNode->data.getRaw());
699
700 assert (context.getRaw() != NULL);
43ae1d95 701
702 context->finishRead();
703
704 /* Skipping all ESI processing. All remaining data gets untouched.
705 * Mainly used when an error or other non-ESI processable entity
706 * has been detected to prevent ESI processing the error body
707 */
708 if (context->flags.passthrough) {
2324cda2 709 clientStreamCallback (thisNode, http, rep, receivedData);
43ae1d95 710 return;
711 }
712
bf8fe701 713 debugs(86, 3, "esiProcessStream: Processing thisNode " << thisNode <<
714 " context " << context.getRaw() << " offset " <<
2324cda2 715 (int) receivedData.offset << " length " <<
716 (unsigned int)receivedData.length);
43ae1d95 717
718 /* once we finish the template, we *cannot* return here */
719 assert (!context->flags.finishedtemplate);
720 assert (!context->cachedASTInUse);
721
722 /* Can we generate any data ?*/
723
2324cda2 724 if (receivedData.data) {
43ae1d95 725 /* Increase our buffer area with incoming data */
2324cda2 726 assert (receivedData.length <= HTTP_REQBUF_SZ);
727 assert (thisNode->readBuffer.offset == receivedData.offset);
728 debugs (86,5, "esiProcessStream found " << receivedData.length << " bytes of body data at offset " << receivedData.offset);
43ae1d95 729 /* secure the data for later use */
730
731 if (!context->incoming.getRaw()) {
732 /* create a new buffer segment */
bf8fe701 733 debugs(86, 5, "esiProcessStream: Setting up incoming buffer");
43ae1d95 734 context->buffered = new ESISegment;
735 context->incoming = context->buffered;
736 }
737
2324cda2 738 if (receivedData.data != &context->incoming->buf[context->incoming->len]) {
43ae1d95 739 /* We have to copy the data out because we didn't supply thisNode buffer */
740 size_t space = HTTP_REQBUF_SZ - context->incoming->len;
2324cda2 741 size_t len = min (space, receivedData.length);
742 debugs(86, 5, "Copying data from " << receivedData.data << " to " <<
bf8fe701 743 &context->incoming->buf[context->incoming->len] <<
744 " because our buffer was not used");
745
41d00cd3 746 memcpy(&context->incoming->buf[context->incoming->len], receivedData.data, len);
43ae1d95 747 context->incoming->len += len;
748
749 if (context->incoming->len == HTTP_REQBUF_SZ) {
750 /* append another buffer */
751 context->incoming->next = new ESISegment;
752 context->incoming = context->incoming->next;
753 }
754
2324cda2 755 if (len != receivedData.length) {
43ae1d95 756 /* capture the remnants */
41d00cd3 757 memcpy(context->incoming->buf, &receivedData.data[len], receivedData.length - len);
2324cda2 758 context->incoming->len = receivedData.length - len;
43ae1d95 759 }
760
761 /* and note where we are up to */
2324cda2 762 context->readpos += receivedData.length;
43ae1d95 763 } else {
764 /* update our position counters, and if needed assign a new buffer */
2324cda2 765 context->incoming->len += receivedData.length;
43ae1d95 766 assert (context->incoming->len <= HTTP_REQBUF_SZ);
767
768 if (context->incoming->len > HTTP_REQBUF_SZ * 3 / 4) {
769 /* allocate a new buffer - to stop us asking for ridiculously small amounts */
770 context->incoming->next = new ESISegment;
771 context->incoming = context->incoming->next;
772 }
773
2324cda2 774 context->readpos += receivedData.length;
43ae1d95 775 }
776 }
777
778 /* EOF / Read error / aborted entry */
2324cda2 779 if (rep == NULL && receivedData.data == NULL && receivedData.length == 0 && !context->flags.finishedtemplate) {
43ae1d95 780 /* TODO: get stream status to test the entry for aborts */
781 /* else flush the esi processor */
bf8fe701 782 debugs(86, 5, "esiProcess: " << context.getRaw() << " Finished reading upstream data");
43ae1d95 783 /* This is correct */
784 context->flags.finishedtemplate = 1;
785 }
786
787 switch (context->kick()) {
788
789 case ESIContext::ESI_KICK_FAILED:
790 /* thisNode can not happen - processing can't fail until we have data,
791 * and when we come here we have sent data to the client
792 */
43ae1d95 793 return;
794
795 case ESIContext::ESI_KICK_SENT:
796
797 case ESIContext::ESI_KICK_INPROGRESS:
43ae1d95 798 return;
799
800 case ESIContext::ESI_KICK_PENDING:
801 break;
802 }
803
804 /* ok.. no data sent, try to pull more data in from upstream.
9837567d 805 * TODO: Don't try thisNode if we have finished reading the template
43ae1d95 806 */
807 if (!context->flags.finishedtemplate && !context->reading()
808 && !context->cachedASTInUse) {
809 StoreIOBuffer tempBuffer;
810 assert (context->incoming.getRaw() && context->incoming->len < HTTP_REQBUF_SZ);
811 tempBuffer.offset = context->readpos;
812 tempBuffer.length = HTTP_REQBUF_SZ - context->incoming->len;
813 tempBuffer.data = &context->incoming->buf[context->incoming->len];
814 context->startRead();
815 clientStreamRead (thisNode, http, tempBuffer);
43ae1d95 816 return;
817 }
818
bf8fe701 819 debugs(86, 3, "esiProcessStream: no data to send, no data to read, awaiting a callback");
43ae1d95 820}
821
822ESIContext::~ESIContext()
823{
824 freeResources ();
825 /* Not freed by freeresources because esi::fail needs it */
826 safe_free (errormessage);
bf8fe701 827 debugs(86, 3, "ESIContext::~ESIContext: Freed " << this);
43ae1d95 828}
829
830ESIContext *
59a1efb2 831ESIContextNew (HttpReply *rep, clientStreamNode *thisNode, ClientHttpRequest *http)
43ae1d95 832{
833 assert (rep);
834 ESIContext *rv = new ESIContext;
835 rv->rep = rep;
836 rv->cbdataLocker = rv;
837
9b769c67 838 if (esiAlwaysPassthrough(rep->sline.status())) {
43ae1d95 839 rv->flags.passthrough = 1;
840 } else {
841 /* remove specific headers for ESI to prevent
842 * downstream cache confusion */
843 HttpHeader *hdr = &rep->header;
789217a2
FC
844 hdr->delById(Http::HdrType::ACCEPT_RANGES);
845 hdr->delById(Http::HdrType::ETAG);
846 hdr->delById(Http::HdrType::CONTENT_LENGTH);
847 hdr->delById(Http::HdrType::CONTENT_MD5);
43ae1d95 848 rv->tree = new esiSequence (rv, true);
849 rv->thisNode = thisNode;
850 rv->http = http;
851 rv->flags.clientwantsdata = 1;
924f73bc 852 rv->varState = new ESIVarState (&http->request->header, http->uri);
bf8fe701 853 debugs(86, 5, "ESIContextNew: Client wants data (always created during reply cycle");
43ae1d95 854 }
855
bf8fe701 856 debugs(86, 5, "ESIContextNew: Create context " << rv);
43ae1d95 857 return rv;
858}
859
860ESIElement::ESIElementType_t
861ESIElement::IdentifyElement (const char *el)
862{
863 int offset = 0;
864 assert (el);
865
866 if (strlen (el) < 5)
867 return ESI_ELEMENT_NONE;
868
869 if (!strncmp (el, "esi:", 4))
870 offset = 4;
871 else if (!strncmp (el, "http://www.edge-delivery.org/esi/1.0|", 37))
872 offset = 37;
873 else
874 return ESI_ELEMENT_NONE;
875
876 if (!strncmp (el + offset, "otherwise", 9))
877 return ESI_ELEMENT_OTHERWISE;
878
879 if (!strncmp (el + offset, "comment", 7))
880 return ESI_ELEMENT_COMMENT;
881
882 if (!strncmp (el + offset, "include", 7))
883 return ESI_ELEMENT_INCLUDE;
884
885 if (!strncmp (el + offset, "attempt", 7))
886 return ESI_ELEMENT_ATTEMPT;
887
924f73bc 888 if (!strncmp (el + offset, "assign", 6))
889 return ESI_ELEMENT_ASSIGN;
890
43ae1d95 891 if (!strncmp (el + offset, "remove", 6))
892 return ESI_ELEMENT_REMOVE;
893
894 if (!strncmp (el + offset, "except", 6))
895 return ESI_ELEMENT_EXCEPT;
896
897 if (!strncmp (el + offset, "choose", 6))
898 return ESI_ELEMENT_CHOOSE;
899
900 if (!strncmp (el + offset, "vars", 4))
901 return ESI_ELEMENT_VARS;
902
903 if (!strncmp (el + offset, "when", 4))
904 return ESI_ELEMENT_WHEN;
905
906 if (!strncmp (el + offset, "try", 3))
907 return ESI_ELEMENT_TRY;
908
909 return ESI_ELEMENT_NONE;
910}
911
912ESIElement::Pointer
913ESIContext::ParserState::top()
914{
915 return stack[stackdepth-1];
916}
917
a5488e5d 918ESIContext::ParserState::ParserState() :
f53969cc
SM
919 stackdepth(0),
920 parsing(0),
921 inited_(false)
43ae1d95 922{}
923
924bool
925ESIContext::ParserState::inited() const
926{
927 return inited_;
928}
929
930void
931ESIContext::addStackElement (ESIElement::Pointer element)
932{
933 /* Put on the stack to allow skipping of 'invalid' markup */
f66e113e
AJ
934
935 // throw an error if the stack location would be invalid
936 if (parserState.stackdepth >= ESI_STACK_DEPTH_LIMIT)
937 throw Esi::Error("ESI Too many nested elements");
938 if (parserState.stackdepth < 0)
939 throw Esi::Error("ESI elements stack error, probable error in ESI template");
940
43ae1d95 941 assert (!failed());
bf8fe701 942 debugs(86, 5, "ESIContext::addStackElement: About to add ESI Node " << element.getRaw());
43ae1d95 943
944 if (!parserState.top()->addElement(element)) {
f66e113e 945 throw Esi::Error("ESIContext::addStackElement failed, probable error in ESI template");
43ae1d95 946 } else {
947 /* added ok, push onto the stack */
f207fe64
FC
948 parserState.stack[parserState.stackdepth] = element;
949 ++parserState.stackdepth;
43ae1d95 950 }
951}
952
953void
954ESIContext::start(const char *el, const char **attr, size_t attrCount)
955{
956 int i;
957 unsigned int ellen = strlen (el);
958 char localbuf [HTTP_REQBUF_SZ];
959 ESIElement::Pointer element;
960 int specifiedattcount = attrCount * 2;
b0365bd9 961 char *position;
9863ae03 962 Must(ellen < sizeof(localbuf)); /* prevent unexpected overruns. */
43ae1d95 963
bf8fe701 964 debugs(86, 5, "ESIContext::Start: element '" << el << "' with " << specifiedattcount << " tags");
43ae1d95 965
966 if (failed())
967 /* waiting for expat to finish the buffer we gave it */
968 return;
969
970 switch (ESIElement::IdentifyElement (el)) {
971
972 case ESIElement::ESI_ELEMENT_NONE:
973 /* Spit out elements we aren't interested in */
974 localbuf[0] = '<';
975 localbuf[1] = '\0';
9863ae03 976 xstrncpy(&localbuf[1], el, sizeof(localbuf) - 2);
b0365bd9 977 position = localbuf + strlen (localbuf);
43ae1d95 978
979 for (i = 0; i < specifiedattcount && attr[i]; i += 2) {
9863ae03 980 Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 1);
f207fe64
FC
981 *position = ' ';
982 ++position;
43ae1d95 983 /* TODO: handle thisNode gracefully */
9863ae03 984 xstrncpy(position, attr[i], sizeof(localbuf) - (position - localbuf));
b0365bd9 985 position += strlen (position);
9863ae03 986 Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 2);
f207fe64
FC
987 *position = '=';
988 ++position;
989 *position = '\"';
990 ++position;
dde08ec7
AJ
991 const char *chPtr = attr[i + 1];
992 char ch;
993 while ((ch = *chPtr++) != '\0') {
994 if (ch == '\"') {
9863ae03
AJ
995 Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 6);
996 xstrncpy(position, "&quot;", sizeof(localbuf) - (position-localbuf));
b0365bd9 997 position += 6;
dde08ec7 998 } else {
9863ae03 999 Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 1);
aec55359
FC
1000 *position = ch;
1001 ++position;
dde08ec7
AJ
1002 }
1003 }
9863ae03 1004 Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 1);
f207fe64
FC
1005 *position = '\"';
1006 ++position;
43ae1d95 1007 }
1008
9863ae03 1009 Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 2);
aec55359
FC
1010 *position = '>';
1011 ++position;
b0365bd9 1012 *position = '\0';
43ae1d95 1013
b0365bd9 1014 addLiteral (localbuf, position - localbuf);
bf8fe701 1015 debugs(86, 5, "esi stack depth " << parserState.stackdepth);
43ae1d95 1016 return;
1017 break;
1018
1019 case ESIElement::ESI_ELEMENT_COMMENT:
1020 /* Put on the stack to allow skipping of 'invalid' markup */
1021 element = new esiComment ();
1022 break;
1023
1024 case ESIElement::ESI_ELEMENT_INCLUDE:
1025 /* Put on the stack to allow skipping of 'invalid' markup */
924f73bc 1026 element = new ESIInclude (parserState.top().getRaw(), specifiedattcount, attr, this);
43ae1d95 1027 break;
1028
1029 case ESIElement::ESI_ELEMENT_REMOVE:
1030 /* Put on the stack to allow skipping of 'invalid' markup */
8c41d298 1031 element = new esiRemove();
43ae1d95 1032 break;
1033
1034 case ESIElement::ESI_ELEMENT_TRY:
1035 /* Put on the stack to allow skipping of 'invalid' markup */
1036 element = new esiTry (parserState.top().getRaw());
1037 break;
1038
1039 case ESIElement::ESI_ELEMENT_ATTEMPT:
1040 /* Put on the stack to allow skipping of 'invalid' markup */
1041 element = new esiAttempt (parserState.top().getRaw());
1042 break;
1043
1044 case ESIElement::ESI_ELEMENT_EXCEPT:
1045 /* Put on the stack to allow skipping of 'invalid' markup */
1046 element = new esiExcept (parserState.top().getRaw());
1047 break;
1048
1049 case ESIElement::ESI_ELEMENT_VARS:
1050 /* Put on the stack to allow skipping of 'invalid' markup */
924f73bc 1051 element = new ESIVar (parserState.top().getRaw());
43ae1d95 1052 break;
1053
1054 case ESIElement::ESI_ELEMENT_CHOOSE:
1055 /* Put on the stack to allow skipping of 'invalid' markup */
1056 element = new esiChoose (parserState.top().getRaw());
1057 break;
1058
1059 case ESIElement::ESI_ELEMENT_WHEN:
1060 /* Put on the stack to allow skipping of 'invalid' markup */
1061 element = new esiWhen (parserState.top().getRaw(), specifiedattcount, attr, varState);
1062 break;
1063
1064 case ESIElement::ESI_ELEMENT_OTHERWISE:
1065 /* Put on the stack to allow skipping of 'invalid' markup */
1066 element = new esiOtherwise (parserState.top().getRaw());
1067 break;
924f73bc 1068
1069 case ESIElement::ESI_ELEMENT_ASSIGN:
1070 /* Put on the stack to allow skipping of 'invalid' markup */
1071 element = new ESIAssign (parserState.top().getRaw(), specifiedattcount, attr, this);
1072 break;
43ae1d95 1073 }
1074
1075 addStackElement(element);
1076
bf8fe701 1077 debugs(86, 5, "esi stack depth " << parserState.stackdepth);
43ae1d95 1078
1079} /* End of start handler */
1080
1081void
1082ESIContext::end(const char *el)
1083{
1084 unsigned int ellen = strlen (el);
1085 char localbuf [HTTP_REQBUF_SZ];
b0365bd9 1086 char *position;
43ae1d95 1087
1088 if (flags.error)
1089 /* waiting for expat to finish the buffer we gave it */
1090 return;
1091
1092 switch (ESIElement::IdentifyElement (el)) {
1093
1094 case ESIElement::ESI_ELEMENT_NONE:
9863ae03 1095 Must(ellen < sizeof(localbuf) - 3); /* prevent unexpected overruns. */
43ae1d95 1096 /* Add elements we aren't interested in */
1097 localbuf[0] = '<';
1098 localbuf[1] = '/';
9863ae03 1099 xstrncpy(&localbuf[2], el, sizeof(localbuf) - 3);
b0365bd9 1100 position = localbuf + strlen (localbuf);
f207fe64
FC
1101 *position = '>';
1102 ++position;
b0365bd9
FC
1103 *position = '\0';
1104 addLiteral (localbuf, position - localbuf);
43ae1d95 1105 break;
1106
1107 case ESIElement::ESI_ELEMENT_COMMENT:
1108
1109 case ESIElement::ESI_ELEMENT_INCLUDE:
1110
1111 case ESIElement::ESI_ELEMENT_REMOVE:
1112
1113 case ESIElement::ESI_ELEMENT_TRY:
1114
1115 case ESIElement::ESI_ELEMENT_ATTEMPT:
1116
1117 case ESIElement::ESI_ELEMENT_EXCEPT:
1118
1119 case ESIElement::ESI_ELEMENT_VARS:
1120
1121 case ESIElement::ESI_ELEMENT_CHOOSE:
1122
1123 case ESIElement::ESI_ELEMENT_WHEN:
1124
1125 case ESIElement::ESI_ELEMENT_OTHERWISE:
924f73bc 1126
1127 case ESIElement::ESI_ELEMENT_ASSIGN:
43ae1d95 1128 /* pop of the stack */
1129 parserState.stack[--parserState.stackdepth] = NULL;
1130 break;
1131 }
1132} /* End of end handler */
1133
1134void
1135ESIContext::parserDefault (const char *s, int len)
1136{
1137 if (failed())
1138 return;
1139
1140 /* handle any skipped data */
1141 addLiteral (s, len);
1142}
1143
1144void
1145ESIContext::parserComment (const char *s)
1146{
1147 if (failed())
1148 return;
1149
1150 if (!strncmp(s, "esi",3)) {
bf8fe701 1151 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block encountered");
43ae1d95 1152 ESIParser::Pointer tempParser = ESIParser::NewParser (this);
1153
1154 /* wrap the comment in some tags */
1155
1156 if (!tempParser->parse("<div>", 5,0) ||
1157 !tempParser->parse(s + 3, strlen(s) - 3, 0) ||
1158 !tempParser->parse("</div>",6,1)) {
fa84c01d 1159 debugs(86, DBG_CRITICAL, "ESIContext::parserComment: Parsing fragment '" << s + 3 << "' failed.");
43ae1d95 1160 setError();
1161 char tempstr[1024];
137a13ea 1162 snprintf(tempstr, 1023, "ESIContext::parserComment: Parse error at line %ld:\n%s\n",
43ae1d95 1163 tempParser->lineNumber(),
1164 tempParser->errorString());
fa84c01d 1165 debugs(86, DBG_CRITICAL, "" << tempstr << "");
43ae1d95 1166
924f73bc 1167 setErrorMessage(tempstr);
43ae1d95 1168 }
1169
bf8fe701 1170 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block parsed");
43ae1d95 1171 return;
1172 } else {
1173 char localbuf [HTTP_REQBUF_SZ];
1174 unsigned int len;
bf8fe701 1175 debugs(86, 5, "ESIContext::parserComment: Regenerating comment block");
43ae1d95 1176 len = strlen (s);
1177
1178 if (len > sizeof (localbuf) - 9) {
fa84c01d 1179 debugs(86, DBG_CRITICAL, "ESIContext::parserComment: Truncating long comment");
43ae1d95 1180 len = sizeof (localbuf) - 9;
1181 }
1182
1183 xstrncpy(localbuf, "<!--", 5);
1184 xstrncpy(localbuf + 4, s, len + 1);
1185 xstrncpy(localbuf + 4 + len, "-->", 4);
1186 addLiteral (localbuf,len + 7);
1187 }
1188}
1189
1190void
1191ESIContext::addLiteral (const char *s, int len)
1192{
1193 /* handle any skipped data */
1194 assert (len);
bf8fe701 1195 debugs(86, 5, "literal length is " << len);
43ae1d95 1196 /* give a literal to the current element */
43ae1d95 1197 ESIElement::Pointer element (new esiLiteral (this, s, len));
1198
f66e113e
AJ
1199 if (!parserState.top()->addElement(element))
1200 throw Esi::Error("ESIContext::addLiteral failed, probable error in ESI template");
43ae1d95 1201}
1202
1203void
1204ESIContext::ParserState::init(ESIParserClient *userData)
1205{
1206 theParser = ESIParser::NewParser (userData);
1207 inited_ = true;
1208}
1209
1210void
1211ESIContext::parseOneBuffer()
1212{
1213 assert (buffered.getRaw());
1214
137a13ea 1215 debugs (86,9,"ESIContext::parseOneBuffer: " << buffered->len << " bytes");
43ae1d95 1216 bool lastBlock = buffered->next.getRaw() == NULL && flags.finishedtemplate ? true : false;
1217
1218 if (! parserState.theParser->parse(buffered->buf, buffered->len, lastBlock)) {
1219 setError();
1220 char tempstr[1024];
137a13ea 1221 snprintf (tempstr, 1023, "esiProcess: Parse error at line %ld:\n%s\n",
43ae1d95 1222 parserState.theParser->lineNumber(),
1223 parserState.theParser->errorString());
fa84c01d 1224 debugs(86, DBG_CRITICAL, "" << tempstr << "");
43ae1d95 1225
924f73bc 1226 setErrorMessage(tempstr);
43ae1d95 1227
1228 assert (flags.error);
1229
1230 return;
1231 }
1232
1233 if (flags.error) {
1234 setError();
1235 return;
1236 }
1237
1238 ESISegment::Pointer temp = buffered;
1239 buffered = temp->next;
1240}
1241
1242void
1243ESIContext::parse()
1244{
1245 if (!parserState.stackdepth) {
bf8fe701 1246 debugs(86, 5, "empty parser stack, inserting the top level node");
43ae1d95 1247 assert (tree.getRaw());
f207fe64
FC
1248 parserState.stack[parserState.stackdepth] = tree;
1249 ++parserState.stackdepth;
43ae1d95 1250 }
1251
1252 if (rep && !parserState.inited())
1253 parserState.init(this);
1254
1255 /* we have data */
1256 if (buffered.getRaw()) {
1257 parserState.parsing = 1;
1258 /* we don't keep any data around */
1259
1260 PROF_start(esiParsing);
1261
f66e113e
AJ
1262 try {
1263 while (buffered.getRaw() && !flags.error)
1264 parseOneBuffer();
1265
9837567d 1266 } catch (Esi::ErrorDetail &errMsg) { // XXX: non-const for c_str()
f66e113e
AJ
1267 // level-2: these are protocol/syntax errors from upstream
1268 debugs(86, 2, "WARNING: ESI syntax error: " << errMsg);
1269 setError();
1270 setErrorMessage(errMsg.c_str());
1271
1272 } catch (...) {
1273 // DBG_IMPORTANT because these are local issues the admin needs to fix
1274 static FadingCounter logEntries; // TODO: set horizon less than infinity
1275 if (logEntries.count(1) < 100)
1276 debugs(86, DBG_IMPORTANT, "ERROR: ESI parser: " << CurrentException);
1277 setError();
1278 setErrorMessage("ESI parser error");
1279 }
43ae1d95 1280
1281 PROF_stop(esiParsing);
1282
1283 /* Tel the read code to allocate a new buffer */
1284 incoming = NULL;
1285
1286 parserState.parsing = 0;
1287 }
1288}
1289
1290esiProcessResult_t
1291ESIContext::process ()
1292{
1293 /* parsing:
1294 * read through buffered, skipping plain text, and skipping any
1295 * <...> entry that is not an <esi: entry.
14e9491b 1296 * when it's found, hand an esiLiteral of the preceding data to our current
43ae1d95 1297 * context
1298 */
1299
1300 if (parserState.parsing) {
1301 /* in middle of parsing - finish here */
1302 return ESI_PROCESS_PENDING_MAYFAIL;
1303 }
1304
1305 assert (flags.finished == 0);
1306
1307 assert (!flags.error);
1308
1309 if (!hasCachedAST())
1310 parse();
1311 else if (!flags.finishedtemplate)
1312 getCachedAST();
1313
1314 if (flags.error) {
bf8fe701 1315 debugs(86, 5, "ESIContext::process: Parsing failed");
43ae1d95 1316 finishChildren ();
1317 parserState.popAll();
1318 return ESI_PROCESS_FAILED;
1319 }
1320
1321 if (!flags.finishedtemplate && !incoming.getRaw() && !cachedASTInUse) {
1322 buffered = new ESISegment;
1323 incoming = buffered;
1324 }
1325
1326 if (!flags.finishedtemplate && !cachedASTInUse) {
1327 return ESI_PROCESS_PENDING_MAYFAIL;
1328 }
1329
1330 assert (flags.finishedtemplate || cachedASTInUse);
1331 updateCachedAST();
1332 /* ok, we've done all we can with the data. What can we process now?
1333 */
1334 {
1335 esiProcessResult_t status;
1336 PROF_start(esiProcessing);
1337 processing = true;
1338 status = tree->process(0);
1339 processing = false;
1340
26ac0430 1341 switch (status) {
43ae1d95 1342
1343 case ESI_PROCESS_COMPLETE:
bf8fe701 1344 debugs(86, 5, "esiProcess: tree Processed OK");
43ae1d95 1345 break;
1346
1347 case ESI_PROCESS_PENDING_WONTFAIL:
bf8fe701 1348 debugs(86, 5, "esiProcess: tree Processed PENDING OK");
43ae1d95 1349 break;
1350
1351 case ESI_PROCESS_PENDING_MAYFAIL:
bf8fe701 1352 debugs(86, 5, "esiProcess: tree Processed PENDING UNKNOWN");
43ae1d95 1353 break;
1354
1355 case ESI_PROCESS_FAILED:
fa84c01d 1356 debugs(86, DBG_CRITICAL, "esiProcess: tree Processed FAILED");
43ae1d95 1357 setError();
1358
924f73bc 1359 setErrorMessage("esiProcess: ESI template Processing failed.");
43ae1d95 1360
1361 PROF_stop(esiProcessing);
1362
1363 return ESI_PROCESS_FAILED;
1364
1365 break;
1366 }
1367
26ac0430 1368 if (status != ESI_PROCESS_PENDING_MAYFAIL && (flags.finishedtemplate || cachedASTInUse)) {
43ae1d95 1369 /* We've read the entire template, and no nodes will
1370 * return failure
1371 */
bf8fe701 1372 debugs(86, 5, "esiProcess, request will succeed");
43ae1d95 1373 flags.oktosend = 1;
1374 }
1375
1376 if (status == ESI_PROCESS_COMPLETE
26ac0430 1377 && (flags.finishedtemplate || cachedASTInUse)) {
43ae1d95 1378 /* we've finished all processing. Render and send. */
bf8fe701 1379 debugs(86, 5, "esiProcess, processing complete");
43ae1d95 1380 flags.finished = 1;
1381 }
1382
1383 PROF_stop(esiProcessing);
1384 return status; /* because we have no callbacks */
1385 }
1386}
1387
1388void
1389ESIContext::ParserState::freeResources()
1390{
1391 theParser = NULL;
1392 inited_ = false;
1393}
1394
1395void
1396ESIContext::ParserState::popAll()
1397{
1398 while (stackdepth)
1399 stack[--stackdepth] = NULL;
1400}
1401
1402void
1403ESIContext::freeResources ()
1404{
b9fde3d8 1405 debugs(86, 5, HERE << "Freeing for this=" << this);
43ae1d95 1406
f522f9b5 1407 rep = nullptr; // refcounted
43ae1d95 1408
1409 finishChildren ();
1410
1411 if (parserState.inited()) {
1412 parserState.freeResources();
1413 }
1414
1415 parserState.popAll();
1416 ESISegmentFreeList (buffered);
1417 ESISegmentFreeList (outbound);
1418 ESISegmentFreeList (outboundtail);
00d77d6b 1419 delete varState;
6269c7b1 1420 varState=NULL;
43ae1d95 1421 /* don't touch incoming, it's a pointer into buffered anyway */
1422}
1423
7976fed3 1424ErrorState *clientBuildError(err_type, Http::StatusCode, char const *, const ConnStateData *, HttpRequest *, const AccessLogEntry::Pointer &);
43ae1d95 1425
43ae1d95 1426/* This can ONLY be used before we have sent *any* data to the client */
1427void
1428ESIContext::fail ()
1429{
bf8fe701 1430 debugs(86, 5, "ESIContext::fail: this=" << this);
43ae1d95 1431 /* check preconditions */
1432 assert (pos == 0);
1433 /* cleanup current state */
1434 freeResources ();
1435 /* Stop altering thisNode request */
1436 flags.oktosend = 1;
1437 flags.finished = 1;
1438 /* don't honour range requests - for errors we send it all */
1439 flags.error = 1;
1440 /* create an error object */
1b76e6c1 1441 // XXX: with the in-direction on remote IP. does the http->getConn()->clientConnection exist?
7976fed3 1442 const auto err = clientBuildError(errorpage, errorstatus, nullptr, http->getConn(), http->request, http->al);
43ae1d95 1443 err->err_msg = errormessage;
1444 errormessage = NULL;
c70281f8 1445 rep = err->BuildHttpReply();
7e6eabbc 1446 // XXX: Leaking err!
0521f8be
FC
1447 assert (rep->body.hasContent());
1448 size_t errorprogress = rep->body.contentSize();
43ae1d95 1449 /* Tell esiSend where to start sending from */
1450 outbound_offset = 0;
1451 /* copy the membuf from the reply to outbound */
1452
0521f8be 1453 while (errorprogress < (size_t)rep->body.contentSize()) {
43ae1d95 1454 appendOutboundData(new ESISegment);
0521f8be 1455 errorprogress += outboundtail->append(rep->body.content() + errorprogress, rep->body.contentSize() - errorprogress);
43ae1d95 1456 }
1457
1458 /* the esiCode now thinks that the error is the outbound,
1459 * and all processing has finished. */
1460 /* Send as much as we can */
1461 send ();
1462
1463 /* don't cancel anything. The stream nodes will clean up after
26ac0430 1464 * themselves when the reply is freed - and we don't know what to
43ae1d95 1465 * clean anyway.
1466 */
1467}
1468
43ae1d95 1469/* Implementation of ESIElements */
1470
1471/* esiComment */
1472esiComment::~esiComment()
1473{
bf8fe701 1474 debugs(86, 5, "esiComment::~esiComment " << this);
43ae1d95 1475}
1476
43ae1d95 1477esiComment::esiComment()
1478{}
1479
1480void
1481esiComment::finish()
1482{}
1483
1484void
1485esiComment::render(ESISegment::Pointer output)
1486{
1487 /* Comments do nothing dude */
bf8fe701 1488 debugs(86, 5, "esiCommentRender: Rendering comment " << this << " into " << output.getRaw());
43ae1d95 1489}
1490
1491ESIElement::Pointer
1492esiComment::makeCacheable() const
1493{
bf8fe701 1494 debugs(86, 5, "esiComment::makeCacheable: returning NULL");
43ae1d95 1495 return NULL;
1496}
1497
1498ESIElement::Pointer
924f73bc 1499esiComment::makeUsable(esiTreeParentPtr, ESIVarState &) const
43ae1d95 1500{
1501 fatal ("esiComment::Usable: unreachable code!\n");
1502 return NULL;
1503}
1504
1505/* esiLiteral */
43ae1d95 1506esiLiteral::~esiLiteral()
1507{
bf8fe701 1508 debugs(86, 5, "esiLiteral::~esiLiteral: " << this);
43ae1d95 1509 ESISegmentFreeList (buffer);
1510 cbdataReferenceDone (varState);
1511}
1512
d59e4742
FC
1513esiLiteral::esiLiteral(ESISegment::Pointer aSegment) :
1514 buffer(aSegment),
1515 varState(nullptr)
43ae1d95 1516{
43ae1d95 1517 /* Nothing to do */
1518 flags.donevars = 1;
1519}
1520
1521void
1522esiLiteral::finish()
1523{}
1524
1525/* precondition: the buffer chain has at least start + length bytes of data
1526 */
1527esiLiteral::esiLiteral(ESIContext *context, const char *s, int numberOfCharacters)
1528{
1529 assert (s);
a5488e5d 1530 flags.donevars = 0;
43ae1d95 1531 buffer = new ESISegment;
1532 ESISegment::Pointer local = buffer;
57d55dfa 1533 size_t start = 0;
43ae1d95 1534 int remainingCharacters = numberOfCharacters;
1535
1536 while (remainingCharacters > 0) {
1537 if (local->len == sizeof (local->buf)) {
1538 local->next = new ESISegment;
1539 local=local->next;
1540 }
1541
1542 size_t len = local->append (&s[start], remainingCharacters);
1543 start += len;
1544 remainingCharacters -= len;
1545 }
1546
a5488e5d 1547 varState = cbdataReference(context->varState);
43ae1d95 1548}
1549
1550void
1551esiLiteral::render (ESISegment::Pointer output)
1552{
bf8fe701 1553 debugs(86, 9, "esiLiteral::render: Rendering " << this);
43ae1d95 1554 /* append the entire chain */
1555 assert (output->next.getRaw() == NULL);
1556 output->next = buffer;
1557 buffer = NULL;
1558}
1559
1560esiProcessResult_t
1561esiLiteral::process (int dovars)
1562{
1563 if (flags.donevars)
1564 return ESI_PROCESS_COMPLETE;
1565
1566 if (dovars) {
1567 ESISegment::Pointer temp = buffer;
1568 /* Ensure variable state is clean */
1569
1570 while (temp.getRaw()) {
6c3b212b 1571 varState->feedData(temp->buf,temp->len);
43ae1d95 1572 temp = temp->next;
1573 }
1574
1575 /* free the pre-processed content */
1576 ESISegmentFreeList (buffer);
1577
1578 buffer = varState->extractList ();
1579 }
1580
1581 flags.donevars = 1;
1582 return ESI_PROCESS_COMPLETE;
1583}
1584
1585esiLiteral::esiLiteral(esiLiteral const &old) : buffer (old.buffer->cloneList()),
f53969cc 1586 varState (NULL)
43ae1d95 1587{
1588 flags.donevars = 0;
1589}
1590
1591ESIElement::Pointer
1592esiLiteral::makeCacheable() const
1593{
1594 return new esiLiteral (*this);
1595}
1596
1597ESIElement::Pointer
9e167fa2 1598esiLiteral::makeUsable(esiTreeParentPtr, ESIVarState &newVarState) const
43ae1d95 1599{
bf8fe701 1600 debugs(86, 5, "esiLiteral::makeUsable: Creating usable literal");
43ae1d95 1601 esiLiteral * result = new esiLiteral (*this);
1602 result->varState = cbdataReference (&newVarState);
1603 return result;
1604}
1605
924f73bc 1606/* esiRemove */
924f73bc 1607void
1608esiRemove::render(ESISegment::Pointer output)
1609{
1610 /* Removes do nothing dude */
bf8fe701 1611 debugs(86, 5, "esiRemoveRender: Rendering remove " << this);
43ae1d95 1612}
1613
924f73bc 1614/* Accept non-ESI children */
1615bool
1616esiRemove::addElement (ESIElement::Pointer element)
43ae1d95 1617{
924f73bc 1618 if (!dynamic_cast<esiLiteral*>(element.getRaw())) {
bf8fe701 1619 debugs(86, 5, "esiRemoveAdd: Failed for " << this);
924f73bc 1620 return false;
1621 }
43ae1d95 1622
924f73bc 1623 return true;
43ae1d95 1624}
1625
924f73bc 1626ESIElement::Pointer
1627esiRemove::makeCacheable() const
43ae1d95 1628{
bf8fe701 1629 debugs(86, 5, "esiRemove::makeCacheable: Returning NULL");
924f73bc 1630 return NULL;
1631}
43ae1d95 1632
924f73bc 1633ESIElement::Pointer
1634esiRemove::makeUsable(esiTreeParentPtr, ESIVarState &) const
1635{
1636 fatal ("esiRemove::Usable: unreachable code!\n");
1637 return NULL;
1638}
43ae1d95 1639
924f73bc 1640/* esiTry */
1641esiTry::~esiTry()
1642{
bf8fe701 1643 debugs(86, 5, "esiTry::~esiTry " << this);
924f73bc 1644}
43ae1d95 1645
a5488e5d 1646esiTry::esiTry(esiTreeParentPtr aParent) :
f53969cc
SM
1647 parent(aParent),
1648 exceptbuffer(NULL)
a5488e5d
AJ
1649{
1650 memset(&flags, 0, sizeof(flags));
1651}
43ae1d95 1652
1653void
a5488e5d 1654esiTry::render(ESISegment::Pointer output)
43ae1d95 1655{
1656 /* Try renders from it's children */
43ae1d95 1657 assert (attempt.getRaw());
1658 assert (except.getRaw());
bf8fe701 1659 debugs(86, 5, "esiTryRender: Rendering Try " << this);
43ae1d95 1660
1661 if (flags.attemptok) {
1662 attempt->render(output);
1663 } else if (flags.exceptok) {
1664 /* prerendered */
1665
1666 if (exceptbuffer.getRaw())
1667 ESISegment::ListTransfer(exceptbuffer, output);
1668 else
1669 except->render(output);
1670 } else
bf8fe701 1671 debugs(86, 5, "esiTryRender: Neither except nor attempt succeeded?!?");
43ae1d95 1672}
1673
1674/* Accept attempt and except only */
1675bool
1676esiTry::addElement(ESIElement::Pointer element)
1677{
bf8fe701 1678 debugs(86, 5, "esiTryAdd: Try " << this << " adding element " <<
1679 element.getRaw());
43ae1d95 1680
1681 if (dynamic_cast<esiLiteral*>(element.getRaw())) {
1682 /* Swallow whitespace */
bf8fe701 1683 debugs(86, 5, "esiTryAdd: Try " << this << " skipping whitespace " << element.getRaw());
43ae1d95 1684 return true;
1685 }
1686
1687 if (dynamic_cast<esiAttempt*>(element.getRaw())) {
1688 if (attempt.getRaw()) {
61beade2 1689 debugs(86, DBG_IMPORTANT, "esiTryAdd: Failed for " << this << " - try already has an attempt node (section 3.4)");
43ae1d95 1690 return false;
1691 }
1692
1693 attempt = element;
1694 return true;
1695 }
1696
1697 if (dynamic_cast<esiExcept*>(element.getRaw())) {
1698 if (except.getRaw()) {
e0236918 1699 debugs(86, DBG_IMPORTANT, "esiTryAdd: Failed for " << this << " - try already has an except node (section 3.4)");
43ae1d95 1700 return false;
1701 }
1702
1703 except = element;
1704 return true;
1705 }
1706
e0236918 1707 debugs(86, DBG_IMPORTANT, "esiTryAdd: Failed to add element " << element.getRaw() << " to try " << this << ", incorrect element type (see section 3.4)");
43ae1d95 1708 return false;
1709}
1710
1711esiProcessResult_t
1712esiTry::bestAttemptRV() const
1713{
1714 if (flags.attemptfailed)
1715 return ESI_PROCESS_COMPLETE;
1716 else
1717 return ESI_PROCESS_PENDING_MAYFAIL;
1718}
1719
1720esiProcessResult_t
1721esiTry::process (int dovars)
1722{
1723 esiProcessResult_t rv = ESI_PROCESS_PENDING_MAYFAIL;
43ae1d95 1724
1725 if (!attempt.getRaw()) {
fa84c01d 1726 debugs(86, DBG_CRITICAL, "esiTryProcess: Try has no attempt element - ESI template is invalid (section 3.4)");
43ae1d95 1727 return ESI_PROCESS_FAILED;
1728 }
1729
1730 if (!except.getRaw()) {
fa84c01d 1731 debugs(86, DBG_CRITICAL, "esiTryProcess: Try has no except element - ESI template is invalid (section 3.4)");
43ae1d95 1732 return ESI_PROCESS_FAILED;
1733 }
1734
1735 if (!flags.attemptfailed)
1736 /* Try the attempt branch */
1737 switch ((rv = attempt->process(dovars))) {
1738
1739 case ESI_PROCESS_COMPLETE:
bf8fe701 1740 debugs(86, 5, "esiTryProcess: attempt Processed OK");
43ae1d95 1741 flags.attemptok = 1;
1742 return ESI_PROCESS_COMPLETE;
1743
1744 case ESI_PROCESS_PENDING_WONTFAIL:
bf8fe701 1745 debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
43ae1d95 1746 /* We're not done yet, but don't need to test except */
1747 return ESI_PROCESS_PENDING_WONTFAIL;
1748
1749 case ESI_PROCESS_PENDING_MAYFAIL:
bf8fe701 1750 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
43ae1d95 1751 break;
1752
1753 case ESI_PROCESS_FAILED:
bf8fe701 1754 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
43ae1d95 1755 flags.attemptfailed = 1;
1756 break;
1757 }
1758
1759 /* attempt is either MAYFAIL or FAILED */
1760 if (flags.exceptok)
1761 return bestAttemptRV();
1762
1763 /* query except to see if it has a definite result */
1764 if (!flags.exceptfailed)
1765 /* Try the except branch */
1766 switch (except->process(dovars)) {
1767
1768 case ESI_PROCESS_COMPLETE:
bf8fe701 1769 debugs(86, 5, "esiTryProcess: except Processed OK");
43ae1d95 1770 flags.exceptok = 1;
1771 return bestAttemptRV();
1772
1773 case ESI_PROCESS_PENDING_WONTFAIL:
bf8fe701 1774 debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
43ae1d95 1775 /* We're not done yet, but can't fail */
1776 return ESI_PROCESS_PENDING_WONTFAIL;
1777
1778 case ESI_PROCESS_PENDING_MAYFAIL:
bf8fe701 1779 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
43ae1d95 1780 /* The except branch fail fail */
1781 return ESI_PROCESS_PENDING_MAYFAIL;
1782
1783 case ESI_PROCESS_FAILED:
bf8fe701 1784 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
43ae1d95 1785 flags.exceptfailed = 1;
1786 break;
1787 }
1788
1789 if (flags.exceptfailed && flags.attemptfailed)
1790 return ESI_PROCESS_FAILED;
1791
1792 /* one of attempt or except returned PENDING MAYFAIL */
1793 return ESI_PROCESS_PENDING_MAYFAIL;
1794}
1795
1796void
1797esiTry::notifyParent()
1798{
1799 if (flags.attemptfailed) {
1800 if (flags.exceptok) {
1801 parent->provideData (exceptbuffer, this);
1802 exceptbuffer = NULL;
1803 } else if (flags.exceptfailed || except.getRaw() == NULL) {
924f73bc 1804 parent->fail (this, "esi:try - except claused failed, or no except clause found");
43ae1d95 1805 }
1806 }
1807
1808 /* nothing to do when except fails and attempt hasn't */
1809}
1810
1811void
924f73bc 1812esiTry::fail(ESIElement *source, char const *anError)
43ae1d95 1813{
1814 assert (source);
1815 assert (source == attempt || source == except);
bf8fe701 1816 debugs(86, 5, "esiTry::fail: this=" << this << ", source=" << source << ", message=" << anError);
43ae1d95 1817
1818 if (source == except) {
1819 flags.exceptfailed = 1;
1820 } else {
1821 flags.attemptfailed = 1;
1822 }
1823
1824 notifyParent();
1825}
1826
1827void
1828esiTry::provideData (ESISegment::Pointer data, ESIElement* source)
1829{
1830 if (source == attempt) {
1831 flags.attemptok = 1;
1832 parent->provideData (data, this);
1833 } else if (source == except) {
1834 flags.exceptok = 1;
1835 assert (exceptbuffer == NULL);
1836 ESISegment::ListTransfer (data, exceptbuffer);
1837 notifyParent();
1838 }
1839}
1840
1841esiTry::esiTry(esiTry const &old)
1842{
1843 attempt = NULL;
1844 except = NULL;
1845 flags.attemptok = 0;
1846 flags.exceptok = 0;
1847 flags.attemptfailed = 0;
1848 flags.exceptfailed = 0;
1849 parent = NULL;
1850 exceptbuffer = NULL;
1851}
1852
1853ESIElement::Pointer
1854esiTry::makeCacheable() const
1855{
bf8fe701 1856 debugs(86, 5, "esiTry::makeCacheable: making cachable Try from " << this);
43ae1d95 1857 esiTry *resultT = new esiTry (*this);
1858 ESIElement::Pointer result = resultT;
1859
1860 if (attempt.getRaw())
1861 resultT->attempt = attempt->makeCacheable();
1862
1863 if (except.getRaw())
1864 resultT->except = except->makeCacheable();
1865
1866 return result;
1867}
1868
1869ESIElement::Pointer
924f73bc 1870esiTry::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
43ae1d95 1871{
bf8fe701 1872 debugs(86, 5, "esiTry::makeUsable: making usable Try from " << this);
43ae1d95 1873 esiTry *resultT = new esiTry (*this);
1874 ESIElement::Pointer result = resultT;
1875
1876 resultT->parent = newParent;
1877
1878 if (attempt.getRaw())
1879 resultT->attempt = attempt->makeUsable(resultT, newVarState);
1880
1881 if (except.getRaw())
1882 resultT->except = except->makeUsable(resultT, newVarState);
1883
1884 return result;
1885}
1886
1887void
1888esiTry::finish()
1889{
1890 parent = NULL;
1891
1892 if (attempt.getRaw())
1893 attempt->finish();
1894
1895 attempt = NULL;
1896
1897 if (except.getRaw())
1898 except->finish();
1899
1900 except = NULL;
1901}
1902
43ae1d95 1903/* esiChoose */
1904esiChoose::~esiChoose()
1905{
bf8fe701 1906 debugs(86, 5, "esiChoose::~esiChoose " << this);
b56b37cf 1907 FinishAllElements(elements); // finish if not already done
43ae1d95 1908}
1909
d59e4742
FC
1910esiChoose::esiChoose(esiTreeParentPtr aParent) :
1911 elements(),
1912 chosenelement(-1),
1913 parent(aParent)
43ae1d95 1914{}
1915
1916void
1917esiChoose::render(ESISegment::Pointer output)
1918{
1919 /* append all processed elements, and trim processed and rendered elements */
1920 assert (output->next == NULL);
1921 assert (elements.size() || otherwise.getRaw());
bf8fe701 1922 debugs(86, 5, "esiChooseRender: rendering");
43ae1d95 1923
1924 if (chosenelement >= 0)
1925 elements[chosenelement]->render(output);
1926 else if (otherwise.getRaw())
1927 otherwise->render(output);
1928}
1929
1930bool
1931esiChoose::addElement(ESIElement::Pointer element)
1932{
1933 /* add an element to the output list */
1934
1935 if (dynamic_cast<esiLiteral*>(element.getRaw())) {
1936 /* Swallow whitespace */
bf8fe701 1937 debugs(86, 5, "esiChooseAdd: Choose " << this << " skipping whitespace " << element.getRaw());
43ae1d95 1938 return true;
1939 }
1940
1941 /* Some elements require specific parents */
1942 if (!(dynamic_cast<esiWhen*>(element.getRaw()) || dynamic_cast<esiOtherwise*>(element.getRaw()))) {
fa84c01d 1943 debugs(86, DBG_CRITICAL, "esiChooseAdd: invalid child node for esi:choose (section 3.3)");
43ae1d95 1944 return false;
1945 }
1946
1947 if (dynamic_cast<esiOtherwise*>(element.getRaw())) {
1948 if (otherwise.getRaw()) {
fa84c01d 1949 debugs(86, DBG_CRITICAL, "esiChooseAdd: only one otherwise node allowed for esi:choose (section 3.3)");
43ae1d95 1950 return false;
1951 }
1952
1953 otherwise = element;
1954 } else {
1955 elements.push_back (element);
1956
137a13ea 1957 debugs (86,3, "esiChooseAdd: Added a new element, elements = " << elements.size());
43ae1d95 1958
ea25a575
TH
1959 if (chosenelement == -1) {
1960 const esiWhen * topElement=dynamic_cast<esiWhen *>(element.getRaw());
1961 if (topElement && topElement->testsTrue()) {
43ae1d95 1962 chosenelement = elements.size() - 1;
137a13ea 1963 debugs (86,3, "esiChooseAdd: Chose element " << elements.size());
43ae1d95 1964 }
ea25a575 1965 }
43ae1d95 1966 }
1967
1968 return true;
1969}
1970
1971void
1972esiChoose::selectElement()
1973{
1974 if (chosenelement > -1)
1975 return;
1976
1977 for (size_t counter = 0; counter < elements.size(); ++counter) {
a5488e5d
AJ
1978 const esiWhen *el = dynamic_cast<esiWhen *>(elements[counter].getRaw());
1979 if (el && el->testsTrue()) {
43ae1d95 1980 chosenelement = counter;
137a13ea 1981 debugs (86,3, "esiChooseAdd: Chose element " << counter + 1);
43ae1d95 1982 return;
1983 }
1984 }
1985}
1986
b56b37cf
AJ
1987// TODO: make ESIElement destructor call finish() instead so it is
1988// a) only called when an element ref-count is 0, and
1989// b) caller can elements.clear() instead of doing this
43ae1d95 1990void
b56b37cf 1991FinishAnElement(ESIElement::Pointer &element, int pos)
43ae1d95 1992{
b56b37cf
AJ
1993 if (element)
1994 element->finish();
43ae1d95 1995
b56b37cf
AJ
1996 debugs(86, 5, "setting index " << pos << ", pointer " << (void*)element.getRaw() << " to nil");
1997 element = nullptr;
43ae1d95 1998}
1999
2000void
b56b37cf 2001FinishAllElements(Esi::Elements &elements)
43ae1d95 2002{
b56b37cf
AJ
2003 int pos = 0;
2004 for (auto &element : elements)
2005 FinishAnElement(element, pos++);
2006}
2007
b56b37cf
AJ
2008void
2009esiChoose::finish()
2010{
2011 FinishAllElements(elements);
43ae1d95 2012
b56b37cf
AJ
2013 if (otherwise.getRaw())
2014 otherwise->finish();
43ae1d95 2015
b56b37cf
AJ
2016 otherwise = nullptr;
2017 parent = nullptr;
43ae1d95 2018}
2019
2020void
2021esiChoose::NULLUnChosen()
2022{
2023 if (chosenelement >= 0) {
2024 if (otherwise.getRaw())
2025 otherwise->finish();
2026
2027 otherwise = NULL;
2028
b56b37cf
AJ
2029 int pos = 0;
2030 for (auto &element : elements) {
2031 if (pos != chosenelement)
2032 FinishAnElement(element, pos++);
2033 }
43ae1d95 2034
43ae1d95 2035 } else if (otherwise.getRaw()) {
b56b37cf 2036 FinishAllElements(elements);
43ae1d95 2037 }
2038}
2039
2040esiProcessResult_t
2041esiChoose::process (int dovars)
2042{
2043 /* process as much of the list as we can, stopping only on
2f8abb64 2044 * failures
43ae1d95 2045 */
2046 /* We MUST have a when clause */
2047 NULLUnChosen();
2048
2049 if (!elements.size()) {
2050 parent->fail(this);
2051
2052 if (otherwise.getRaw())
2053 otherwise->finish();
2054
2055 otherwise = NULL;
2056
2057 parent = NULL;
2058
2059 return ESI_PROCESS_FAILED;
2060 }
2061
2062 if (chosenelement >= 0) {
2063 return elements[chosenelement]->process(dovars);
2064 } else if (otherwise.getRaw())
2065 return otherwise->process(dovars);
2066 else
2067 return ESI_PROCESS_COMPLETE;
2068}
2069
2070void
2071esiChoose::checkValidSource (ESIElement::Pointer source) const
2072{
2073 if (!elements.size())
2074 fatal ("invalid callback = no when clause\n");
2075
2076 if (chosenelement >= 0)
2077 assert (source == elements[chosenelement]);
2078 else if (otherwise.getRaw())
2079 assert (source == otherwise);
2080 else
2081 fatal ("esiChoose::checkValidSource: invalid callback - no elements chosen\n");
2082}
2083
2084void
924f73bc 2085esiChoose::fail(ESIElement * source, char const *anError)
43ae1d95 2086{
2087 checkValidSource (source);
b56b37cf 2088 FinishAllElements(elements);
43ae1d95 2089
2090 if (otherwise.getRaw())
2091 otherwise->finish();
2092
2093 otherwise = NULL;
2094
924f73bc 2095 parent->fail(this, anError);
43ae1d95 2096
2097 parent = NULL;
2098}
2099
2100void
2101esiChoose::provideData (ESISegment::Pointer data, ESIElement*source)
2102{
2103 checkValidSource (source);
2104 parent->provideData (data, this);
2105}
2106
43ae1d95 2107esiChoose::esiChoose(esiChoose const &old) : chosenelement(-1), otherwise (NULL), parent (NULL)
2108{
2109 for (size_t counter = 0; counter < old.elements.size(); ++counter) {
2110 ESIElement::Pointer newElement = old.elements[counter]->makeCacheable();
2111
2112 if (newElement.getRaw())
2113 assert (addElement(newElement));
2114 }
2115}
2116
2117void
2118esiChoose::makeCachableElements(esiChoose const &old)
2119{
2120 for (size_t counter = 0; counter < old.elements.size(); ++counter) {
2121 ESIElement::Pointer newElement = old.elements[counter]->makeCacheable();
2122
2123 if (newElement.getRaw())
2124 assert (addElement(newElement));
2125 }
2126}
2127
2128void
924f73bc 2129esiChoose::makeUsableElements(esiChoose const &old, ESIVarState &newVarState)
43ae1d95 2130{
2131 for (size_t counter = 0; counter < old.elements.size(); ++counter) {
2132 ESIElement::Pointer newElement = old.elements[counter]->makeUsable (this, newVarState);
2133
2134 if (newElement.getRaw())
2135 assert (addElement(newElement));
2136 }
2137}
2138
2139ESIElement::Pointer
2140esiChoose::makeCacheable() const
2141{
2142 esiChoose *resultC = new esiChoose (*this);
2143 ESIElement::Pointer result = resultC;
2144 resultC->makeCachableElements(*this);
2145
2146 if (otherwise.getRaw())
2147 resultC->otherwise = otherwise->makeCacheable();
2148
2149 return result;
2150}
2151
2152ESIElement::Pointer
924f73bc 2153esiChoose::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
43ae1d95 2154{
2155 esiChoose *resultC = new esiChoose (*this);
2156 ESIElement::Pointer result = resultC;
2157 resultC->parent = newParent;
2158 resultC->makeUsableElements(*this, newVarState);
2159 resultC->selectElement();
2160
2161 if (otherwise.getRaw())
2162 resultC->otherwise = otherwise->makeUsable(resultC, newVarState);
2163
2164 return result;
2165}
2166
43ae1d95 2167/* esiWhen */
a5488e5d 2168esiWhen::esiWhen(esiTreeParentPtr aParent, int attrcount, const char **attr,ESIVarState *aVar) :
f53969cc
SM
2169 esiSequence(aParent),
2170 testValue(false),
2171 unevaluatedExpression(NULL),
2172 varState(NULL)
43ae1d95 2173{
43ae1d95 2174 char const *expression = NULL;
2175
2176 for (int loopCounter = 0; loopCounter < attrcount && attr[loopCounter]; loopCounter += 2) {
2177 if (!strcmp(attr[loopCounter],"test")) {
2178 /* evaluate test */
bf8fe701 2179 debugs(86, 5, "esiWhen::esiWhen: Evaluating '" << attr[loopCounter+1] << "'");
43ae1d95 2180 /* TODO: warn the user instead of asserting */
2181 assert (expression == NULL);
2182 expression = attr[loopCounter+1];
2183 } else {
2184 /* ignore mistyped attributes.
2185 * TODO:? error on these for user feedback - config parameter needed
2186 */
e0236918 2187 debugs(86, DBG_IMPORTANT, "Found misttyped attribute on ESI When clause");
43ae1d95 2188 }
2189 }
2190
2191 /* No expression ? default is not matching */
2192 if (!expression)
2193 return;
2194
2195 unevaluatedExpression = xstrdup(expression);
2196
2197 varState = cbdataReference (aVar);
2198
2199 evaluate();
2200}
2201
2202esiWhen::~esiWhen()
2203{
2204 safe_free (unevaluatedExpression);
2205
2206 if (varState)
2207 cbdataReferenceDone (varState);
2208}
2209
2210void
2211esiWhen::evaluate()
2212{
2213 if (!unevaluatedExpression)
2214 return;
2215
a5488e5d 2216 assert(varState);
43ae1d95 2217
2218 varState->feedData(unevaluatedExpression, strlen (unevaluatedExpression));
2219
2220 char const *expression = varState->extractChar ();
2221
924f73bc 2222 setTestResult(ESIExpression::Evaluate (expression));
43ae1d95 2223
2224 safe_free (expression);
2225}
2226
a5488e5d 2227esiWhen::esiWhen(esiWhen const &old) :
f53969cc
SM
2228 esiSequence(old),
2229 testValue(false),
2230 unevaluatedExpression(NULL),
2231 varState(NULL)
43ae1d95 2232{
43ae1d95 2233 if (old.unevaluatedExpression)
2234 unevaluatedExpression = xstrdup(old.unevaluatedExpression);
43ae1d95 2235}
2236
2237ESIElement::Pointer
2238esiWhen::makeCacheable() const
2239{
2240 return new esiWhen(*this);
2241}
2242
2243ESIElement::Pointer
924f73bc 2244esiWhen::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
43ae1d95 2245{
2246 esiWhen *resultW = new esiWhen (*this);
2247 ESIElement::Pointer result = resultW;
2248 resultW->parent = newParent;
2249 resultW->makeUsableElements(*this, newVarState);
2250 resultW->varState = cbdataReference (&newVarState);
2251 resultW->evaluate();
2252 return result;
2253}
2254
43ae1d95 2255/* TODO: implement surrogate targeting and control processing */
2256int
2257esiEnableProcessing (HttpReply *rep)
2258{
2259 int rv = 0;
2260
3a5e0b03 2261 if (rep->surrogate_control) {
45a58345 2262 HttpHdrScTarget *sctusable =
633b9845 2263 rep->surrogate_control->getMergedTarget(Config.Accel.surrogate_id);
43ae1d95 2264
bf574df8
AJ
2265 // found something targeted at us
2266 if (sctusable &&
2267 sctusable->hasContent() &&
2268 sctusable->content().pos("ESI/1.0")) {
43ae1d95 2269 rv = 1;
bf574df8 2270 }
43ae1d95 2271
45a58345 2272 delete sctusable;
43ae1d95 2273 }
2274
2275 return rv;
2276}
2277
454e8283 2278#endif /* USE_SQUID_ESI == 1 */
16c6c6da 2279