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