2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 86 ESI processing */
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.
16 #if (USE_SQUID_ESI == 1)
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"
28 #include "esi/Except.h"
29 #include "esi/Expression.h"
30 #include "esi/Segment.h"
31 #include "esi/VarState.h"
32 #include "FadingCounter.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"
42 #include "SquidConfig.h"
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
53 class ESIStreamContext
;
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.
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.
73 * NOT TODO: esi:inline - out of scope.
76 /* make comparisons with refcount pointers easy */
78 operator == (ESIElement
const *lhs
, ESIElement::Pointer
const &rhs
)
80 return lhs
== rhs
.getRaw();
83 typedef ESIContext::esiKick_t esiKick_t
;
85 /* some core operators */
87 class esiComment
: public ESIElement
89 MEMPROXY_CLASS(esiComment
);
92 ~esiComment() override
;
94 Pointer
makeCacheable() const override
;
95 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const override
;
97 void render(ESISegment::Pointer
) override
;
98 void finish() override
;
101 #include "esi/Literal.h"
103 #include "esi/Sequence.h"
105 #include "esi/Include.h"
109 class esiRemove
: public ESIElement
111 MEMPROXY_CLASS(esiRemove
);
114 esiRemove() : ESIElement() {}
115 ~esiRemove() override
{}
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
{}
124 class esiTry
: public ESIElement
126 MEMPROXY_CLASS(esiTry
);
129 esiTry(esiTreeParentPtr aParent
);
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
;
140 ESIElement::Pointer attempt
;
141 ESIElement::Pointer except
;
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 */
149 void finish() override
;
153 esiTreeParentPtr parent
;
154 ESISegment::Pointer exceptbuffer
;
155 esiTry (esiTry
const &);
156 esiProcessResult_t
bestAttemptRV() const;
161 class esiChoose
: public ESIElement
163 MEMPROXY_CLASS(esiChoose
);
166 esiChoose(esiTreeParentPtr
);
167 ~esiChoose() override
;
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
;
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
;
181 Esi::Elements elements
;
183 ESIElement::Pointer otherwise
;
184 void finish() override
;
187 esiChoose(esiChoose
const &);
188 esiTreeParentPtr parent
;
189 void checkValidSource (ESIElement::Pointer source
) const;
190 void selectElement();
193 class esiWhen
: public esiSequence
195 MEMPROXY_CLASS(esiWhen
);
198 esiWhen(esiTreeParentPtr aParent
, int attributes
, const char **attr
, ESIVarState
*);
200 Pointer
makeCacheable() const override
;
201 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const override
;
203 bool testsTrue() const { return testValue
;}
205 void setTestResult(bool aBool
) {testValue
= aBool
;}
208 esiWhen (esiWhen
const &);
210 char const *unevaluatedExpression
;
211 ESIVarState
*varState
;
215 struct esiOtherwise
: public esiSequence
{
216 esiOtherwise(esiTreeParentPtr aParent
) : esiSequence (aParent
) {}
219 CBDATA_CLASS_INIT(ESIContext
);
221 void ESIContext::startRead()
227 void ESIContext::finishRead()
233 bool ESIContext::reading() const
238 ESIStreamContext::ESIStreamContext() : finished(false), include (nullptr), localbuffer (new ESISegment
), buffer (nullptr)
241 /* Local functions */
243 static ESIContext
*ESIContextNew(HttpReply
*, clientStreamNode
*, ClientHttpRequest
*);
246 ESIContext::setError()
249 errorstatus
= Http::scInternalServerError
;
254 ESIContext::appendOutboundData(ESISegment::Pointer theData
)
256 if (!outbound
.getRaw()) {
258 outboundtail
= outbound
;
260 assert (outboundtail
->next
.getRaw() == nullptr);
261 outboundtail
->next
= theData
;
265 debugs(86, 9, "ESIContext::appendOutboundData: outbound " << outbound
.getRaw());
269 ESIContext::provideData (ESISegment::Pointer theData
, ESIElement
* source
)
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
);
283 ESIContext::fail(ESIElement
*, char const *anError
)
286 setErrorMessage (anError
);
292 ESIContext::fixupOutboundTail()
294 /* TODO: fixup thisNode outboundtail dross a little */
296 if (outboundtail
.getRaw())
297 outboundtail
= outboundtail
->tail();
304 debugs(86, 5, "esiKick: Re-entered whilst in progress");
305 // return ESI_KICK_INPROGRESS;
310 /* we've been detached from - we can't do anything more */
311 return ESI_KICK_FAILED
;
313 /* Something has occurred. Process any remaining nodes */
315 /* Process some of our data */
316 switch (process ()) {
318 case ESI_PROCESS_COMPLETE
:
319 debugs(86, 5, "esiKick: esiProcess OK");
322 case ESI_PROCESS_PENDING_WONTFAIL
:
323 debugs(86, 5, "esiKick: esiProcess PENDING OK");
326 case ESI_PROCESS_PENDING_MAYFAIL
:
327 debugs(86, 5, "esiKick: esiProcess PENDING UNKNOWN");
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
341 return ESI_KICK_FAILED
;
344 /* Render if we can to get maximal sent data */
345 assert (tree
.getRaw() || flags
.error
);
347 if (!flags
.finished
&& !outbound
.getRaw()) {
348 outboundtail
= new ESISegment
;
349 outbound
= outboundtail
;
352 if (!flags
.error
&& !flags
.finished
)
353 tree
->render(outboundtail
);
358 /* Is there data to send? */
360 /* some data was sent. we're finished until the next read */
362 return ESI_KICK_SENT
;
366 /* nothing to send */
367 return flags
.error
? ESI_KICK_FAILED
: ESI_KICK_PENDING
;
370 /* request from downstream for more data
373 esiStreamRead (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
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);
383 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
384 assert (context
.getRaw() != nullptr);
386 if (context
->flags
.passthrough
) {
387 /* passthru mode - read into supplied buffers */
388 next
= thisNode
->next();
389 clientStreamRead (thisNode
, http
, next
->readBuffer
);
393 context
->flags
.clientwantsdata
= 1;
394 debugs(86, 5, "esiStreamRead: Client now wants data");
396 /* Ok, not passing through */
398 switch (context
->kick ()) {
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
405 case ESIContext::ESI_KICK_SENT
:
407 case ESIContext::ESI_KICK_INPROGRESS
:
410 case ESIContext::ESI_KICK_PENDING
:
414 /* Nothing to send */
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
423 debugs(86, 5, "esiStreamRead: Waiting for async resume of esi processing");
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 */
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
);
443 if (context
->reading())
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
;
454 assert (context
->incoming
.getRaw() && context
->incoming
->len
!= HTTP_REQBUF_SZ
);
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
);
465 clientStream_status_t
466 esiStreamStatus (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
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);
475 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
476 assert (context
.getRaw() != nullptr);
478 if (context
->flags
.passthrough
)
479 return clientStreamStatus (thisNode
, http
);
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 */
487 /* ?? RC: we can't be aborted / fail ? */
492 esiAlwaysPassthrough(Http::StatusCode sline
)
498 case Http::scContinue
: /* Should never reach us... but squid needs to alter to accommodate this */
500 case Http::scSwitchingProtocols
: /* Ditto */
502 case Http::scProcessing
: /* Unknown - some extension */
504 case Http::scNoContent
: /* no body, no esi */
506 case Http::scNotModified
: /* ESI does not affect assembled page headers, so 304s are valid */
519 ESIContext::trimBlanks()
521 /* trim leading empty buffers ? */
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
;
529 if (outboundtail
.getRaw())
530 assert (outbound
.getRaw());
533 /* Send data downstream
534 * Returns 0 if nothing was sent. Non-zero if data was sent.
539 debugs(86, 5, "ESIContext::send: this=" << this);
540 /* send any processed data */
544 if (!flags
.clientwantsdata
) {
545 debugs(86, 5, "ESIContext::send: Client does not want data - not sending anything");
549 if (tree
.getRaw() && tree
->mayFail()) {
550 debugs(86, 5, "ESIContext::send: Tree may fail. Not sending.");
555 if (!(rep
|| (outbound
.getRaw() &&
556 outbound
->len
&& (outbound_offset
<= outbound
->len
)))) {
557 debugs(86, 5, "ESIContext::send: Nothing to send.");
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);
570 if (outbound
.getRaw())
571 len
= min (next
->readBuffer
.length
, outbound
->len
- outbound_offset
);
573 /* prevent corruption on range requests, even though we don't support them yet */
574 assert (pos
== next
->readBuffer
.offset
);
576 /* We must send data or a reply */
577 assert (len
!= 0 || rep
!= nullptr);
580 memcpy(next
->readBuffer
.data
, &outbound
->buf
[outbound_offset
], len
);
582 if (len
+ outbound_offset
== outbound
->len
) {
583 ESISegment::Pointer temp
= outbound
->next
;
584 /* remove the used buffer */
591 if (!outbound
.getRaw())
592 outboundtail
= nullptr;
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 */
603 if (temprep
&& varState
)
604 varState
->buildVary(temprep
.getRaw());
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
);
615 len
= 1; /* tell the caller we sent something (because we sent headers */
617 cbdataReferenceDone (templock
);
619 debugs (86,5,"ESIContext::send: this=" << this << " sent " << len
);
625 ESIContext::finishChildren()
633 /* Detach event from a client Stream */
635 esiStreamDetach (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
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;
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...
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
669 esiProcessStream (clientStreamNode
*thisNode
, ClientHttpRequest
*http
, HttpReply
*rep
, StoreIOBuffer receivedData
)
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
));
679 * if data is NULL thisNode is the first entrance. If rep is also NULL,
680 * something is wrong.
682 assert (thisNode
->data
.getRaw() != nullptr || rep
);
683 assert (thisNode
->node
.next
!= nullptr);
685 if (!thisNode
->data
.getRaw())
686 /* setup ESI context from reply headers */
687 thisNode
->data
= ESIContextNew(rep
, thisNode
, http
);
689 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
691 assert (context
.getRaw() != nullptr);
693 context
->finishRead();
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
699 if (context
->flags
.passthrough
) {
700 clientStreamCallback (thisNode
, http
, rep
, receivedData
);
704 debugs(86, 3, "esiProcessStream: Processing thisNode " << thisNode
<<
705 " context " << context
.getRaw() << " offset " <<
706 (int) receivedData
.offset
<< " length " <<
707 (unsigned int)receivedData
.length
);
709 /* once we finish the template, we *cannot* return here */
710 assert (!context
->flags
.finishedtemplate
);
711 assert (!context
->cachedASTInUse
);
713 /* Can we generate any data ?*/
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 */
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
;
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");
737 memcpy(&context
->incoming
->buf
[context
->incoming
->len
], receivedData
.data
, len
);
738 context
->incoming
->len
+= len
;
740 if (context
->incoming
->len
== HTTP_REQBUF_SZ
) {
741 /* append another buffer */
742 context
->incoming
->next
= new ESISegment
;
743 context
->incoming
= context
->incoming
->next
;
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
;
752 /* and note where we are up to */
753 context
->readpos
+= receivedData
.length
;
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
);
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
;
765 context
->readpos
+= receivedData
.length
;
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;
778 switch (context
->kick()) {
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
786 case ESIContext::ESI_KICK_SENT
:
788 case ESIContext::ESI_KICK_INPROGRESS
:
791 case ESIContext::ESI_KICK_PENDING
:
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
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
);
810 debugs(86, 3, "esiProcessStream: no data to send, no data to read, awaiting a callback");
813 ESIContext::~ESIContext()
816 /* Not freed by freeresources because esi::fail needs it */
817 safe_free (errormessage
);
818 debugs(86, 3, "ESIContext::~ESIContext: Freed " << this);
822 ESIContextNew (HttpReply
*rep
, clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
825 ESIContext
*rv
= new ESIContext
;
827 rv
->cbdataLocker
= rv
;
829 if (esiAlwaysPassthrough(rep
->sline
.status())) {
830 rv
->flags
.passthrough
= 1;
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
;
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");
847 debugs(86, 5, "ESIContextNew: Create context " << rv
);
851 ESIElement::ESIElementType_t
852 ESIElement::IdentifyElement (const char *el
)
858 return ESI_ELEMENT_NONE
;
860 if (!strncmp (el
, "esi:", 4))
862 else if (!strncmp (el
, "http://www.edge-delivery.org/esi/1.0|", 37))
865 return ESI_ELEMENT_NONE
;
867 if (!strncmp (el
+ offset
, "otherwise", 9))
868 return ESI_ELEMENT_OTHERWISE
;
870 if (!strncmp (el
+ offset
, "comment", 7))
871 return ESI_ELEMENT_COMMENT
;
873 if (!strncmp (el
+ offset
, "include", 7))
874 return ESI_ELEMENT_INCLUDE
;
876 if (!strncmp (el
+ offset
, "attempt", 7))
877 return ESI_ELEMENT_ATTEMPT
;
879 if (!strncmp (el
+ offset
, "assign", 6))
880 return ESI_ELEMENT_ASSIGN
;
882 if (!strncmp (el
+ offset
, "remove", 6))
883 return ESI_ELEMENT_REMOVE
;
885 if (!strncmp (el
+ offset
, "except", 6))
886 return ESI_ELEMENT_EXCEPT
;
888 if (!strncmp (el
+ offset
, "choose", 6))
889 return ESI_ELEMENT_CHOOSE
;
891 if (!strncmp (el
+ offset
, "vars", 4))
892 return ESI_ELEMENT_VARS
;
894 if (!strncmp (el
+ offset
, "when", 4))
895 return ESI_ELEMENT_WHEN
;
897 if (!strncmp (el
+ offset
, "try", 3))
898 return ESI_ELEMENT_TRY
;
900 return ESI_ELEMENT_NONE
;
904 ESIContext::ParserState::top()
906 return stack
[stackdepth
-1];
909 ESIContext::ParserState::ParserState() :
916 ESIContext::ParserState::inited() const
922 ESIContext::addStackElement (ESIElement::Pointer element
)
924 /* Put on the stack to allow skipping of 'invalid' markup */
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");
933 debugs(86, 5, "ESIContext::addStackElement: About to add ESI Node " << element
.getRaw());
935 if (!parserState
.top()->addElement(element
)) {
936 throw Esi::Error("ESIContext::addStackElement failed, probable error in ESI template");
938 /* added ok, push onto the stack */
939 parserState
.stack
[parserState
.stackdepth
] = element
;
940 ++parserState
.stackdepth
;
945 ESIContext::start(const char *el
, const char **attr
, size_t attrCount
)
948 unsigned int ellen
= strlen (el
);
949 char localbuf
[HTTP_REQBUF_SZ
];
950 ESIElement::Pointer element
;
951 int specifiedattcount
= attrCount
* 2;
953 Must(ellen
< sizeof(localbuf
)); /* prevent unexpected overruns. */
955 debugs(86, 5, "ESIContext::Start: element '" << el
<< "' with " << specifiedattcount
<< " tags");
958 /* waiting for expat to finish the buffer we gave it */
961 switch (ESIElement::IdentifyElement (el
)) {
963 case ESIElement::ESI_ELEMENT_NONE
:
964 /* Spit out elements we aren't interested in */
967 xstrncpy(&localbuf
[1], el
, sizeof(localbuf
) - 2);
968 position
= localbuf
+ strlen (localbuf
);
970 for (i
= 0; i
< specifiedattcount
&& attr
[i
]; i
+= 2) {
971 Must(static_cast<size_t>(position
- localbuf
) < sizeof(localbuf
) - 1);
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);
982 const char *chPtr
= attr
[i
+ 1];
984 while ((ch
= *chPtr
++) != '\0') {
986 Must(static_cast<size_t>(position
- localbuf
) < sizeof(localbuf
) - 6);
987 xstrncpy(position
, """, sizeof(localbuf
) - (position
-localbuf
));
990 Must(static_cast<size_t>(position
- localbuf
) < sizeof(localbuf
) - 1);
995 Must(static_cast<size_t>(position
- localbuf
) < sizeof(localbuf
) - 1);
1000 Must(static_cast<size_t>(position
- localbuf
) < sizeof(localbuf
) - 2);
1005 addLiteral (localbuf
, position
- localbuf
);
1006 debugs(86, 5, "esi stack depth " << parserState
.stackdepth
);
1010 case ESIElement::ESI_ELEMENT_COMMENT
:
1011 /* Put on the stack to allow skipping of 'invalid' markup */
1012 element
= new esiComment ();
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);
1020 case ESIElement::ESI_ELEMENT_REMOVE
:
1021 /* Put on the stack to allow skipping of 'invalid' markup */
1022 element
= new esiRemove();
1025 case ESIElement::ESI_ELEMENT_TRY
:
1026 /* Put on the stack to allow skipping of 'invalid' markup */
1027 element
= new esiTry (parserState
.top().getRaw());
1030 case ESIElement::ESI_ELEMENT_ATTEMPT
:
1031 /* Put on the stack to allow skipping of 'invalid' markup */
1032 element
= new esiAttempt (parserState
.top().getRaw());
1035 case ESIElement::ESI_ELEMENT_EXCEPT
:
1036 /* Put on the stack to allow skipping of 'invalid' markup */
1037 element
= new esiExcept (parserState
.top().getRaw());
1040 case ESIElement::ESI_ELEMENT_VARS
:
1041 /* Put on the stack to allow skipping of 'invalid' markup */
1042 element
= new ESIVar (parserState
.top().getRaw());
1045 case ESIElement::ESI_ELEMENT_CHOOSE
:
1046 /* Put on the stack to allow skipping of 'invalid' markup */
1047 element
= new esiChoose (parserState
.top().getRaw());
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
);
1055 case ESIElement::ESI_ELEMENT_OTHERWISE
:
1056 /* Put on the stack to allow skipping of 'invalid' markup */
1057 element
= new esiOtherwise (parserState
.top().getRaw());
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);
1066 addStackElement(element
);
1068 debugs(86, 5, "esi stack depth " << parserState
.stackdepth
);
1070 } /* End of start handler */
1073 ESIContext::end(const char *el
)
1075 unsigned int ellen
= strlen (el
);
1076 char localbuf
[HTTP_REQBUF_SZ
];
1080 /* waiting for expat to finish the buffer we gave it */
1083 switch (ESIElement::IdentifyElement (el
)) {
1085 case ESIElement::ESI_ELEMENT_NONE
:
1086 Must(ellen
< sizeof(localbuf
) - 3); /* prevent unexpected overruns. */
1087 /* Add elements we aren't interested in */
1090 xstrncpy(&localbuf
[2], el
, sizeof(localbuf
) - 3);
1091 position
= localbuf
+ strlen (localbuf
);
1095 addLiteral (localbuf
, position
- localbuf
);
1098 case ESIElement::ESI_ELEMENT_COMMENT
:
1100 case ESIElement::ESI_ELEMENT_INCLUDE
:
1102 case ESIElement::ESI_ELEMENT_REMOVE
:
1104 case ESIElement::ESI_ELEMENT_TRY
:
1106 case ESIElement::ESI_ELEMENT_ATTEMPT
:
1108 case ESIElement::ESI_ELEMENT_EXCEPT
:
1110 case ESIElement::ESI_ELEMENT_VARS
:
1112 case ESIElement::ESI_ELEMENT_CHOOSE
:
1114 case ESIElement::ESI_ELEMENT_WHEN
:
1116 case ESIElement::ESI_ELEMENT_OTHERWISE
:
1118 case ESIElement::ESI_ELEMENT_ASSIGN
:
1119 /* pop of the stack */
1120 parserState
.stack
[--parserState
.stackdepth
] = nullptr;
1123 } /* End of end handler */
1126 ESIContext::parserDefault (const char *s
, int len
)
1131 /* handle any skipped data */
1132 addLiteral (s
, len
);
1136 ESIContext::parserComment (const char *s
)
1141 if (!strncmp(s
, "esi",3)) {
1142 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block encountered");
1143 ESIParser::Pointer tempParser
= ESIParser::NewParser (this);
1145 /* wrap the comment in some tags */
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.");
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
<< "");
1158 setErrorMessage(tempstr
);
1161 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block parsed");
1164 char localbuf
[HTTP_REQBUF_SZ
];
1166 debugs(86, 5, "ESIContext::parserComment: Regenerating comment block");
1169 if (len
> sizeof (localbuf
) - 9) {
1170 debugs(86, DBG_CRITICAL
, "ESIContext::parserComment: Truncating long comment");
1171 len
= sizeof (localbuf
) - 9;
1174 xstrncpy(localbuf
, "<!--", 5);
1175 xstrncpy(localbuf
+ 4, s
, len
+ 1);
1176 xstrncpy(localbuf
+ 4 + len
, "-->", 4);
1177 addLiteral (localbuf
,len
+ 7);
1182 ESIContext::addLiteral (const char *s
, int len
)
1184 /* handle any skipped data */
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
));
1190 if (!parserState
.top()->addElement(element
))
1191 throw Esi::Error("ESIContext::addLiteral failed, probable error in ESI template");
1195 ESIContext::ParserState::init(ESIParserClient
*userData
)
1197 theParser
= ESIParser::NewParser (userData
);
1202 ESIContext::parseOneBuffer()
1204 assert (buffered
.getRaw());
1206 debugs (86,9,"ESIContext::parseOneBuffer: " << buffered
->len
<< " bytes");
1207 bool lastBlock
= buffered
->next
.getRaw() == nullptr && flags
.finishedtemplate
? true : false;
1209 if (! parserState
.theParser
->parse(buffered
->buf
, buffered
->len
, lastBlock
)) {
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
<< "");
1217 setErrorMessage(tempstr
);
1219 assert (flags
.error
);
1229 ESISegment::Pointer temp
= buffered
;
1230 buffered
= temp
->next
;
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
;
1243 if (rep
&& !parserState
.inited())
1244 parserState
.init(this);
1247 if (buffered
.getRaw()) {
1248 parserState
.parsing
= 1;
1249 /* we don't keep any data around */
1252 while (buffered
.getRaw() && !flags
.error
)
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
);
1259 setErrorMessage(errMsg
.c_str());
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
);
1267 setErrorMessage("ESI parser error");
1270 /* Tel the read code to allocate a new buffer */
1273 parserState
.parsing
= 0;
1278 ESIContext::process ()
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
1287 if (parserState
.parsing
) {
1288 /* in middle of parsing - finish here */
1289 return ESI_PROCESS_PENDING_MAYFAIL
;
1292 assert (flags
.finished
== 0);
1294 assert (!flags
.error
);
1296 if (!hasCachedAST())
1298 else if (!flags
.finishedtemplate
)
1302 debugs(86, 5, "ESIContext::process: Parsing failed");
1304 parserState
.popAll();
1305 return ESI_PROCESS_FAILED
;
1308 if (!flags
.finishedtemplate
&& !incoming
.getRaw() && !cachedASTInUse
) {
1309 buffered
= new ESISegment
;
1310 incoming
= buffered
;
1313 if (!flags
.finishedtemplate
&& !cachedASTInUse
) {
1314 return ESI_PROCESS_PENDING_MAYFAIL
;
1317 assert (flags
.finishedtemplate
|| cachedASTInUse
);
1319 /* ok, we've done all we can with the data. What can we process now?
1322 esiProcessResult_t status
;
1324 status
= tree
->process(0);
1329 case ESI_PROCESS_COMPLETE
:
1330 debugs(86, 5, "esiProcess: tree Processed OK");
1333 case ESI_PROCESS_PENDING_WONTFAIL
:
1334 debugs(86, 5, "esiProcess: tree Processed PENDING OK");
1337 case ESI_PROCESS_PENDING_MAYFAIL
:
1338 debugs(86, 5, "esiProcess: tree Processed PENDING UNKNOWN");
1341 case ESI_PROCESS_FAILED
:
1342 debugs(86, DBG_CRITICAL
, "ERROR: esiProcess: tree Processed FAILED");
1345 setErrorMessage("esiProcess: ESI template Processing failed.");
1346 return ESI_PROCESS_FAILED
;
1351 if (status
!= ESI_PROCESS_PENDING_MAYFAIL
&& (flags
.finishedtemplate
|| cachedASTInUse
)) {
1352 /* We've read the entire template, and no nodes will
1355 debugs(86, 5, "esiProcess, request will succeed");
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");
1366 return status
; /* because we have no callbacks */
1371 ESIContext::ParserState::freeResources()
1373 theParser
= nullptr;
1378 ESIContext::ParserState::popAll()
1381 stack
[--stackdepth
] = nullptr;
1385 ESIContext::freeResources ()
1387 debugs(86, 5, "Freeing for this=" << this);
1389 rep
= nullptr; // refcounted
1393 if (parserState
.inited()) {
1394 parserState
.freeResources();
1397 parserState
.popAll();
1398 ESISegmentFreeList (buffered
);
1399 ESISegmentFreeList (outbound
);
1400 ESISegmentFreeList (outboundtail
);
1403 /* don't touch incoming, it's a pointer into buffered anyway */
1406 ErrorState
*clientBuildError(err_type
, Http::StatusCode
, char const *, const ConnStateData
*, HttpRequest
*, const AccessLogEntryPointer
&);
1408 /* This can ONLY be used before we have sent *any* data to the client */
1412 debugs(86, 5, "ESIContext::fail: this=" << this);
1413 /* check preconditions */
1415 /* cleanup current state */
1417 /* Stop altering thisNode request */
1420 /* don't honour range requests - for errors we send it all */
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 */
1435 while (errorprogress
< (size_t)rep
->body
.contentSize()) {
1436 appendOutboundData(new ESISegment
);
1437 errorprogress
+= outboundtail
->append(rep
->body
.content() + errorprogress
, rep
->body
.contentSize() - errorprogress
);
1440 /* the esiCode now thinks that the error is the outbound,
1441 * and all processing has finished. */
1442 /* Send as much as we can */
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
1451 /* Implementation of ESIElements */
1454 esiComment::~esiComment()
1456 debugs(86, 5, "esiComment::~esiComment " << this);
1459 esiComment::esiComment()
1463 esiComment::finish()
1467 esiComment::render(ESISegment::Pointer output
)
1469 /* Comments do nothing dude */
1470 debugs(86, 5, "esiCommentRender: Rendering comment " << this << " into " << output
.getRaw());
1474 esiComment::makeCacheable() const
1476 debugs(86, 5, "esiComment::makeCacheable: returning NULL");
1481 esiComment::makeUsable(esiTreeParentPtr
, ESIVarState
&) const
1483 fatal ("esiComment::Usable: unreachable code!\n");
1488 esiLiteral::~esiLiteral()
1490 debugs(86, 5, "esiLiteral::~esiLiteral: " << this);
1491 ESISegmentFreeList (buffer
);
1492 cbdataReferenceDone (varState
);
1495 esiLiteral::esiLiteral(ESISegment::Pointer aSegment
) :
1504 esiLiteral::finish()
1507 /* precondition: the buffer chain has at least start + length bytes of data
1509 esiLiteral::esiLiteral(ESIContext
*context
, const char *s
, int numberOfCharacters
)
1513 buffer
= new ESISegment
;
1514 ESISegment::Pointer local
= buffer
;
1516 int remainingCharacters
= numberOfCharacters
;
1518 while (remainingCharacters
> 0) {
1519 if (local
->len
== sizeof (local
->buf
)) {
1520 local
->next
= new ESISegment
;
1524 size_t len
= local
->append (&s
[start
], remainingCharacters
);
1526 remainingCharacters
-= len
;
1529 varState
= cbdataReference(context
->varState
);
1533 esiLiteral::render (ESISegment::Pointer output
)
1535 debugs(86, 9, "esiLiteral::render: Rendering " << this);
1536 /* append the entire chain */
1537 assert (output
->next
.getRaw() == nullptr);
1538 output
->next
= buffer
;
1543 esiLiteral::process (int dovars
)
1546 return ESI_PROCESS_COMPLETE
;
1549 ESISegment::Pointer temp
= buffer
;
1550 /* Ensure variable state is clean */
1552 while (temp
.getRaw()) {
1553 varState
->feedData(temp
->buf
,temp
->len
);
1557 /* free the pre-processed content */
1558 ESISegmentFreeList (buffer
);
1560 buffer
= varState
->extractList ();
1564 return ESI_PROCESS_COMPLETE
;
1567 esiLiteral::esiLiteral(esiLiteral
const &old
) : buffer (old
.buffer
->cloneList()),
1574 esiLiteral::makeCacheable() const
1576 return new esiLiteral (*this);
1580 esiLiteral::makeUsable(esiTreeParentPtr
, ESIVarState
&newVarState
) const
1582 debugs(86, 5, "esiLiteral::makeUsable: Creating usable literal");
1583 esiLiteral
* result
= new esiLiteral (*this);
1584 result
->varState
= cbdataReference (&newVarState
);
1590 esiRemove::render(ESISegment::Pointer
)
1592 /* Removes do nothing dude */
1593 debugs(86, 5, "esiRemoveRender: Rendering remove " << this);
1596 /* Accept non-ESI children */
1598 esiRemove::addElement (ESIElement::Pointer element
)
1600 if (!dynamic_cast<esiLiteral
*>(element
.getRaw())) {
1601 debugs(86, 5, "esiRemoveAdd: Failed for " << this);
1609 esiRemove::makeCacheable() const
1611 debugs(86, 5, "esiRemove::makeCacheable: Returning NULL");
1616 esiRemove::makeUsable(esiTreeParentPtr
, ESIVarState
&) const
1618 fatal ("esiRemove::Usable: unreachable code!\n");
1625 debugs(86, 5, "esiTry::~esiTry " << this);
1628 esiTry::esiTry(esiTreeParentPtr aParent
) :
1630 exceptbuffer(nullptr)
1632 memset(&flags
, 0, sizeof(flags
));
1636 esiTry::render(ESISegment::Pointer output
)
1638 /* Try renders from it's children */
1639 assert (attempt
.getRaw());
1640 assert (except
.getRaw());
1641 debugs(86, 5, "esiTryRender: Rendering Try " << this);
1643 if (flags
.attemptok
) {
1644 attempt
->render(output
);
1645 } else if (flags
.exceptok
) {
1648 if (exceptbuffer
.getRaw())
1649 ESISegment::ListTransfer(exceptbuffer
, output
);
1651 except
->render(output
);
1653 debugs(86, 5, "esiTryRender: Neither except nor attempt succeeded?!?");
1656 /* Accept attempt and except only */
1658 esiTry::addElement(ESIElement::Pointer element
)
1660 debugs(86, 5, "esiTryAdd: Try " << this << " adding element " <<
1663 if (dynamic_cast<esiLiteral
*>(element
.getRaw())) {
1664 /* Swallow whitespace */
1665 debugs(86, 5, "esiTryAdd: Try " << this << " skipping whitespace " << element
.getRaw());
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)");
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)");
1689 debugs(86, DBG_IMPORTANT
, "ERROR: esiTryAdd: Failed to add element " << element
.getRaw() << " to try " << this << ", incorrect element type (see section 3.4)");
1694 esiTry::bestAttemptRV() const
1696 if (flags
.attemptfailed
)
1697 return ESI_PROCESS_COMPLETE
;
1699 return ESI_PROCESS_PENDING_MAYFAIL
;
1703 esiTry::process (int dovars
)
1705 esiProcessResult_t rv
= ESI_PROCESS_PENDING_MAYFAIL
;
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
;
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
;
1717 if (!flags
.attemptfailed
)
1718 /* Try the attempt branch */
1719 switch ((rv
= attempt
->process(dovars
))) {
1721 case ESI_PROCESS_COMPLETE
:
1722 debugs(86, 5, "esiTryProcess: attempt Processed OK");
1723 flags
.attemptok
= 1;
1724 return ESI_PROCESS_COMPLETE
;
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
;
1731 case ESI_PROCESS_PENDING_MAYFAIL
:
1732 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
1735 case ESI_PROCESS_FAILED
:
1736 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
1737 flags
.attemptfailed
= 1;
1741 /* attempt is either MAYFAIL or FAILED */
1743 return bestAttemptRV();
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
)) {
1750 case ESI_PROCESS_COMPLETE
:
1751 debugs(86, 5, "esiTryProcess: except Processed OK");
1753 return bestAttemptRV();
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
;
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
;
1765 case ESI_PROCESS_FAILED
:
1766 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
1767 flags
.exceptfailed
= 1;
1771 if (flags
.exceptfailed
&& flags
.attemptfailed
)
1772 return ESI_PROCESS_FAILED
;
1774 /* one of attempt or except returned PENDING MAYFAIL */
1775 return ESI_PROCESS_PENDING_MAYFAIL
;
1779 esiTry::notifyParent()
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");
1790 /* nothing to do when except fails and attempt hasn't */
1794 esiTry::fail(ESIElement
*source
, char const *anError
)
1797 assert (source
== attempt
|| source
== except
);
1798 debugs(86, 5, "esiTry::fail: this=" << this << ", source=" << source
<< ", message=" << anError
);
1800 if (source
== except
) {
1801 flags
.exceptfailed
= 1;
1803 flags
.attemptfailed
= 1;
1810 esiTry::provideData (ESISegment::Pointer data
, ESIElement
* source
)
1812 if (source
== attempt
) {
1813 flags
.attemptok
= 1;
1814 parent
->provideData (data
, this);
1815 } else if (source
== except
) {
1817 assert (exceptbuffer
== nullptr);
1818 ESISegment::ListTransfer (data
, exceptbuffer
);
1823 esiTry::esiTry(esiTry
const &)
1827 flags
.attemptok
= 0;
1829 flags
.attemptfailed
= 0;
1830 flags
.exceptfailed
= 0;
1832 exceptbuffer
= nullptr;
1836 esiTry::makeCacheable() const
1838 debugs(86, 5, "esiTry::makeCacheable: making cachable Try from " << this);
1839 esiTry
*resultT
= new esiTry (*this);
1840 ESIElement::Pointer result
= resultT
;
1842 if (attempt
.getRaw())
1843 resultT
->attempt
= attempt
->makeCacheable();
1845 if (except
.getRaw())
1846 resultT
->except
= except
->makeCacheable();
1852 esiTry::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
1854 debugs(86, 5, "esiTry::makeUsable: making usable Try from " << this);
1855 esiTry
*resultT
= new esiTry (*this);
1856 ESIElement::Pointer result
= resultT
;
1858 resultT
->parent
= newParent
;
1860 if (attempt
.getRaw())
1861 resultT
->attempt
= attempt
->makeUsable(resultT
, newVarState
);
1863 if (except
.getRaw())
1864 resultT
->except
= except
->makeUsable(resultT
, newVarState
);
1874 if (attempt
.getRaw())
1879 if (except
.getRaw())
1886 esiChoose::~esiChoose()
1888 debugs(86, 5, "esiChoose::~esiChoose " << this);
1889 FinishAllElements(elements
); // finish if not already done
1892 esiChoose::esiChoose(esiTreeParentPtr aParent
) :
1899 esiChoose::render(ESISegment::Pointer output
)
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");
1906 if (chosenelement
>= 0)
1907 elements
[chosenelement
]->render(output
);
1908 else if (otherwise
.getRaw())
1909 otherwise
->render(output
);
1913 esiChoose::addElement(ESIElement::Pointer element
)
1915 /* add an element to the output list */
1917 if (dynamic_cast<esiLiteral
*>(element
.getRaw())) {
1918 /* Swallow whitespace */
1919 debugs(86, 5, "esiChooseAdd: Choose " << this << " skipping whitespace " << element
.getRaw());
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)");
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)");
1935 otherwise
= element
;
1937 elements
.push_back (element
);
1939 debugs (86,3, "esiChooseAdd: Added a new element, elements = " << elements
.size());
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());
1954 esiChoose::selectElement()
1956 if (chosenelement
> -1)
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);
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
1973 FinishAnElement(ESIElement::Pointer
&element
, int pos
)
1978 debugs(86, 5, "setting index " << pos
<< ", pointer " << (void*)element
.getRaw() << " to nil");
1983 FinishAllElements(Esi::Elements
&elements
)
1986 for (auto &element
: elements
)
1987 FinishAnElement(element
, pos
++);
1993 FinishAllElements(elements
);
1995 if (otherwise
.getRaw())
1996 otherwise
->finish();
1998 otherwise
= nullptr;
2003 esiChoose::NULLUnChosen()
2005 if (chosenelement
>= 0) {
2006 if (otherwise
.getRaw())
2007 otherwise
->finish();
2009 otherwise
= nullptr;
2012 for (auto &element
: elements
) {
2013 if (pos
!= chosenelement
)
2014 FinishAnElement(element
, pos
++);
2017 } else if (otherwise
.getRaw()) {
2018 FinishAllElements(elements
);
2023 esiChoose::process (int dovars
)
2025 /* process as much of the list as we can, stopping only on
2028 /* We MUST have a when clause */
2031 if (!elements
.size()) {
2034 if (otherwise
.getRaw())
2035 otherwise
->finish();
2037 otherwise
= nullptr;
2041 return ESI_PROCESS_FAILED
;
2044 if (chosenelement
>= 0) {
2045 return elements
[chosenelement
]->process(dovars
);
2046 } else if (otherwise
.getRaw())
2047 return otherwise
->process(dovars
);
2049 return ESI_PROCESS_COMPLETE
;
2053 esiChoose::checkValidSource (ESIElement::Pointer source
) const
2055 if (!elements
.size())
2056 fatal ("invalid callback = no when clause\n");
2058 if (chosenelement
>= 0)
2059 assert (source
== elements
[chosenelement
]);
2060 else if (otherwise
.getRaw())
2061 assert (source
== otherwise
);
2063 fatal ("esiChoose::checkValidSource: invalid callback - no elements chosen\n");
2067 esiChoose::fail(ESIElement
* source
, char const *anError
)
2069 checkValidSource (source
);
2070 FinishAllElements(elements
);
2072 if (otherwise
.getRaw())
2073 otherwise
->finish();
2075 otherwise
= nullptr;
2077 parent
->fail(this, anError
);
2083 esiChoose::provideData (ESISegment::Pointer data
, ESIElement
*source
)
2085 checkValidSource (source
);
2086 parent
->provideData (data
, this);
2089 esiChoose::esiChoose(esiChoose
const &old
) : chosenelement(-1), otherwise (nullptr), parent (nullptr)
2091 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2092 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeCacheable();
2094 if (newElement
.getRaw())
2095 assert (addElement(newElement
));
2100 esiChoose::makeCachableElements(esiChoose
const &old
)
2102 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2103 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeCacheable();
2105 if (newElement
.getRaw())
2106 assert (addElement(newElement
));
2111 esiChoose::makeUsableElements(esiChoose
const &old
, ESIVarState
&newVarState
)
2113 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2114 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeUsable (this, newVarState
);
2116 if (newElement
.getRaw())
2117 assert (addElement(newElement
));
2122 esiChoose::makeCacheable() const
2124 esiChoose
*resultC
= new esiChoose (*this);
2125 ESIElement::Pointer result
= resultC
;
2126 resultC
->makeCachableElements(*this);
2128 if (otherwise
.getRaw())
2129 resultC
->otherwise
= otherwise
->makeCacheable();
2135 esiChoose::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
2137 esiChoose
*resultC
= new esiChoose (*this);
2138 ESIElement::Pointer result
= resultC
;
2139 resultC
->parent
= newParent
;
2140 resultC
->makeUsableElements(*this, newVarState
);
2141 resultC
->selectElement();
2143 if (otherwise
.getRaw())
2144 resultC
->otherwise
= otherwise
->makeUsable(resultC
, newVarState
);
2150 esiWhen::esiWhen(esiTreeParentPtr aParent
, int attrcount
, const char **attr
,ESIVarState
*aVar
) :
2151 esiSequence(aParent
),
2153 unevaluatedExpression(nullptr),
2156 char const *expression
= nullptr;
2158 for (int loopCounter
= 0; loopCounter
< attrcount
&& attr
[loopCounter
]; loopCounter
+= 2) {
2159 if (!strcmp(attr
[loopCounter
],"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];
2166 /* ignore mistyped attributes.
2167 * TODO:? error on these for user feedback - config parameter needed
2169 debugs(86, DBG_IMPORTANT
, "Found misttyped attribute on ESI When clause");
2173 /* No expression ? default is not matching */
2177 unevaluatedExpression
= xstrdup(expression
);
2179 varState
= cbdataReference (aVar
);
2186 safe_free (unevaluatedExpression
);
2188 cbdataReferenceDone(varState
);
2194 if (!unevaluatedExpression
)
2199 varState
->feedData(unevaluatedExpression
, strlen (unevaluatedExpression
));
2201 char const *expression
= varState
->extractChar ();
2203 setTestResult(ESIExpression::Evaluate (expression
));
2205 safe_free (expression
);
2208 esiWhen::esiWhen(esiWhen
const &old
) :
2211 unevaluatedExpression(nullptr),
2214 if (old
.unevaluatedExpression
)
2215 unevaluatedExpression
= xstrdup(old
.unevaluatedExpression
);
2219 esiWhen::makeCacheable() const
2221 return new esiWhen(*this);
2225 esiWhen::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
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();
2236 /* TODO: implement surrogate targeting and control processing */
2238 esiEnableProcessing (HttpReply
*rep
)
2242 if (rep
->surrogate_control
) {
2243 HttpHdrScTarget
*sctusable
=
2244 rep
->surrogate_control
->getMergedTarget(Config
.Accel
.surrogate_id
);
2246 // found something targeted at us
2248 sctusable
->hasContent() &&
2249 sctusable
->content().pos("ESI/1.0")) {
2259 #endif /* USE_SQUID_ESI == 1 */