2 * Copyright (C) 1996-2017 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"
33 #include "http/Stream.h"
34 #include "HttpHdrSc.h"
35 #include "HttpHdrScTarget.h"
36 #include "HttpReply.h"
37 #include "HttpRequest.h"
38 #include "ip/Address.h"
40 #include "profiler/Profiler.h"
41 #include "SquidConfig.h"
43 /* quick reference on behaviour here.
44 * The ESI specification 1.0 requires the ESI processor to be able to
45 * return an error code at any point in the processing. To that end
46 * we buffer the incoming esi body until we know we will be able to
47 * satisfy the request. At that point we start streaming the queued
52 class ESIStreamContext
;
54 /* TODO: split this out into separate files ? */
55 /* Parsing: quick and dirty. ESI files are not valid XML, so a generic
56 * XML parser is not much use. Also we need a push parser not a pull
57 * parser, so LibXML is out.
59 * Interpreter methods:
60 * Render: May only ever be called after Process returns PROCESS_COMPLETE.
61 * Renders the resulting content into a ESISegment chain.
62 * Process: returns the status of the node.
63 * COMPLETE - processing is complete, rendering may staret
64 * PENDING_WONTFAIL - process is incomplete, but the element *will*
65 * be able to be rendered given time.
66 * PENDING_MAYFAIL - processing is incomplete, and the element *may*
67 * fail to be able to rendered.
68 * FAILED - processing failed, return an error to the client.
72 * NOT TODO: esi:inline - out of scope.
75 /* make comparisons with refcount pointers easy */
76 bool operator == (ESIElement
const *lhs
, ESIElement::Pointer
const &rhs
)
78 return lhs
== rhs
.getRaw();
81 typedef ESIContext::esiKick_t esiKick_t
;
83 /* some core operators */
85 class esiComment
: public ESIElement
87 MEMPROXY_CLASS(esiComment
);
92 Pointer
makeCacheable() const;
93 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
95 void render(ESISegment::Pointer
);
99 #include "esi/Literal.h"
101 #include "esi/Sequence.h"
103 #include "esi/Include.h"
107 class esiRemove
: public ESIElement
109 MEMPROXY_CLASS(esiRemove
);
112 esiRemove() : ESIElement() {}
113 virtual ~esiRemove() {}
115 virtual void render(ESISegment::Pointer
);
116 virtual bool addElement (ESIElement::Pointer
);
117 virtual Pointer
makeCacheable() const;
118 virtual Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
119 virtual void finish() {}
122 class esiTry
: public ESIElement
124 MEMPROXY_CLASS(esiTry
);
127 esiTry(esiTreeParentPtr aParent
);
130 void render(ESISegment::Pointer
);
131 bool addElement (ESIElement::Pointer
);
132 void fail(ESIElement
*, char const * = NULL
);
133 esiProcessResult_t
process (int dovars
);
134 void provideData (ESISegment::Pointer data
, ESIElement
* source
);
135 Pointer
makeCacheable() const;
136 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
138 ESIElement::Pointer attempt
;
139 ESIElement::Pointer except
;
142 int attemptok
:1; /* the attempt branch process correctly */
143 int exceptok
:1; /* likewise */
144 int attemptfailed
:1; /* The attempt branch failed */
145 int exceptfailed
:1; /* the except branch failed */
151 esiTreeParentPtr parent
;
152 ESISegment::Pointer exceptbuffer
;
153 esiTry (esiTry
const &);
154 esiProcessResult_t
bestAttemptRV() const;
159 class esiChoose
: public ESIElement
161 MEMPROXY_CLASS(esiChoose
);
164 esiChoose(esiTreeParentPtr
);
167 void render(ESISegment::Pointer
);
168 bool addElement (ESIElement::Pointer
);
169 void fail(ESIElement
*, char const * = NULL
);
170 esiProcessResult_t
process (int dovars
);
172 void provideData (ESISegment::Pointer data
, ESIElement
*source
);
173 void makeCachableElements(esiChoose
const &old
);
174 void makeUsableElements(esiChoose
const &old
, ESIVarState
&);
175 Pointer
makeCacheable() const;
176 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
179 ElementList elements
;
181 ESIElement::Pointer otherwise
;
185 esiChoose(esiChoose
const &);
186 esiTreeParentPtr parent
;
187 void checkValidSource (ESIElement::Pointer source
) const;
188 void selectElement();
191 class esiWhen
: public esiSequence
193 MEMPROXY_CLASS(esiWhen
);
196 esiWhen(esiTreeParentPtr aParent
, int attributes
, const char **attr
, ESIVarState
*);
198 Pointer
makeCacheable() const;
199 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
201 bool testsTrue() const { return testValue
;}
203 void setTestResult(bool aBool
) {testValue
= aBool
;}
206 esiWhen (esiWhen
const &);
208 char const *unevaluatedExpression
;
209 ESIVarState
*varState
;
213 struct esiOtherwise
: public esiSequence
{
214 esiOtherwise(esiTreeParentPtr aParent
) : esiSequence (aParent
) {}
217 CBDATA_CLASS_INIT(ESIContext
);
219 void ESIContext::startRead()
225 void ESIContext::finishRead()
231 bool ESIContext::reading() const
236 ESIStreamContext::ESIStreamContext() : finished(false), include (NULL
), localbuffer (new ESISegment
), buffer (NULL
)
239 /* Local functions */
241 static ESIContext
*ESIContextNew(HttpReply
*, clientStreamNode
*, ClientHttpRequest
*);
244 ESIContext::setError()
247 errorstatus
= Http::scInternalServerError
;
252 ESIContext::appendOutboundData(ESISegment::Pointer theData
)
254 if (!outbound
.getRaw()) {
256 outboundtail
= outbound
;
258 assert (outboundtail
->next
.getRaw() == NULL
);
259 outboundtail
->next
= theData
;
263 debugs(86, 9, "ESIContext::appendOutboundData: outbound " << outbound
.getRaw());
267 ESIContext::provideData (ESISegment::Pointer theData
, ESIElement
* source
)
269 debugs(86, 5, "ESIContext::provideData: " << this << " " << theData
.getRaw() << " " << source
);
270 /* No callbacks permitted after finish() called on the tree */
271 assert (tree
.getRaw());
272 assert (source
== tree
);
273 appendOutboundData(theData
);
281 ESIContext::fail (ESIElement
* source
, char const *anError
)
284 setErrorMessage (anError
);
290 ESIContext::fixupOutboundTail()
292 /* TODO: fixup thisNode outboundtail dross a little */
294 if (outboundtail
.getRaw())
295 outboundtail
= outboundtail
->tail();
302 debugs(86, 5, "esiKick: Re-entered whilst in progress");
303 // return ESI_KICK_INPROGRESS;
308 /* we've been detached from - we can't do anything more */
309 return ESI_KICK_FAILED
;
311 /* Something has occured. Process any remaining nodes */
313 /* Process some of our data */
314 switch (process ()) {
316 case ESI_PROCESS_COMPLETE
:
317 debugs(86, 5, "esiKick: esiProcess OK");
320 case ESI_PROCESS_PENDING_WONTFAIL
:
321 debugs(86, 5, "esiKick: esiProcess PENDING OK");
324 case ESI_PROCESS_PENDING_MAYFAIL
:
325 debugs(86, 5, "esiKick: esiProcess PENDING UNKNOWN");
328 case ESI_PROCESS_FAILED
:
329 debugs(86, 2, "esiKick: esiProcess " << this << " FAILED");
330 /* this can not happen - processing can't fail until we have data,
331 * and when we come here we have sent data to the client
339 return ESI_KICK_FAILED
;
342 /* Render if we can to get maximal sent data */
343 assert (tree
.getRaw() || flags
.error
);
345 if (!flags
.finished
&& !outbound
.getRaw()) {
346 outboundtail
= new ESISegment
;
347 outbound
= outboundtail
;
350 if (!flags
.error
&& !flags
.finished
)
351 tree
->render(outboundtail
);
356 /* Is there data to send? */
358 /* some data was sent. we're finished until the next read */
360 return ESI_KICK_SENT
;
364 /* nothing to send */
365 return flags
.error
? ESI_KICK_FAILED
: ESI_KICK_PENDING
;
368 /* request from downstream for more data
371 esiStreamRead (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
373 clientStreamNode
*next
;
374 /* Test preconditions */
375 assert (thisNode
!= NULL
);
376 assert (cbdataReferenceValid (thisNode
));
377 /* we are not in the chain until ESI is detected on a data callback */
378 assert (thisNode
->node
.prev
!= NULL
);
379 assert (thisNode
->node
.next
!= NULL
);
381 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
382 assert (context
.getRaw() != NULL
);
384 if (context
->flags
.passthrough
) {
385 /* passthru mode - read into supplied buffers */
386 next
= thisNode
->next();
387 clientStreamRead (thisNode
, http
, next
->readBuffer
);
391 context
->flags
.clientwantsdata
= 1;
392 debugs(86, 5, "esiStreamRead: Client now wants data");
394 /* Ok, not passing through */
396 switch (context
->kick ()) {
398 case ESIContext::ESI_KICK_FAILED
:
399 /* this can not happen - processing can't fail until we have data,
400 * and when we come here we have sent data to the client
403 case ESIContext::ESI_KICK_SENT
:
405 case ESIContext::ESI_KICK_INPROGRESS
:
408 case ESIContext::ESI_KICK_PENDING
:
412 /* Nothing to send */
414 if (context
->flags
.oktosend
&& (context
->flags
.finishedtemplate
415 || context
->cachedASTInUse
) &&
416 ! context
->flags
.finished
) {
417 /* we've started sending, finished reading, but not finished
418 * processing. stop here, a callback will resume the stream
421 debugs(86, 5, "esiStreamRead: Waiting for async resume of esi processing");
425 if (context
->flags
.oktosend
&& context
->flags
.finished
&& context
->outbound
.getRaw()) {
426 debugs(86, 5, "all processing complete, but outbound data still buffered");
427 assert (!context
->flags
.clientwantsdata
);
428 /* client MUST be processing the last reply */
432 if (context
->flags
.oktosend
&& context
->flags
.finished
) {
433 StoreIOBuffer tempBuffer
;
434 assert (!context
->outbound
.getRaw());
435 /* We've finished processing, and there is no more data buffered */
436 debugs(86, 5, "Telling recipient EOF on READ");
437 clientStreamCallback (thisNode
, http
, NULL
, tempBuffer
);
441 if (context
->reading())
444 /* no data that is ready to send, and still reading? well, lets get some */
445 /* secure a buffer */
446 if (!context
->incoming
.getRaw()) {
447 /* create a new buffer segment */
448 context
->buffered
= new ESISegment
;
449 context
->incoming
= context
->buffered
;
452 assert (context
->incoming
.getRaw() && context
->incoming
->len
!= HTTP_REQBUF_SZ
);
454 StoreIOBuffer tempBuffer
;
455 tempBuffer
.offset
= context
->readpos
;
456 tempBuffer
.length
= context
->incoming
->len
- HTTP_REQBUF_SZ
;
457 tempBuffer
.data
= &context
->incoming
->buf
[context
->incoming
->len
];
458 context
->startRead();
459 clientStreamRead (thisNode
, http
, tempBuffer
);
463 clientStream_status_t
464 esiStreamStatus (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
466 /* Test preconditions */
467 assert (thisNode
!= NULL
);
468 assert (cbdataReferenceValid (thisNode
));
469 /* we are not in the chain until ESI is detected on a data callback */
470 assert (thisNode
->node
.prev
!= NULL
);
471 assert (thisNode
->node
.next
!= NULL
);
473 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
474 assert (context
.getRaw() != NULL
);
476 if (context
->flags
.passthrough
)
477 return clientStreamStatus (thisNode
, http
);
479 if (context
->flags
.oktosend
&& context
->flags
.finished
&&
480 !(context
->outbound
.getRaw() && context
->outbound_offset
< context
->outbound
->len
)) {
481 debugs(86, 5, "Telling recipient EOF on STATUS");
482 return STREAM_UNPLANNED_COMPLETE
; /* we don't know lengths in advance */
485 /* ?? RC: we can't be aborted / fail ? */
490 esiAlwaysPassthrough(Http::StatusCode sline
)
496 case Http::scContinue
: /* Should never reach us... but squid needs to alter to accomodate this */
498 case Http::scSwitchingProtocols
: /* Ditto */
500 case Http::scProcessing
: /* Unknown - some extension */
502 case Http::scNoContent
: /* no body, no esi */
504 case Http::scNotModified
: /* ESI does not affect assembled page headers, so 304s are valid */
517 ESIContext::trimBlanks()
519 /* trim leading empty buffers ? */
521 while (outbound
.getRaw() && outbound
->next
.getRaw() && !outbound
->len
) {
522 debugs(86, 5, "ESIContext::trimBlanks: " << this <<
523 " skipping segment " << outbound
.getRaw());
524 outbound
= outbound
->next
;
527 if (outboundtail
.getRaw())
528 assert (outbound
.getRaw());
531 /* Send data downstream
532 * Returns 0 if nothing was sent. Non-zero if data was sent.
537 debugs(86, 5, "ESIContext::send: this=" << this);
538 /* send any processed data */
542 if (!flags
.clientwantsdata
) {
543 debugs(86, 5, "ESIContext::send: Client does not want data - not sending anything");
547 if (tree
.getRaw() && tree
->mayFail()) {
548 debugs(86, 5, "ESIContext::send: Tree may fail. Not sending.");
555 if (!flags
.oktosend
) {
557 fatal("ESIContext::send: Not OK to send.\n");
563 if (!(rep
|| (outbound
.getRaw() &&
564 outbound
->len
&& (outbound_offset
<= outbound
->len
)))) {
565 debugs(86, 5, "ESIContext::send: Nothing to send.");
569 debugs(86, 5, "ESIContext::send: Sending something...");
570 /* Yes! Send it without asking for more upstream */
571 /* memcopying because the client provided the buffer */
572 /* TODO: skip data until pos == next->readoff; */
573 assert (thisNode
->data
== this);
574 clientStreamNode
*next
= thisNode
->next();
575 ESIContext
*templock
= cbdataReference (this);
578 if (outbound
.getRaw())
579 len
= min (next
->readBuffer
.length
, outbound
->len
- outbound_offset
);
581 /* prevent corruption on range requests, even though we don't support them yet */
582 assert (pos
== next
->readBuffer
.offset
);
584 /* We must send data or a reply */
585 assert (len
!= 0 || rep
!= NULL
);
588 memcpy(next
->readBuffer
.data
, &outbound
->buf
[outbound_offset
], len
);
590 if (len
+ outbound_offset
== outbound
->len
) {
591 ESISegment::Pointer temp
= outbound
->next
;
592 /* remove the used buffer */
599 if (!outbound
.getRaw())
605 flags
.clientwantsdata
= 0;
606 debugs(86, 5, "ESIContext::send: this=" << this << " Client no longer wants data ");
607 /* Deal with re-entrancy */
608 HttpReplyPointer temprep
= rep
;
609 rep
= NULL
; /* freed downstream */
611 if (temprep
&& varState
)
612 varState
->buildVary(temprep
.getRaw());
615 StoreIOBuffer tempBuffer
;
616 tempBuffer
.length
= len
;
617 tempBuffer
.offset
= pos
- len
;
618 tempBuffer
.data
= next
->readBuffer
.data
;
619 clientStreamCallback (thisNode
, http
, temprep
.getRaw(), tempBuffer
);
623 len
= 1; /* tell the caller we sent something (because we sent headers */
625 cbdataReferenceDone (templock
);
627 debugs (86,5,"ESIContext::send: this=" << this << " sent " << len
);
633 ESIContext::finishChildren()
641 /* Detach event from a client Stream */
643 esiStreamDetach (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
645 /* if we have pending callbacks, tell them we're done. */
646 /* test preconditions */
647 assert (thisNode
!= NULL
);
648 assert (cbdataReferenceValid (thisNode
));
649 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
650 assert (context
.getRaw() != NULL
);
651 /* detach from the stream */
652 clientStreamDetach (thisNode
,http
);
653 /* if we have pending callbacks (from subincludes), tell them we're done. */
654 context
->thisNode
= NULL
;
655 context
->flags
.detached
= 1;
656 context
->finishChildren();
657 /* HACK for parser stack not being emptied */
658 context
->parserState
.stack
[0] = NULL
;
659 /* allow refcount logic to trigger */
660 context
->cbdataLocker
= NULL
;
663 /* Process incoming data for ESI tags */
664 /* ESI TODO: Long term: we should have a framework to parse html/xml and
665 * callback to a set of processors like thisNode, to prevent multiple parsing
666 * overhead. More thoughts on thisNode: We have to parse multiple times, because
667 * the output of one processor may create a very different tree. What we could
668 * do is something like DOM and pass that down to a final renderer. This is
669 * getting into web server territory though...
672 * This is not the last node in the stream.
673 * ESI processing has been enabled.
674 * There is context data or a reply structure
677 esiProcessStream (clientStreamNode
*thisNode
, ClientHttpRequest
*http
, HttpReply
*rep
, StoreIOBuffer receivedData
)
679 /* test preconditions */
680 assert (thisNode
!= NULL
);
681 /* ESI TODO: handle thisNode rather than asserting - it should only ever
682 * happen if we cause an abort and the callback chain
683 * loops back to here, so we can simply return. However, that itself
684 * shouldn't happen, so it stays as an assert for now. */
685 assert (cbdataReferenceValid (thisNode
));
687 * if data is NULL thisNode is the first entrance. If rep is also NULL,
688 * something is wrong.
690 assert (thisNode
->data
.getRaw() != NULL
|| rep
);
691 assert (thisNode
->node
.next
!= NULL
);
693 if (!thisNode
->data
.getRaw())
694 /* setup ESI context from reply headers */
695 thisNode
->data
= ESIContextNew(rep
, thisNode
, http
);
697 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
699 assert (context
.getRaw() != NULL
);
701 context
->finishRead();
703 /* Skipping all ESI processing. All remaining data gets untouched.
704 * Mainly used when an error or other non-ESI processable entity
705 * has been detected to prevent ESI processing the error body
707 if (context
->flags
.passthrough
) {
708 clientStreamCallback (thisNode
, http
, rep
, receivedData
);
712 debugs(86, 3, "esiProcessStream: Processing thisNode " << thisNode
<<
713 " context " << context
.getRaw() << " offset " <<
714 (int) receivedData
.offset
<< " length " <<
715 (unsigned int)receivedData
.length
);
717 /* once we finish the template, we *cannot* return here */
718 assert (!context
->flags
.finishedtemplate
);
719 assert (!context
->cachedASTInUse
);
721 /* Can we generate any data ?*/
723 if (receivedData
.data
) {
724 /* Increase our buffer area with incoming data */
725 assert (receivedData
.length
<= HTTP_REQBUF_SZ
);
726 assert (thisNode
->readBuffer
.offset
== receivedData
.offset
);
727 debugs (86,5, "esiProcessStream found " << receivedData
.length
<< " bytes of body data at offset " << receivedData
.offset
);
728 /* secure the data for later use */
730 if (!context
->incoming
.getRaw()) {
731 /* create a new buffer segment */
732 debugs(86, 5, "esiProcessStream: Setting up incoming buffer");
733 context
->buffered
= new ESISegment
;
734 context
->incoming
= context
->buffered
;
737 if (receivedData
.data
!= &context
->incoming
->buf
[context
->incoming
->len
]) {
738 /* We have to copy the data out because we didn't supply thisNode buffer */
739 size_t space
= HTTP_REQBUF_SZ
- context
->incoming
->len
;
740 size_t len
= min (space
, receivedData
.length
);
741 debugs(86, 5, "Copying data from " << receivedData
.data
<< " to " <<
742 &context
->incoming
->buf
[context
->incoming
->len
] <<
743 " because our buffer was not used");
745 memcpy(&context
->incoming
->buf
[context
->incoming
->len
], receivedData
.data
, len
);
746 context
->incoming
->len
+= len
;
748 if (context
->incoming
->len
== HTTP_REQBUF_SZ
) {
749 /* append another buffer */
750 context
->incoming
->next
= new ESISegment
;
751 context
->incoming
= context
->incoming
->next
;
754 if (len
!= receivedData
.length
) {
755 /* capture the remnants */
756 memcpy(context
->incoming
->buf
, &receivedData
.data
[len
], receivedData
.length
- len
);
757 context
->incoming
->len
= receivedData
.length
- len
;
760 /* and note where we are up to */
761 context
->readpos
+= receivedData
.length
;
763 /* update our position counters, and if needed assign a new buffer */
764 context
->incoming
->len
+= receivedData
.length
;
765 assert (context
->incoming
->len
<= HTTP_REQBUF_SZ
);
767 if (context
->incoming
->len
> HTTP_REQBUF_SZ
* 3 / 4) {
768 /* allocate a new buffer - to stop us asking for ridiculously small amounts */
769 context
->incoming
->next
= new ESISegment
;
770 context
->incoming
= context
->incoming
->next
;
773 context
->readpos
+= receivedData
.length
;
777 /* EOF / Read error / aborted entry */
778 if (rep
== NULL
&& receivedData
.data
== NULL
&& receivedData
.length
== 0 && !context
->flags
.finishedtemplate
) {
779 /* TODO: get stream status to test the entry for aborts */
780 /* else flush the esi processor */
781 debugs(86, 5, "esiProcess: " << context
.getRaw() << " Finished reading upstream data");
782 /* This is correct */
783 context
->flags
.finishedtemplate
= 1;
786 switch (context
->kick()) {
788 case ESIContext::ESI_KICK_FAILED
:
789 /* thisNode can not happen - processing can't fail until we have data,
790 * and when we come here we have sent data to the client
794 case ESIContext::ESI_KICK_SENT
:
796 case ESIContext::ESI_KICK_INPROGRESS
:
799 case ESIContext::ESI_KICK_PENDING
:
803 /* ok.. no data sent, try to pull more data in from upstream.
804 * FIXME: Don't try thisNode if we have finished reading the template
806 if (!context
->flags
.finishedtemplate
&& !context
->reading()
807 && !context
->cachedASTInUse
) {
808 StoreIOBuffer tempBuffer
;
809 assert (context
->incoming
.getRaw() && context
->incoming
->len
< HTTP_REQBUF_SZ
);
810 tempBuffer
.offset
= context
->readpos
;
811 tempBuffer
.length
= HTTP_REQBUF_SZ
- context
->incoming
->len
;
812 tempBuffer
.data
= &context
->incoming
->buf
[context
->incoming
->len
];
813 context
->startRead();
814 clientStreamRead (thisNode
, http
, tempBuffer
);
818 debugs(86, 3, "esiProcessStream: no data to send, no data to read, awaiting a callback");
821 ESIContext::~ESIContext()
824 /* Not freed by freeresources because esi::fail needs it */
825 safe_free (errormessage
);
826 debugs(86, 3, "ESIContext::~ESIContext: Freed " << this);
830 ESIContextNew (HttpReply
*rep
, clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
833 ESIContext
*rv
= new ESIContext
;
835 rv
->cbdataLocker
= rv
;
837 if (esiAlwaysPassthrough(rep
->sline
.status())) {
838 rv
->flags
.passthrough
= 1;
840 /* remove specific headers for ESI to prevent
841 * downstream cache confusion */
842 HttpHeader
*hdr
= &rep
->header
;
843 hdr
->delById(Http::HdrType::ACCEPT_RANGES
);
844 hdr
->delById(Http::HdrType::ETAG
);
845 hdr
->delById(Http::HdrType::CONTENT_LENGTH
);
846 hdr
->delById(Http::HdrType::CONTENT_MD5
);
847 rv
->tree
= new esiSequence (rv
, true);
848 rv
->thisNode
= thisNode
;
850 rv
->flags
.clientwantsdata
= 1;
851 rv
->varState
= new ESIVarState (&http
->request
->header
, http
->uri
);
852 debugs(86, 5, "ESIContextNew: Client wants data (always created during reply cycle");
855 debugs(86, 5, "ESIContextNew: Create context " << rv
);
859 ESIElement::ESIElementType_t
860 ESIElement::IdentifyElement (const char *el
)
866 return ESI_ELEMENT_NONE
;
868 if (!strncmp (el
, "esi:", 4))
870 else if (!strncmp (el
, "http://www.edge-delivery.org/esi/1.0|", 37))
873 return ESI_ELEMENT_NONE
;
875 if (!strncmp (el
+ offset
, "otherwise", 9))
876 return ESI_ELEMENT_OTHERWISE
;
878 if (!strncmp (el
+ offset
, "comment", 7))
879 return ESI_ELEMENT_COMMENT
;
881 if (!strncmp (el
+ offset
, "include", 7))
882 return ESI_ELEMENT_INCLUDE
;
884 if (!strncmp (el
+ offset
, "attempt", 7))
885 return ESI_ELEMENT_ATTEMPT
;
887 if (!strncmp (el
+ offset
, "assign", 6))
888 return ESI_ELEMENT_ASSIGN
;
890 if (!strncmp (el
+ offset
, "remove", 6))
891 return ESI_ELEMENT_REMOVE
;
893 if (!strncmp (el
+ offset
, "except", 6))
894 return ESI_ELEMENT_EXCEPT
;
896 if (!strncmp (el
+ offset
, "choose", 6))
897 return ESI_ELEMENT_CHOOSE
;
899 if (!strncmp (el
+ offset
, "vars", 4))
900 return ESI_ELEMENT_VARS
;
902 if (!strncmp (el
+ offset
, "when", 4))
903 return ESI_ELEMENT_WHEN
;
905 if (!strncmp (el
+ offset
, "try", 3))
906 return ESI_ELEMENT_TRY
;
908 return ESI_ELEMENT_NONE
;
912 ESIContext::ParserState::top()
914 return stack
[stackdepth
-1];
917 ESIContext::ParserState::ParserState() :
924 ESIContext::ParserState::inited() const
930 ESIContext::addStackElement (ESIElement::Pointer element
)
932 /* Put on the stack to allow skipping of 'invalid' markup */
933 assert (parserState
.stackdepth
<11);
935 debugs(86, 5, "ESIContext::addStackElement: About to add ESI Node " << element
.getRaw());
937 if (!parserState
.top()->addElement(element
)) {
938 debugs(86, DBG_IMPORTANT
, "ESIContext::addStackElement: failed to add esi node, probable error in ESI template");
941 /* added ok, push onto the stack */
942 parserState
.stack
[parserState
.stackdepth
] = element
;
943 ++parserState
.stackdepth
;
948 ESIContext::start(const char *el
, const char **attr
, size_t attrCount
)
951 unsigned int ellen
= strlen (el
);
952 char localbuf
[HTTP_REQBUF_SZ
];
953 ESIElement::Pointer element
;
954 int specifiedattcount
= attrCount
* 2;
956 Must(ellen
< sizeof(localbuf
)); /* prevent unexpected overruns. */
958 debugs(86, 5, "ESIContext::Start: element '" << el
<< "' with " << specifiedattcount
<< " tags");
961 /* waiting for expat to finish the buffer we gave it */
964 switch (ESIElement::IdentifyElement (el
)) {
966 case ESIElement::ESI_ELEMENT_NONE
:
967 /* Spit out elements we aren't interested in */
970 xstrncpy(&localbuf
[1], el
, sizeof(localbuf
) - 2);
971 position
= localbuf
+ strlen (localbuf
);
973 for (i
= 0; i
< specifiedattcount
&& attr
[i
]; i
+= 2) {
974 Must(static_cast<size_t>(position
- localbuf
) < sizeof(localbuf
) - 1);
977 /* TODO: handle thisNode gracefully */
978 xstrncpy(position
, attr
[i
], sizeof(localbuf
) - (position
- localbuf
));
979 position
+= strlen (position
);
980 Must(static_cast<size_t>(position
- localbuf
) < sizeof(localbuf
) - 2);
985 const char *chPtr
= attr
[i
+ 1];
987 while ((ch
= *chPtr
++) != '\0') {
989 Must(static_cast<size_t>(position
- localbuf
) < sizeof(localbuf
) - 6);
990 xstrncpy(position
, """, sizeof(localbuf
) - (position
-localbuf
));
993 Must(static_cast<size_t>(position
- localbuf
) < sizeof(localbuf
) - 1);
998 Must(static_cast<size_t>(position
- localbuf
) < sizeof(localbuf
) - 1);
1003 Must(static_cast<size_t>(position
- localbuf
) < sizeof(localbuf
) - 2);
1008 addLiteral (localbuf
, position
- localbuf
);
1009 debugs(86, 5, "esi stack depth " << parserState
.stackdepth
);
1013 case ESIElement::ESI_ELEMENT_COMMENT
:
1014 /* Put on the stack to allow skipping of 'invalid' markup */
1015 element
= new esiComment ();
1018 case ESIElement::ESI_ELEMENT_INCLUDE
:
1019 /* Put on the stack to allow skipping of 'invalid' markup */
1020 element
= new ESIInclude (parserState
.top().getRaw(), specifiedattcount
, attr
, this);
1023 case ESIElement::ESI_ELEMENT_REMOVE
:
1024 /* Put on the stack to allow skipping of 'invalid' markup */
1025 element
= new esiRemove();
1028 case ESIElement::ESI_ELEMENT_TRY
:
1029 /* Put on the stack to allow skipping of 'invalid' markup */
1030 element
= new esiTry (parserState
.top().getRaw());
1033 case ESIElement::ESI_ELEMENT_ATTEMPT
:
1034 /* Put on the stack to allow skipping of 'invalid' markup */
1035 element
= new esiAttempt (parserState
.top().getRaw());
1038 case ESIElement::ESI_ELEMENT_EXCEPT
:
1039 /* Put on the stack to allow skipping of 'invalid' markup */
1040 element
= new esiExcept (parserState
.top().getRaw());
1043 case ESIElement::ESI_ELEMENT_VARS
:
1044 /* Put on the stack to allow skipping of 'invalid' markup */
1045 element
= new ESIVar (parserState
.top().getRaw());
1048 case ESIElement::ESI_ELEMENT_CHOOSE
:
1049 /* Put on the stack to allow skipping of 'invalid' markup */
1050 element
= new esiChoose (parserState
.top().getRaw());
1053 case ESIElement::ESI_ELEMENT_WHEN
:
1054 /* Put on the stack to allow skipping of 'invalid' markup */
1055 element
= new esiWhen (parserState
.top().getRaw(), specifiedattcount
, attr
, varState
);
1058 case ESIElement::ESI_ELEMENT_OTHERWISE
:
1059 /* Put on the stack to allow skipping of 'invalid' markup */
1060 element
= new esiOtherwise (parserState
.top().getRaw());
1063 case ESIElement::ESI_ELEMENT_ASSIGN
:
1064 /* Put on the stack to allow skipping of 'invalid' markup */
1065 element
= new ESIAssign (parserState
.top().getRaw(), specifiedattcount
, attr
, this);
1069 addStackElement(element
);
1071 debugs(86, 5, "esi stack depth " << parserState
.stackdepth
);
1073 } /* End of start handler */
1076 ESIContext::end(const char *el
)
1078 unsigned int ellen
= strlen (el
);
1079 char localbuf
[HTTP_REQBUF_SZ
];
1083 /* waiting for expat to finish the buffer we gave it */
1086 switch (ESIElement::IdentifyElement (el
)) {
1088 case ESIElement::ESI_ELEMENT_NONE
:
1089 Must(ellen
< sizeof(localbuf
) - 3); /* prevent unexpected overruns. */
1090 /* Add elements we aren't interested in */
1093 xstrncpy(&localbuf
[2], el
, sizeof(localbuf
) - 3);
1094 position
= localbuf
+ strlen (localbuf
);
1098 addLiteral (localbuf
, position
- localbuf
);
1101 case ESIElement::ESI_ELEMENT_COMMENT
:
1103 case ESIElement::ESI_ELEMENT_INCLUDE
:
1105 case ESIElement::ESI_ELEMENT_REMOVE
:
1107 case ESIElement::ESI_ELEMENT_TRY
:
1109 case ESIElement::ESI_ELEMENT_ATTEMPT
:
1111 case ESIElement::ESI_ELEMENT_EXCEPT
:
1113 case ESIElement::ESI_ELEMENT_VARS
:
1115 case ESIElement::ESI_ELEMENT_CHOOSE
:
1117 case ESIElement::ESI_ELEMENT_WHEN
:
1119 case ESIElement::ESI_ELEMENT_OTHERWISE
:
1121 case ESIElement::ESI_ELEMENT_ASSIGN
:
1122 /* pop of the stack */
1123 parserState
.stack
[--parserState
.stackdepth
] = NULL
;
1126 } /* End of end handler */
1129 ESIContext::parserDefault (const char *s
, int len
)
1134 /* handle any skipped data */
1135 addLiteral (s
, len
);
1139 ESIContext::parserComment (const char *s
)
1144 if (!strncmp(s
, "esi",3)) {
1145 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block encountered");
1146 ESIParser::Pointer tempParser
= ESIParser::NewParser (this);
1148 /* wrap the comment in some tags */
1150 if (!tempParser
->parse("<div>", 5,0) ||
1151 !tempParser
->parse(s
+ 3, strlen(s
) - 3, 0) ||
1152 !tempParser
->parse("</div>",6,1)) {
1153 debugs(86, DBG_CRITICAL
, "ESIContext::parserComment: Parsing fragment '" << s
+ 3 << "' failed.");
1156 snprintf(tempstr
, 1023, "ESIContext::parserComment: Parse error at line %ld:\n%s\n",
1157 tempParser
->lineNumber(),
1158 tempParser
->errorString());
1159 debugs(86, DBG_CRITICAL
, "" << tempstr
<< "");
1161 setErrorMessage(tempstr
);
1164 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block parsed");
1167 char localbuf
[HTTP_REQBUF_SZ
];
1169 debugs(86, 5, "ESIContext::parserComment: Regenerating comment block");
1172 if (len
> sizeof (localbuf
) - 9) {
1173 debugs(86, DBG_CRITICAL
, "ESIContext::parserComment: Truncating long comment");
1174 len
= sizeof (localbuf
) - 9;
1177 xstrncpy(localbuf
, "<!--", 5);
1178 xstrncpy(localbuf
+ 4, s
, len
+ 1);
1179 xstrncpy(localbuf
+ 4 + len
, "-->", 4);
1180 addLiteral (localbuf
,len
+ 7);
1185 ESIContext::addLiteral (const char *s
, int len
)
1187 /* handle any skipped data */
1189 debugs(86, 5, "literal length is " << len
);
1190 /* give a literal to the current element */
1191 assert (parserState
.stackdepth
<11);
1192 ESIElement::Pointer
element (new esiLiteral (this, s
, len
));
1194 if (!parserState
.top()->addElement(element
)) {
1195 debugs(86, DBG_IMPORTANT
, "ESIContext::addLiteral: failed to add esi node, probable error in ESI template");
1201 ESIContext::ParserState::init(ESIParserClient
*userData
)
1203 theParser
= ESIParser::NewParser (userData
);
1208 ESIContext::parseOneBuffer()
1210 assert (buffered
.getRaw());
1212 debugs (86,9,"ESIContext::parseOneBuffer: " << buffered
->len
<< " bytes");
1213 bool lastBlock
= buffered
->next
.getRaw() == NULL
&& flags
.finishedtemplate
? true : false;
1215 if (! parserState
.theParser
->parse(buffered
->buf
, buffered
->len
, lastBlock
)) {
1218 snprintf (tempstr
, 1023, "esiProcess: Parse error at line %ld:\n%s\n",
1219 parserState
.theParser
->lineNumber(),
1220 parserState
.theParser
->errorString());
1221 debugs(86, DBG_CRITICAL
, "" << tempstr
<< "");
1223 setErrorMessage(tempstr
);
1225 assert (flags
.error
);
1235 ESISegment::Pointer temp
= buffered
;
1236 buffered
= temp
->next
;
1242 if (!parserState
.stackdepth
) {
1243 debugs(86, 5, "empty parser stack, inserting the top level node");
1244 assert (tree
.getRaw());
1245 parserState
.stack
[parserState
.stackdepth
] = tree
;
1246 ++parserState
.stackdepth
;
1249 if (rep
&& !parserState
.inited())
1250 parserState
.init(this);
1253 if (buffered
.getRaw()) {
1254 parserState
.parsing
= 1;
1255 /* we don't keep any data around */
1257 PROF_start(esiParsing
);
1259 while (buffered
.getRaw() && !flags
.error
)
1262 PROF_stop(esiParsing
);
1264 /* Tel the read code to allocate a new buffer */
1267 parserState
.parsing
= 0;
1272 ESIContext::process ()
1275 * read through buffered, skipping plain text, and skipping any
1276 * <...> entry that is not an <esi: entry.
1277 * when it's found, hand an esiLiteral of the preceding data to our current
1281 if (parserState
.parsing
) {
1282 /* in middle of parsing - finish here */
1283 return ESI_PROCESS_PENDING_MAYFAIL
;
1286 assert (flags
.finished
== 0);
1288 assert (!flags
.error
);
1290 if (!hasCachedAST())
1292 else if (!flags
.finishedtemplate
)
1296 debugs(86, 5, "ESIContext::process: Parsing failed");
1298 parserState
.popAll();
1299 return ESI_PROCESS_FAILED
;
1302 if (!flags
.finishedtemplate
&& !incoming
.getRaw() && !cachedASTInUse
) {
1303 buffered
= new ESISegment
;
1304 incoming
= buffered
;
1307 if (!flags
.finishedtemplate
&& !cachedASTInUse
) {
1308 return ESI_PROCESS_PENDING_MAYFAIL
;
1311 assert (flags
.finishedtemplate
|| cachedASTInUse
);
1313 /* ok, we've done all we can with the data. What can we process now?
1316 esiProcessResult_t status
;
1317 PROF_start(esiProcessing
);
1319 status
= tree
->process(0);
1324 case ESI_PROCESS_COMPLETE
:
1325 debugs(86, 5, "esiProcess: tree Processed OK");
1328 case ESI_PROCESS_PENDING_WONTFAIL
:
1329 debugs(86, 5, "esiProcess: tree Processed PENDING OK");
1332 case ESI_PROCESS_PENDING_MAYFAIL
:
1333 debugs(86, 5, "esiProcess: tree Processed PENDING UNKNOWN");
1336 case ESI_PROCESS_FAILED
:
1337 debugs(86, DBG_CRITICAL
, "esiProcess: tree Processed FAILED");
1340 setErrorMessage("esiProcess: ESI template Processing failed.");
1342 PROF_stop(esiProcessing
);
1344 return ESI_PROCESS_FAILED
;
1349 if (status
!= ESI_PROCESS_PENDING_MAYFAIL
&& (flags
.finishedtemplate
|| cachedASTInUse
)) {
1350 /* We've read the entire template, and no nodes will
1353 debugs(86, 5, "esiProcess, request will succeed");
1357 if (status
== ESI_PROCESS_COMPLETE
1358 && (flags
.finishedtemplate
|| cachedASTInUse
)) {
1359 /* we've finished all processing. Render and send. */
1360 debugs(86, 5, "esiProcess, processing complete");
1364 PROF_stop(esiProcessing
);
1365 return status
; /* because we have no callbacks */
1370 ESIContext::ParserState::freeResources()
1377 ESIContext::ParserState::popAll()
1380 stack
[--stackdepth
] = NULL
;
1384 ESIContext::freeResources ()
1386 debugs(86, 5, HERE
<< "Freeing for this=" << this);
1388 rep
= nullptr; // refcounted
1392 if (parserState
.inited()) {
1393 parserState
.freeResources();
1396 parserState
.popAll();
1397 ESISegmentFreeList (buffered
);
1398 ESISegmentFreeList (outbound
);
1399 ESISegmentFreeList (outboundtail
);
1402 /* don't touch incoming, it's a pointer into buffered anyway */
1405 ErrorState
*clientBuildError (err_type
, Http::StatusCode
, char const *, Ip::Address
&, HttpRequest
*);
1407 /* This can ONLY be used before we have sent *any* data to the client */
1411 debugs(86, 5, "ESIContext::fail: this=" << this);
1412 /* check preconditions */
1414 /* cleanup current state */
1416 /* Stop altering thisNode request */
1419 /* don't honour range requests - for errors we send it all */
1421 /* create an error object */
1422 // XXX: with the in-direction on remote IP. does the http->getConn()->clientConnection exist?
1423 ErrorState
* err
= clientBuildError(errorpage
, errorstatus
, NULL
, http
->getConn()->clientConnection
->remote
, http
->request
);
1424 err
->err_msg
= errormessage
;
1425 errormessage
= NULL
;
1426 rep
= err
->BuildHttpReply();
1427 assert (rep
->body
.hasContent());
1428 size_t errorprogress
= rep
->body
.contentSize();
1429 /* Tell esiSend where to start sending from */
1430 outbound_offset
= 0;
1431 /* copy the membuf from the reply to outbound */
1433 while (errorprogress
< (size_t)rep
->body
.contentSize()) {
1434 appendOutboundData(new ESISegment
);
1435 errorprogress
+= outboundtail
->append(rep
->body
.content() + errorprogress
, rep
->body
.contentSize() - errorprogress
);
1438 /* the esiCode now thinks that the error is the outbound,
1439 * and all processing has finished. */
1440 /* Send as much as we can */
1443 /* don't cancel anything. The stream nodes will clean up after
1444 * themselves when the reply is freed - and we don't know what to
1449 /* Implementation of ESIElements */
1452 esiComment::~esiComment()
1454 debugs(86, 5, "esiComment::~esiComment " << this);
1457 esiComment::esiComment()
1461 esiComment::finish()
1465 esiComment::render(ESISegment::Pointer output
)
1467 /* Comments do nothing dude */
1468 debugs(86, 5, "esiCommentRender: Rendering comment " << this << " into " << output
.getRaw());
1472 esiComment::makeCacheable() const
1474 debugs(86, 5, "esiComment::makeCacheable: returning NULL");
1479 esiComment::makeUsable(esiTreeParentPtr
, ESIVarState
&) const
1481 fatal ("esiComment::Usable: unreachable code!\n");
1486 esiLiteral::~esiLiteral()
1488 debugs(86, 5, "esiLiteral::~esiLiteral: " << this);
1489 ESISegmentFreeList (buffer
);
1490 cbdataReferenceDone (varState
);
1493 esiLiteral::esiLiteral(ESISegment::Pointer aSegment
) :
1502 esiLiteral::finish()
1505 /* precondition: the buffer chain has at least start + length bytes of data
1507 esiLiteral::esiLiteral(ESIContext
*context
, const char *s
, int numberOfCharacters
)
1511 buffer
= new ESISegment
;
1512 ESISegment::Pointer local
= buffer
;
1514 int remainingCharacters
= numberOfCharacters
;
1516 while (remainingCharacters
> 0) {
1517 if (local
->len
== sizeof (local
->buf
)) {
1518 local
->next
= new ESISegment
;
1522 size_t len
= local
->append (&s
[start
], remainingCharacters
);
1524 remainingCharacters
-= len
;
1527 varState
= cbdataReference(context
->varState
);
1531 esiLiteral::render (ESISegment::Pointer output
)
1533 debugs(86, 9, "esiLiteral::render: Rendering " << this);
1534 /* append the entire chain */
1535 assert (output
->next
.getRaw() == NULL
);
1536 output
->next
= buffer
;
1541 esiLiteral::process (int dovars
)
1544 return ESI_PROCESS_COMPLETE
;
1547 ESISegment::Pointer temp
= buffer
;
1548 /* Ensure variable state is clean */
1550 while (temp
.getRaw()) {
1551 varState
->feedData(temp
->buf
,temp
->len
);
1555 /* free the pre-processed content */
1556 ESISegmentFreeList (buffer
);
1558 buffer
= varState
->extractList ();
1562 return ESI_PROCESS_COMPLETE
;
1565 esiLiteral::esiLiteral(esiLiteral
const &old
) : buffer (old
.buffer
->cloneList()),
1572 esiLiteral::makeCacheable() const
1574 return new esiLiteral (*this);
1578 esiLiteral::makeUsable(esiTreeParentPtr
, ESIVarState
&newVarState
) const
1580 debugs(86, 5, "esiLiteral::makeUsable: Creating usable literal");
1581 esiLiteral
* result
= new esiLiteral (*this);
1582 result
->varState
= cbdataReference (&newVarState
);
1588 esiRemove::render(ESISegment::Pointer output
)
1590 /* Removes do nothing dude */
1591 debugs(86, 5, "esiRemoveRender: Rendering remove " << this);
1594 /* Accept non-ESI children */
1596 esiRemove::addElement (ESIElement::Pointer element
)
1598 if (!dynamic_cast<esiLiteral
*>(element
.getRaw())) {
1599 debugs(86, 5, "esiRemoveAdd: Failed for " << this);
1607 esiRemove::makeCacheable() const
1609 debugs(86, 5, "esiRemove::makeCacheable: Returning NULL");
1614 esiRemove::makeUsable(esiTreeParentPtr
, ESIVarState
&) const
1616 fatal ("esiRemove::Usable: unreachable code!\n");
1623 debugs(86, 5, "esiTry::~esiTry " << this);
1626 esiTry::esiTry(esiTreeParentPtr aParent
) :
1630 memset(&flags
, 0, sizeof(flags
));
1634 esiTry::render(ESISegment::Pointer output
)
1636 /* Try renders from it's children */
1637 assert (attempt
.getRaw());
1638 assert (except
.getRaw());
1639 debugs(86, 5, "esiTryRender: Rendering Try " << this);
1641 if (flags
.attemptok
) {
1642 attempt
->render(output
);
1643 } else if (flags
.exceptok
) {
1646 if (exceptbuffer
.getRaw())
1647 ESISegment::ListTransfer(exceptbuffer
, output
);
1649 except
->render(output
);
1651 debugs(86, 5, "esiTryRender: Neither except nor attempt succeeded?!?");
1654 /* Accept attempt and except only */
1656 esiTry::addElement(ESIElement::Pointer element
)
1658 debugs(86, 5, "esiTryAdd: Try " << this << " adding element " <<
1661 if (dynamic_cast<esiLiteral
*>(element
.getRaw())) {
1662 /* Swallow whitespace */
1663 debugs(86, 5, "esiTryAdd: Try " << this << " skipping whitespace " << element
.getRaw());
1667 if (dynamic_cast<esiAttempt
*>(element
.getRaw())) {
1668 if (attempt
.getRaw()) {
1669 debugs(86, DBG_IMPORTANT
, "esiTryAdd: Failed for " << this << " - try allready has an attempt node (section 3.4)");
1677 if (dynamic_cast<esiExcept
*>(element
.getRaw())) {
1678 if (except
.getRaw()) {
1679 debugs(86, DBG_IMPORTANT
, "esiTryAdd: Failed for " << this << " - try already has an except node (section 3.4)");
1687 debugs(86, DBG_IMPORTANT
, "esiTryAdd: Failed to add element " << element
.getRaw() << " to try " << this << ", incorrect element type (see section 3.4)");
1692 esiTry::bestAttemptRV() const
1694 if (flags
.attemptfailed
)
1695 return ESI_PROCESS_COMPLETE
;
1697 return ESI_PROCESS_PENDING_MAYFAIL
;
1701 esiTry::process (int dovars
)
1703 esiProcessResult_t rv
= ESI_PROCESS_PENDING_MAYFAIL
;
1705 if (!attempt
.getRaw()) {
1706 debugs(86, DBG_CRITICAL
, "esiTryProcess: Try has no attempt element - ESI template is invalid (section 3.4)");
1707 return ESI_PROCESS_FAILED
;
1710 if (!except
.getRaw()) {
1711 debugs(86, DBG_CRITICAL
, "esiTryProcess: Try has no except element - ESI template is invalid (section 3.4)");
1712 return ESI_PROCESS_FAILED
;
1715 if (!flags
.attemptfailed
)
1716 /* Try the attempt branch */
1717 switch ((rv
= attempt
->process(dovars
))) {
1719 case ESI_PROCESS_COMPLETE
:
1720 debugs(86, 5, "esiTryProcess: attempt Processed OK");
1721 flags
.attemptok
= 1;
1722 return ESI_PROCESS_COMPLETE
;
1724 case ESI_PROCESS_PENDING_WONTFAIL
:
1725 debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
1726 /* We're not done yet, but don't need to test except */
1727 return ESI_PROCESS_PENDING_WONTFAIL
;
1729 case ESI_PROCESS_PENDING_MAYFAIL
:
1730 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
1733 case ESI_PROCESS_FAILED
:
1734 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
1735 flags
.attemptfailed
= 1;
1739 /* attempt is either MAYFAIL or FAILED */
1741 return bestAttemptRV();
1743 /* query except to see if it has a definite result */
1744 if (!flags
.exceptfailed
)
1745 /* Try the except branch */
1746 switch (except
->process(dovars
)) {
1748 case ESI_PROCESS_COMPLETE
:
1749 debugs(86, 5, "esiTryProcess: except Processed OK");
1751 return bestAttemptRV();
1753 case ESI_PROCESS_PENDING_WONTFAIL
:
1754 debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
1755 /* We're not done yet, but can't fail */
1756 return ESI_PROCESS_PENDING_WONTFAIL
;
1758 case ESI_PROCESS_PENDING_MAYFAIL
:
1759 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
1760 /* The except branch fail fail */
1761 return ESI_PROCESS_PENDING_MAYFAIL
;
1763 case ESI_PROCESS_FAILED
:
1764 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
1765 flags
.exceptfailed
= 1;
1769 if (flags
.exceptfailed
&& flags
.attemptfailed
)
1770 return ESI_PROCESS_FAILED
;
1772 /* one of attempt or except returned PENDING MAYFAIL */
1773 return ESI_PROCESS_PENDING_MAYFAIL
;
1777 esiTry::notifyParent()
1779 if (flags
.attemptfailed
) {
1780 if (flags
.exceptok
) {
1781 parent
->provideData (exceptbuffer
, this);
1782 exceptbuffer
= NULL
;
1783 } else if (flags
.exceptfailed
|| except
.getRaw() == NULL
) {
1784 parent
->fail (this, "esi:try - except claused failed, or no except clause found");
1788 /* nothing to do when except fails and attempt hasn't */
1792 esiTry::fail(ESIElement
*source
, char const *anError
)
1795 assert (source
== attempt
|| source
== except
);
1796 debugs(86, 5, "esiTry::fail: this=" << this << ", source=" << source
<< ", message=" << anError
);
1798 if (source
== except
) {
1799 flags
.exceptfailed
= 1;
1801 flags
.attemptfailed
= 1;
1808 esiTry::provideData (ESISegment::Pointer data
, ESIElement
* source
)
1810 if (source
== attempt
) {
1811 flags
.attemptok
= 1;
1812 parent
->provideData (data
, this);
1813 } else if (source
== except
) {
1815 assert (exceptbuffer
== NULL
);
1816 ESISegment::ListTransfer (data
, exceptbuffer
);
1821 esiTry::esiTry(esiTry
const &old
)
1825 flags
.attemptok
= 0;
1827 flags
.attemptfailed
= 0;
1828 flags
.exceptfailed
= 0;
1830 exceptbuffer
= NULL
;
1834 esiTry::makeCacheable() const
1836 debugs(86, 5, "esiTry::makeCacheable: making cachable Try from " << this);
1837 esiTry
*resultT
= new esiTry (*this);
1838 ESIElement::Pointer result
= resultT
;
1840 if (attempt
.getRaw())
1841 resultT
->attempt
= attempt
->makeCacheable();
1843 if (except
.getRaw())
1844 resultT
->except
= except
->makeCacheable();
1850 esiTry::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
1852 debugs(86, 5, "esiTry::makeUsable: making usable Try from " << this);
1853 esiTry
*resultT
= new esiTry (*this);
1854 ESIElement::Pointer result
= resultT
;
1856 resultT
->parent
= newParent
;
1858 if (attempt
.getRaw())
1859 resultT
->attempt
= attempt
->makeUsable(resultT
, newVarState
);
1861 if (except
.getRaw())
1862 resultT
->except
= except
->makeUsable(resultT
, newVarState
);
1872 if (attempt
.getRaw())
1877 if (except
.getRaw())
1884 esiChoose::~esiChoose()
1886 debugs(86, 5, "esiChoose::~esiChoose " << this);
1889 esiChoose::esiChoose(esiTreeParentPtr aParent
) :
1896 esiChoose::render(ESISegment::Pointer output
)
1898 /* append all processed elements, and trim processed and rendered elements */
1899 assert (output
->next
== NULL
);
1900 assert (elements
.size() || otherwise
.getRaw());
1901 debugs(86, 5, "esiChooseRender: rendering");
1903 if (chosenelement
>= 0)
1904 elements
[chosenelement
]->render(output
);
1905 else if (otherwise
.getRaw())
1906 otherwise
->render(output
);
1910 esiChoose::addElement(ESIElement::Pointer element
)
1912 /* add an element to the output list */
1914 if (dynamic_cast<esiLiteral
*>(element
.getRaw())) {
1915 /* Swallow whitespace */
1916 debugs(86, 5, "esiChooseAdd: Choose " << this << " skipping whitespace " << element
.getRaw());
1920 /* Some elements require specific parents */
1921 if (!(dynamic_cast<esiWhen
*>(element
.getRaw()) || dynamic_cast<esiOtherwise
*>(element
.getRaw()))) {
1922 debugs(86, DBG_CRITICAL
, "esiChooseAdd: invalid child node for esi:choose (section 3.3)");
1926 if (dynamic_cast<esiOtherwise
*>(element
.getRaw())) {
1927 if (otherwise
.getRaw()) {
1928 debugs(86, DBG_CRITICAL
, "esiChooseAdd: only one otherwise node allowed for esi:choose (section 3.3)");
1932 otherwise
= element
;
1934 elements
.push_back (element
);
1936 debugs (86,3, "esiChooseAdd: Added a new element, elements = " << elements
.size());
1938 if (chosenelement
== -1) {
1939 const esiWhen
* topElement
=dynamic_cast<esiWhen
*>(element
.getRaw());
1940 if (topElement
&& topElement
->testsTrue()) {
1941 chosenelement
= elements
.size() - 1;
1942 debugs (86,3, "esiChooseAdd: Chose element " << elements
.size());
1951 esiChoose::selectElement()
1953 if (chosenelement
> -1)
1956 for (size_t counter
= 0; counter
< elements
.size(); ++counter
) {
1957 const esiWhen
*el
= dynamic_cast<esiWhen
*>(elements
[counter
].getRaw());
1958 if (el
&& el
->testsTrue()) {
1959 chosenelement
= counter
;
1960 debugs (86,3, "esiChooseAdd: Chose element " << counter
+ 1);
1969 elements
.setNULL(0, elements
.size());
1971 if (otherwise
.getRaw())
1972 otherwise
->finish();
1980 ElementList::setNULL (int start
, int end
)
1982 assert (start
>= 0 && start
<= elementcount
);
1983 assert (end
>= 0 && end
<= elementcount
);
1985 for (int loopPosition
= start
; loopPosition
< end
; ++loopPosition
) {
1986 if (elements
[loopPosition
].getRaw())
1987 elements
[loopPosition
]->finish();
1989 debugs(86, 5, "esiSequence::NULLElements: Setting index " <<
1990 loopPosition
<< ", pointer " <<
1991 elements
[loopPosition
].getRaw() << " to NULL");
1993 elements
[loopPosition
] = NULL
;
1998 esiChoose::NULLUnChosen()
2000 if (chosenelement
>= 0) {
2001 if (otherwise
.getRaw())
2002 otherwise
->finish();
2006 elements
.setNULL (0, chosenelement
);
2008 elements
.setNULL (chosenelement
+ 1, elements
.size());
2009 } else if (otherwise
.getRaw()) {
2010 elements
.setNULL (0, elements
.size());
2015 esiChoose::process (int dovars
)
2017 /* process as much of the list as we can, stopping only on
2020 /* We MUST have a when clause */
2023 if (!elements
.size()) {
2026 if (otherwise
.getRaw())
2027 otherwise
->finish();
2033 return ESI_PROCESS_FAILED
;
2036 if (chosenelement
>= 0) {
2037 return elements
[chosenelement
]->process(dovars
);
2038 } else if (otherwise
.getRaw())
2039 return otherwise
->process(dovars
);
2041 return ESI_PROCESS_COMPLETE
;
2045 esiChoose::checkValidSource (ESIElement::Pointer source
) const
2047 if (!elements
.size())
2048 fatal ("invalid callback = no when clause\n");
2050 if (chosenelement
>= 0)
2051 assert (source
== elements
[chosenelement
]);
2052 else if (otherwise
.getRaw())
2053 assert (source
== otherwise
);
2055 fatal ("esiChoose::checkValidSource: invalid callback - no elements chosen\n");
2059 esiChoose::fail(ESIElement
* source
, char const *anError
)
2061 checkValidSource (source
);
2062 elements
.setNULL (0, elements
.size());
2064 if (otherwise
.getRaw())
2065 otherwise
->finish();
2069 parent
->fail(this, anError
);
2075 esiChoose::provideData (ESISegment::Pointer data
, ESIElement
*source
)
2077 checkValidSource (source
);
2078 parent
->provideData (data
, this);
2081 esiChoose::esiChoose(esiChoose
const &old
) : chosenelement(-1), otherwise (NULL
), parent (NULL
)
2083 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2084 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeCacheable();
2086 if (newElement
.getRaw())
2087 assert (addElement(newElement
));
2092 esiChoose::makeCachableElements(esiChoose
const &old
)
2094 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2095 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeCacheable();
2097 if (newElement
.getRaw())
2098 assert (addElement(newElement
));
2103 esiChoose::makeUsableElements(esiChoose
const &old
, ESIVarState
&newVarState
)
2105 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2106 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeUsable (this, newVarState
);
2108 if (newElement
.getRaw())
2109 assert (addElement(newElement
));
2114 esiChoose::makeCacheable() const
2116 esiChoose
*resultC
= new esiChoose (*this);
2117 ESIElement::Pointer result
= resultC
;
2118 resultC
->makeCachableElements(*this);
2120 if (otherwise
.getRaw())
2121 resultC
->otherwise
= otherwise
->makeCacheable();
2127 esiChoose::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
2129 esiChoose
*resultC
= new esiChoose (*this);
2130 ESIElement::Pointer result
= resultC
;
2131 resultC
->parent
= newParent
;
2132 resultC
->makeUsableElements(*this, newVarState
);
2133 resultC
->selectElement();
2135 if (otherwise
.getRaw())
2136 resultC
->otherwise
= otherwise
->makeUsable(resultC
, newVarState
);
2142 ElementList::ElementList () : elements(NULL
), allocedcount(0), allocedsize(0), elementcount (0)
2145 ElementList::~ElementList()
2147 debugs(86, 5, "ElementList::~ElementList " << this);
2148 setNULL(0, elementcount
);
2151 memFreeBuf (allocedsize
, elements
);
2154 ESIElement::Pointer
&
2155 ElementList::operator [] (int index
)
2157 return elements
[index
];
2160 ESIElement::Pointer
const &
2161 ElementList::operator [] (int index
) const
2163 return elements
[index
];
2167 ElementList::pop_front (size_t const count
)
2172 memmove(elements
, &elements
[count
], (elementcount
- count
) * sizeof (ESIElement::Pointer
));
2174 elementcount
-= count
;
2178 ElementList::push_back(ESIElement::Pointer
&newElement
)
2180 elements
= (ESIElement::Pointer
*)memReallocBuf (elements
, ++elementcount
* sizeof (ESIElement::Pointer
),
2183 allocedcount
= elementcount
;
2184 memset(&elements
[elementcount
- 1], '\0', sizeof (ESIElement::Pointer
));
2185 elements
[elementcount
- 1] = newElement
;
2189 ElementList::size() const
2191 return elementcount
;
2195 esiWhen::esiWhen(esiTreeParentPtr aParent
, int attrcount
, const char **attr
,ESIVarState
*aVar
) :
2196 esiSequence(aParent
),
2198 unevaluatedExpression(NULL
),
2201 char const *expression
= NULL
;
2203 for (int loopCounter
= 0; loopCounter
< attrcount
&& attr
[loopCounter
]; loopCounter
+= 2) {
2204 if (!strcmp(attr
[loopCounter
],"test")) {
2206 debugs(86, 5, "esiWhen::esiWhen: Evaluating '" << attr
[loopCounter
+1] << "'");
2207 /* TODO: warn the user instead of asserting */
2208 assert (expression
== NULL
);
2209 expression
= attr
[loopCounter
+1];
2211 /* ignore mistyped attributes.
2212 * TODO:? error on these for user feedback - config parameter needed
2214 debugs(86, DBG_IMPORTANT
, "Found misttyped attribute on ESI When clause");
2218 /* No expression ? default is not matching */
2222 unevaluatedExpression
= xstrdup(expression
);
2224 varState
= cbdataReference (aVar
);
2231 safe_free (unevaluatedExpression
);
2234 cbdataReferenceDone (varState
);
2240 if (!unevaluatedExpression
)
2245 varState
->feedData(unevaluatedExpression
, strlen (unevaluatedExpression
));
2247 char const *expression
= varState
->extractChar ();
2249 setTestResult(ESIExpression::Evaluate (expression
));
2251 safe_free (expression
);
2254 esiWhen::esiWhen(esiWhen
const &old
) :
2257 unevaluatedExpression(NULL
),
2260 if (old
.unevaluatedExpression
)
2261 unevaluatedExpression
= xstrdup(old
.unevaluatedExpression
);
2265 esiWhen::makeCacheable() const
2267 return new esiWhen(*this);
2271 esiWhen::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
2273 esiWhen
*resultW
= new esiWhen (*this);
2274 ESIElement::Pointer result
= resultW
;
2275 resultW
->parent
= newParent
;
2276 resultW
->makeUsableElements(*this, newVarState
);
2277 resultW
->varState
= cbdataReference (&newVarState
);
2278 resultW
->evaluate();
2282 /* TODO: implement surrogate targeting and control processing */
2284 esiEnableProcessing (HttpReply
*rep
)
2288 if (rep
->surrogate_control
) {
2289 HttpHdrScTarget
*sctusable
=
2290 rep
->surrogate_control
->getMergedTarget(Config
.Accel
.surrogate_id
);
2292 // found something targeted at us
2294 sctusable
->hasContent() &&
2295 sctusable
->content().pos("ESI/1.0")) {
2305 #endif /* USE_SQUID_ESI == 1 */