2 * Copyright (C) 1996-2015 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 "HttpHdrSc.h"
34 #include "HttpHdrScTarget.h"
35 #include "HttpReply.h"
36 #include "HttpRequest.h"
37 #include "ip/Address.h"
39 #include "profiler/Profiler.h"
40 #include "SquidConfig.h"
42 /* quick reference on behaviour here.
43 * The ESI specification 1.0 requires the ESI processor to be able to
44 * return an error code at any point in the processing. To that end
45 * we buffer the incoming esi body until we know we will be able to
46 * satisfy the request. At that point we start streaming the queued
51 class ESIStreamContext
;
53 /* TODO: split this out into separate files ? */
54 /* Parsing: quick and dirty. ESI files are not valid XML, so a generic
55 * XML parser is not much use. Also we need a push parser not a pull
56 * parser, so LibXML is out.
58 * Interpreter methods:
59 * Render: May only ever be called after Process returns PROCESS_COMPLETE.
60 * Renders the resulting content into a ESISegment chain.
61 * Process: returns the status of the node.
62 * COMPLETE - processing is complete, rendering may staret
63 * PENDING_WONTFAIL - process is incomplete, but the element *will*
64 * be able to be rendered given time.
65 * PENDING_MAYFAIL - processing is incomplete, and the element *may*
66 * fail to be able to rendered.
67 * FAILED - processing failed, return an error to the client.
71 * NOT TODO: esi:inline - out of scope.
74 /* make comparisons with refcount pointers easy */
75 bool operator == (ESIElement
const *lhs
, ESIElement::Pointer
const &rhs
)
77 return lhs
== rhs
.getRaw();
80 typedef ESIContext::esiKick_t esiKick_t
;
82 /* some core operators */
84 class esiComment
: public ESIElement
86 MEMPROXY_CLASS(esiComment
);
91 Pointer
makeCacheable() const;
92 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
94 void render(ESISegment::Pointer
);
98 #include "esi/Literal.h"
100 #include "esi/Sequence.h"
102 #include "esi/Include.h"
106 class esiRemove
: public ESIElement
108 MEMPROXY_CLASS(esiRemove
);
111 esiRemove() : ESIElement() {}
112 virtual ~esiRemove() {}
114 virtual void render(ESISegment::Pointer
);
115 virtual bool addElement (ESIElement::Pointer
);
116 virtual Pointer
makeCacheable() const;
117 virtual Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
118 virtual void finish() {}
121 class esiTry
: public ESIElement
123 MEMPROXY_CLASS(esiTry
);
126 esiTry(esiTreeParentPtr aParent
);
129 void render(ESISegment::Pointer
);
130 bool addElement (ESIElement::Pointer
);
131 void fail(ESIElement
*, char const * = NULL
);
132 esiProcessResult_t
process (int dovars
);
133 void provideData (ESISegment::Pointer data
, ESIElement
* source
);
134 Pointer
makeCacheable() const;
135 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
137 ESIElement::Pointer attempt
;
138 ESIElement::Pointer except
;
141 int attemptok
:1; /* the attempt branch process correctly */
142 int exceptok
:1; /* likewise */
143 int attemptfailed
:1; /* The attempt branch failed */
144 int exceptfailed
:1; /* the except branch failed */
150 esiTreeParentPtr parent
;
151 ESISegment::Pointer exceptbuffer
;
152 esiTry (esiTry
const &);
153 esiProcessResult_t
bestAttemptRV() const;
158 class esiChoose
: public ESIElement
160 MEMPROXY_CLASS(esiChoose
);
163 esiChoose(esiTreeParentPtr
);
166 void render(ESISegment::Pointer
);
167 bool addElement (ESIElement::Pointer
);
168 void fail(ESIElement
*, char const * = NULL
);
169 esiProcessResult_t
process (int dovars
);
171 void provideData (ESISegment::Pointer data
, ESIElement
*source
);
172 void makeCachableElements(esiChoose
const &old
);
173 void makeUsableElements(esiChoose
const &old
, ESIVarState
&);
174 Pointer
makeCacheable() const;
175 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
178 ElementList elements
;
180 ESIElement::Pointer otherwise
;
184 esiChoose(esiChoose
const &);
185 esiTreeParentPtr parent
;
186 void checkValidSource (ESIElement::Pointer source
) const;
187 void selectElement();
190 class esiWhen
: public esiSequence
192 MEMPROXY_CLASS(esiWhen
);
195 esiWhen(esiTreeParentPtr aParent
, int attributes
, const char **attr
, ESIVarState
*);
197 Pointer
makeCacheable() const;
198 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
200 bool testsTrue() const { return testValue
;}
202 void setTestResult(bool aBool
) {testValue
= aBool
;}
205 esiWhen (esiWhen
const &);
207 char const *unevaluatedExpression
;
208 ESIVarState
*varState
;
212 struct esiOtherwise
: public esiSequence
{
213 esiOtherwise(esiTreeParentPtr aParent
) : esiSequence (aParent
) {}
216 CBDATA_CLASS_INIT(ESIContext
);
218 void ESIContext::startRead()
224 void ESIContext::finishRead()
230 bool ESIContext::reading() const
235 ESIStreamContext::ESIStreamContext() : finished(false), include (NULL
), localbuffer (new ESISegment
), buffer (NULL
)
238 /* Local functions */
240 static ESIContext
*ESIContextNew(HttpReply
*, clientStreamNode
*, ClientHttpRequest
*);
243 ESIContext::setError()
246 errorstatus
= Http::scInternalServerError
;
251 ESIContext::appendOutboundData(ESISegment::Pointer theData
)
253 if (!outbound
.getRaw()) {
255 outboundtail
= outbound
;
257 assert (outboundtail
->next
.getRaw() == NULL
);
258 outboundtail
->next
= theData
;
262 debugs(86, 9, "ESIContext::appendOutboundData: outbound " << outbound
.getRaw());
266 ESIContext::provideData (ESISegment::Pointer theData
, ESIElement
* source
)
268 debugs(86, 5, "ESIContext::provideData: " << this << " " << theData
.getRaw() << " " << source
);
269 /* No callbacks permitted after finish() called on the tree */
270 assert (tree
.getRaw());
271 assert (source
== tree
);
272 appendOutboundData(theData
);
280 ESIContext::fail (ESIElement
* source
, char const *anError
)
283 setErrorMessage (anError
);
289 ESIContext::fixupOutboundTail()
291 /* TODO: fixup thisNode outboundtail dross a little */
293 if (outboundtail
.getRaw())
294 outboundtail
= outboundtail
->tail();
301 debugs(86, 5, "esiKick: Re-entered whilst in progress");
302 // return ESI_KICK_INPROGRESS;
307 /* we've been detached from - we can't do anything more */
308 return ESI_KICK_FAILED
;
310 /* Something has occured. Process any remaining nodes */
312 /* Process some of our data */
313 switch (process ()) {
315 case ESI_PROCESS_COMPLETE
:
316 debugs(86, 5, "esiKick: esiProcess OK");
319 case ESI_PROCESS_PENDING_WONTFAIL
:
320 debugs(86, 5, "esiKick: esiProcess PENDING OK");
323 case ESI_PROCESS_PENDING_MAYFAIL
:
324 debugs(86, 5, "esiKick: esiProcess PENDING UNKNOWN");
327 case ESI_PROCESS_FAILED
:
328 debugs(86, 2, "esiKick: esiProcess " << this << " FAILED");
329 /* this can not happen - processing can't fail until we have data,
330 * and when we come here we have sent data to the client
338 return ESI_KICK_FAILED
;
341 /* Render if we can to get maximal sent data */
342 assert (tree
.getRaw() || flags
.error
);
344 if (!flags
.finished
&& !outbound
.getRaw()) {
345 outboundtail
= new ESISegment
;
346 outbound
= outboundtail
;
349 if (!flags
.error
&& !flags
.finished
)
350 tree
->render(outboundtail
);
355 /* Is there data to send? */
357 /* some data was sent. we're finished until the next read */
359 return ESI_KICK_SENT
;
363 /* nothing to send */
364 return flags
.error
? ESI_KICK_FAILED
: ESI_KICK_PENDING
;
367 /* request from downstream for more data
370 esiStreamRead (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
372 clientStreamNode
*next
;
373 /* Test preconditions */
374 assert (thisNode
!= NULL
);
375 assert (cbdataReferenceValid (thisNode
));
376 /* we are not in the chain until ESI is detected on a data callback */
377 assert (thisNode
->node
.prev
!= NULL
);
378 assert (thisNode
->node
.next
!= NULL
);
380 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
381 assert (context
.getRaw() != NULL
);
383 if (context
->flags
.passthrough
) {
384 /* passthru mode - read into supplied buffers */
385 next
= thisNode
->next();
386 clientStreamRead (thisNode
, http
, next
->readBuffer
);
390 context
->flags
.clientwantsdata
= 1;
391 debugs(86, 5, "esiStreamRead: Client now wants data");
393 /* Ok, not passing through */
395 switch (context
->kick ()) {
397 case ESIContext::ESI_KICK_FAILED
:
398 /* this can not happen - processing can't fail until we have data,
399 * and when we come here we have sent data to the client
402 case ESIContext::ESI_KICK_SENT
:
404 case ESIContext::ESI_KICK_INPROGRESS
:
407 case ESIContext::ESI_KICK_PENDING
:
411 /* Nothing to send */
413 if (context
->flags
.oktosend
&& (context
->flags
.finishedtemplate
414 || context
->cachedASTInUse
) &&
415 ! context
->flags
.finished
) {
416 /* we've started sending, finished reading, but not finished
417 * processing. stop here, a callback will resume the stream
420 debugs(86, 5, "esiStreamRead: Waiting for async resume of esi processing");
424 if (context
->flags
.oktosend
&& context
->flags
.finished
&& context
->outbound
.getRaw()) {
425 debugs(86, 5, "all processing complete, but outbound data still buffered");
426 assert (!context
->flags
.clientwantsdata
);
427 /* client MUST be processing the last reply */
431 if (context
->flags
.oktosend
&& context
->flags
.finished
) {
432 StoreIOBuffer tempBuffer
;
433 assert (!context
->outbound
.getRaw());
434 /* We've finished processing, and there is no more data buffered */
435 debugs(86, 5, "Telling recipient EOF on READ");
436 clientStreamCallback (thisNode
, http
, NULL
, tempBuffer
);
440 if (context
->reading())
443 /* no data that is ready to send, and still reading? well, lets get some */
444 /* secure a buffer */
445 if (!context
->incoming
.getRaw()) {
446 /* create a new buffer segment */
447 context
->buffered
= new ESISegment
;
448 context
->incoming
= context
->buffered
;
451 assert (context
->incoming
.getRaw() && context
->incoming
->len
!= HTTP_REQBUF_SZ
);
453 StoreIOBuffer tempBuffer
;
454 tempBuffer
.offset
= context
->readpos
;
455 tempBuffer
.length
= context
->incoming
->len
- HTTP_REQBUF_SZ
;
456 tempBuffer
.data
= &context
->incoming
->buf
[context
->incoming
->len
];
457 context
->startRead();
458 clientStreamRead (thisNode
, http
, tempBuffer
);
462 clientStream_status_t
463 esiStreamStatus (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
465 /* Test preconditions */
466 assert (thisNode
!= NULL
);
467 assert (cbdataReferenceValid (thisNode
));
468 /* we are not in the chain until ESI is detected on a data callback */
469 assert (thisNode
->node
.prev
!= NULL
);
470 assert (thisNode
->node
.next
!= NULL
);
472 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
473 assert (context
.getRaw() != NULL
);
475 if (context
->flags
.passthrough
)
476 return clientStreamStatus (thisNode
, http
);
478 if (context
->flags
.oktosend
&& context
->flags
.finished
&&
479 !(context
->outbound
.getRaw() && context
->outbound_offset
< context
->outbound
->len
)) {
480 debugs(86, 5, "Telling recipient EOF on STATUS");
481 return STREAM_UNPLANNED_COMPLETE
; /* we don't know lengths in advance */
484 /* ?? RC: we can't be aborted / fail ? */
489 esiAlwaysPassthrough(Http::StatusCode sline
)
495 case Http::scContinue
: /* Should never reach us... but squid needs to alter to accomodate this */
497 case Http::scSwitchingProtocols
: /* Ditto */
499 case Http::scProcessing
: /* Unknown - some extension */
501 case Http::scNoContent
: /* no body, no esi */
503 case Http::scNotModified
: /* ESI does not affect assembled page headers, so 304s are valid */
516 ESIContext::trimBlanks()
518 /* trim leading empty buffers ? */
520 while (outbound
.getRaw() && outbound
->next
.getRaw() && !outbound
->len
) {
521 debugs(86, 5, "ESIContext::trimBlanks: " << this <<
522 " skipping segment " << outbound
.getRaw());
523 outbound
= outbound
->next
;
526 if (outboundtail
.getRaw())
527 assert (outbound
.getRaw());
530 /* Send data downstream
531 * Returns 0 if nothing was sent. Non-zero if data was sent.
536 debugs(86, 5, "ESIContext::send: this=" << this);
537 /* send any processed data */
541 if (!flags
.clientwantsdata
) {
542 debugs(86, 5, "ESIContext::send: Client does not want data - not sending anything");
546 if (tree
.getRaw() && tree
->mayFail()) {
547 debugs(86, 5, "ESIContext::send: Tree may fail. Not sending.");
554 if (!flags
.oktosend
) {
556 fatal("ESIContext::send: Not OK to send.\n");
562 if (!(rep
|| (outbound
.getRaw() &&
563 outbound
->len
&& (outbound_offset
<= outbound
->len
)))) {
564 debugs(86, 5, "ESIContext::send: Nothing to send.");
568 debugs(86, 5, "ESIContext::send: Sending something...");
569 /* Yes! Send it without asking for more upstream */
570 /* memcopying because the client provided the buffer */
571 /* TODO: skip data until pos == next->readoff; */
572 assert (thisNode
->data
== this);
573 clientStreamNode
*next
= thisNode
->next();
574 ESIContext
*templock
= cbdataReference (this);
577 if (outbound
.getRaw())
578 len
= min (next
->readBuffer
.length
, outbound
->len
- outbound_offset
);
580 /* prevent corruption on range requests, even though we don't support them yet */
581 assert (pos
== next
->readBuffer
.offset
);
583 /* We must send data or a reply */
584 assert (len
!= 0 || rep
!= NULL
);
587 memcpy(next
->readBuffer
.data
, &outbound
->buf
[outbound_offset
], len
);
589 if (len
+ outbound_offset
== outbound
->len
) {
590 ESISegment::Pointer temp
= outbound
->next
;
591 /* remove the used buffer */
598 if (!outbound
.getRaw())
604 flags
.clientwantsdata
= 0;
605 debugs(86, 5, "ESIContext::send: this=" << this << " Client no longer wants data ");
606 /* Deal with re-entrancy */
607 HttpReply
*temprep
= rep
;
608 rep
= NULL
; /* freed downstream */
610 if (temprep
&& varState
)
611 varState
->buildVary (temprep
);
614 StoreIOBuffer tempBuffer
;
615 tempBuffer
.length
= len
;
616 tempBuffer
.offset
= pos
- len
;
617 tempBuffer
.data
= next
->readBuffer
.data
;
618 clientStreamCallback (thisNode
, http
, temprep
, tempBuffer
);
622 len
= 1; /* tell the caller we sent something (because we sent headers */
624 cbdataReferenceDone (templock
);
626 debugs (86,5,"ESIContext::send: this=" << this << " sent " << len
);
632 ESIContext::finishChildren()
640 /* Detach event from a client Stream */
642 esiStreamDetach (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
644 /* if we have pending callbacks, tell them we're done. */
645 /* test preconditions */
646 assert (thisNode
!= NULL
);
647 assert (cbdataReferenceValid (thisNode
));
648 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
649 assert (context
.getRaw() != NULL
);
650 /* detach from the stream */
651 clientStreamDetach (thisNode
,http
);
652 /* if we have pending callbacks (from subincludes), tell them we're done. */
653 context
->thisNode
= NULL
;
654 context
->flags
.detached
= 1;
655 context
->finishChildren();
656 /* HACK for parser stack not being emptied */
657 context
->parserState
.stack
[0] = NULL
;
658 /* allow refcount logic to trigger */
659 context
->cbdataLocker
= NULL
;
662 /* Process incoming data for ESI tags */
663 /* ESI TODO: Long term: we should have a framework to parse html/xml and
664 * callback to a set of processors like thisNode, to prevent multiple parsing
665 * overhead. More thoughts on thisNode: We have to parse multiple times, because
666 * the output of one processor may create a very different tree. What we could
667 * do is something like DOM and pass that down to a final renderer. This is
668 * getting into web server territory though...
671 * This is not the last node in the stream.
672 * ESI processing has been enabled.
673 * There is context data or a reply structure
676 esiProcessStream (clientStreamNode
*thisNode
, ClientHttpRequest
*http
, HttpReply
*rep
, StoreIOBuffer receivedData
)
678 /* test preconditions */
679 assert (thisNode
!= NULL
);
680 /* ESI TODO: handle thisNode rather than asserting - it should only ever
681 * happen if we cause an abort and the callback chain
682 * loops back to here, so we can simply return. However, that itself
683 * shouldn't happen, so it stays as an assert for now. */
684 assert (cbdataReferenceValid (thisNode
));
686 * if data is NULL thisNode is the first entrance. If rep is also NULL,
687 * something is wrong.
689 assert (thisNode
->data
.getRaw() != NULL
|| rep
);
690 assert (thisNode
->node
.next
!= NULL
);
692 if (!thisNode
->data
.getRaw())
693 /* setup ESI context from reply headers */
694 thisNode
->data
= ESIContextNew(rep
, thisNode
, http
);
696 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
698 assert (context
.getRaw() != NULL
);
700 context
->finishRead();
702 /* Skipping all ESI processing. All remaining data gets untouched.
703 * Mainly used when an error or other non-ESI processable entity
704 * has been detected to prevent ESI processing the error body
706 if (context
->flags
.passthrough
) {
707 clientStreamCallback (thisNode
, http
, rep
, receivedData
);
711 debugs(86, 3, "esiProcessStream: Processing thisNode " << thisNode
<<
712 " context " << context
.getRaw() << " offset " <<
713 (int) receivedData
.offset
<< " length " <<
714 (unsigned int)receivedData
.length
);
716 /* once we finish the template, we *cannot* return here */
717 assert (!context
->flags
.finishedtemplate
);
718 assert (!context
->cachedASTInUse
);
720 /* Can we generate any data ?*/
722 if (receivedData
.data
) {
723 /* Increase our buffer area with incoming data */
724 assert (receivedData
.length
<= HTTP_REQBUF_SZ
);
725 assert (thisNode
->readBuffer
.offset
== receivedData
.offset
);
726 debugs (86,5, "esiProcessStream found " << receivedData
.length
<< " bytes of body data at offset " << receivedData
.offset
);
727 /* secure the data for later use */
729 if (!context
->incoming
.getRaw()) {
730 /* create a new buffer segment */
731 debugs(86, 5, "esiProcessStream: Setting up incoming buffer");
732 context
->buffered
= new ESISegment
;
733 context
->incoming
= context
->buffered
;
736 if (receivedData
.data
!= &context
->incoming
->buf
[context
->incoming
->len
]) {
737 /* We have to copy the data out because we didn't supply thisNode buffer */
738 size_t space
= HTTP_REQBUF_SZ
- context
->incoming
->len
;
739 size_t len
= min (space
, receivedData
.length
);
740 debugs(86, 5, "Copying data from " << receivedData
.data
<< " to " <<
741 &context
->incoming
->buf
[context
->incoming
->len
] <<
742 " because our buffer was not used");
744 memcpy(&context
->incoming
->buf
[context
->incoming
->len
], receivedData
.data
, len
);
745 context
->incoming
->len
+= len
;
747 if (context
->incoming
->len
== HTTP_REQBUF_SZ
) {
748 /* append another buffer */
749 context
->incoming
->next
= new ESISegment
;
750 context
->incoming
= context
->incoming
->next
;
753 if (len
!= receivedData
.length
) {
754 /* capture the remnants */
755 memcpy(context
->incoming
->buf
, &receivedData
.data
[len
], receivedData
.length
- len
);
756 context
->incoming
->len
= receivedData
.length
- len
;
759 /* and note where we are up to */
760 context
->readpos
+= receivedData
.length
;
762 /* update our position counters, and if needed assign a new buffer */
763 context
->incoming
->len
+= receivedData
.length
;
764 assert (context
->incoming
->len
<= HTTP_REQBUF_SZ
);
766 if (context
->incoming
->len
> HTTP_REQBUF_SZ
* 3 / 4) {
767 /* allocate a new buffer - to stop us asking for ridiculously small amounts */
768 context
->incoming
->next
= new ESISegment
;
769 context
->incoming
= context
->incoming
->next
;
772 context
->readpos
+= receivedData
.length
;
776 /* EOF / Read error / aborted entry */
777 if (rep
== NULL
&& receivedData
.data
== NULL
&& receivedData
.length
== 0 && !context
->flags
.finishedtemplate
) {
778 /* TODO: get stream status to test the entry for aborts */
779 /* else flush the esi processor */
780 debugs(86, 5, "esiProcess: " << context
.getRaw() << " Finished reading upstream data");
781 /* This is correct */
782 context
->flags
.finishedtemplate
= 1;
785 switch (context
->kick()) {
787 case ESIContext::ESI_KICK_FAILED
:
788 /* thisNode can not happen - processing can't fail until we have data,
789 * and when we come here we have sent data to the client
793 case ESIContext::ESI_KICK_SENT
:
795 case ESIContext::ESI_KICK_INPROGRESS
:
798 case ESIContext::ESI_KICK_PENDING
:
802 /* ok.. no data sent, try to pull more data in from upstream.
803 * FIXME: Don't try thisNode if we have finished reading the template
805 if (!context
->flags
.finishedtemplate
&& !context
->reading()
806 && !context
->cachedASTInUse
) {
807 StoreIOBuffer tempBuffer
;
808 assert (context
->incoming
.getRaw() && context
->incoming
->len
< HTTP_REQBUF_SZ
);
809 tempBuffer
.offset
= context
->readpos
;
810 tempBuffer
.length
= HTTP_REQBUF_SZ
- context
->incoming
->len
;
811 tempBuffer
.data
= &context
->incoming
->buf
[context
->incoming
->len
];
812 context
->startRead();
813 clientStreamRead (thisNode
, http
, tempBuffer
);
817 debugs(86, 3, "esiProcessStream: no data to send, no data to read, awaiting a callback");
820 ESIContext::~ESIContext()
823 /* Not freed by freeresources because esi::fail needs it */
824 safe_free (errormessage
);
825 debugs(86, 3, "ESIContext::~ESIContext: Freed " << this);
829 ESIContextNew (HttpReply
*rep
, clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
832 ESIContext
*rv
= new ESIContext
;
834 rv
->cbdataLocker
= rv
;
836 if (esiAlwaysPassthrough(rep
->sline
.status())) {
837 rv
->flags
.passthrough
= 1;
839 /* remove specific headers for ESI to prevent
840 * downstream cache confusion */
841 HttpHeader
*hdr
= &rep
->header
;
842 hdr
->delById(Http::HdrType::ACCEPT_RANGES
);
843 hdr
->delById(Http::HdrType::ETAG
);
844 hdr
->delById(Http::HdrType::CONTENT_LENGTH
);
845 hdr
->delById(Http::HdrType::CONTENT_MD5
);
846 rv
->tree
= new esiSequence (rv
, true);
847 rv
->thisNode
= thisNode
;
849 rv
->flags
.clientwantsdata
= 1;
850 rv
->varState
= new ESIVarState (&http
->request
->header
, http
->uri
);
851 debugs(86, 5, "ESIContextNew: Client wants data (always created during reply cycle");
854 debugs(86, 5, "ESIContextNew: Create context " << rv
);
858 ESIElement::ESIElementType_t
859 ESIElement::IdentifyElement (const char *el
)
865 return ESI_ELEMENT_NONE
;
867 if (!strncmp (el
, "esi:", 4))
869 else if (!strncmp (el
, "http://www.edge-delivery.org/esi/1.0|", 37))
872 return ESI_ELEMENT_NONE
;
874 if (!strncmp (el
+ offset
, "otherwise", 9))
875 return ESI_ELEMENT_OTHERWISE
;
877 if (!strncmp (el
+ offset
, "comment", 7))
878 return ESI_ELEMENT_COMMENT
;
880 if (!strncmp (el
+ offset
, "include", 7))
881 return ESI_ELEMENT_INCLUDE
;
883 if (!strncmp (el
+ offset
, "attempt", 7))
884 return ESI_ELEMENT_ATTEMPT
;
886 if (!strncmp (el
+ offset
, "assign", 6))
887 return ESI_ELEMENT_ASSIGN
;
889 if (!strncmp (el
+ offset
, "remove", 6))
890 return ESI_ELEMENT_REMOVE
;
892 if (!strncmp (el
+ offset
, "except", 6))
893 return ESI_ELEMENT_EXCEPT
;
895 if (!strncmp (el
+ offset
, "choose", 6))
896 return ESI_ELEMENT_CHOOSE
;
898 if (!strncmp (el
+ offset
, "vars", 4))
899 return ESI_ELEMENT_VARS
;
901 if (!strncmp (el
+ offset
, "when", 4))
902 return ESI_ELEMENT_WHEN
;
904 if (!strncmp (el
+ offset
, "try", 3))
905 return ESI_ELEMENT_TRY
;
907 return ESI_ELEMENT_NONE
;
911 ESIContext::ParserState::top()
913 return stack
[stackdepth
-1];
916 ESIContext::ParserState::ParserState() :
923 ESIContext::ParserState::inited() const
929 ESIContext::addStackElement (ESIElement::Pointer element
)
931 /* Put on the stack to allow skipping of 'invalid' markup */
932 assert (parserState
.stackdepth
<11);
934 debugs(86, 5, "ESIContext::addStackElement: About to add ESI Node " << element
.getRaw());
936 if (!parserState
.top()->addElement(element
)) {
937 debugs(86, DBG_IMPORTANT
, "ESIContext::addStackElement: failed to add esi node, probable error in ESI template");
940 /* added ok, push onto the stack */
941 parserState
.stack
[parserState
.stackdepth
] = element
;
942 ++parserState
.stackdepth
;
947 ESIContext::start(const char *el
, const char **attr
, size_t attrCount
)
950 unsigned int ellen
= strlen (el
);
951 char localbuf
[HTTP_REQBUF_SZ
];
952 ESIElement::Pointer element
;
953 int specifiedattcount
= attrCount
* 2;
955 assert (ellen
< sizeof (localbuf
)); /* prevent unexpected overruns. */
957 debugs(86, 5, "ESIContext::Start: element '" << el
<< "' with " << specifiedattcount
<< " tags");
960 /* waiting for expat to finish the buffer we gave it */
963 switch (ESIElement::IdentifyElement (el
)) {
965 case ESIElement::ESI_ELEMENT_NONE
:
966 /* Spit out elements we aren't interested in */
969 assert (xstrncpy (&localbuf
[1], el
, sizeof(localbuf
) - 2));
970 position
= localbuf
+ strlen (localbuf
);
972 for (i
= 0; i
< specifiedattcount
&& attr
[i
]; i
+= 2) {
975 /* TODO: handle thisNode gracefully */
976 assert (xstrncpy (position
, attr
[i
], sizeof(localbuf
) + (position
- localbuf
)));
977 position
+= strlen (position
);
982 const char *chPtr
= attr
[i
+ 1];
984 while ((ch
= *chPtr
++) != '\0') {
986 assert( xstrncpy(position
, """, sizeof(localbuf
) + (position
-localbuf
)) );
993 position
+= strlen (position
);
1002 addLiteral (localbuf
, position
- localbuf
);
1003 debugs(86, 5, "esi stack depth " << parserState
.stackdepth
);
1007 case ESIElement::ESI_ELEMENT_COMMENT
:
1008 /* Put on the stack to allow skipping of 'invalid' markup */
1009 element
= new esiComment ();
1012 case ESIElement::ESI_ELEMENT_INCLUDE
:
1013 /* Put on the stack to allow skipping of 'invalid' markup */
1014 element
= new ESIInclude (parserState
.top().getRaw(), specifiedattcount
, attr
, this);
1017 case ESIElement::ESI_ELEMENT_REMOVE
:
1018 /* Put on the stack to allow skipping of 'invalid' markup */
1019 element
= new esiRemove();
1022 case ESIElement::ESI_ELEMENT_TRY
:
1023 /* Put on the stack to allow skipping of 'invalid' markup */
1024 element
= new esiTry (parserState
.top().getRaw());
1027 case ESIElement::ESI_ELEMENT_ATTEMPT
:
1028 /* Put on the stack to allow skipping of 'invalid' markup */
1029 element
= new esiAttempt (parserState
.top().getRaw());
1032 case ESIElement::ESI_ELEMENT_EXCEPT
:
1033 /* Put on the stack to allow skipping of 'invalid' markup */
1034 element
= new esiExcept (parserState
.top().getRaw());
1037 case ESIElement::ESI_ELEMENT_VARS
:
1038 /* Put on the stack to allow skipping of 'invalid' markup */
1039 element
= new ESIVar (parserState
.top().getRaw());
1042 case ESIElement::ESI_ELEMENT_CHOOSE
:
1043 /* Put on the stack to allow skipping of 'invalid' markup */
1044 element
= new esiChoose (parserState
.top().getRaw());
1047 case ESIElement::ESI_ELEMENT_WHEN
:
1048 /* Put on the stack to allow skipping of 'invalid' markup */
1049 element
= new esiWhen (parserState
.top().getRaw(), specifiedattcount
, attr
, varState
);
1052 case ESIElement::ESI_ELEMENT_OTHERWISE
:
1053 /* Put on the stack to allow skipping of 'invalid' markup */
1054 element
= new esiOtherwise (parserState
.top().getRaw());
1057 case ESIElement::ESI_ELEMENT_ASSIGN
:
1058 /* Put on the stack to allow skipping of 'invalid' markup */
1059 element
= new ESIAssign (parserState
.top().getRaw(), specifiedattcount
, attr
, this);
1063 addStackElement(element
);
1065 debugs(86, 5, "esi stack depth " << parserState
.stackdepth
);
1067 } /* End of start handler */
1070 ESIContext::end(const char *el
)
1072 unsigned int ellen
= strlen (el
);
1073 char localbuf
[HTTP_REQBUF_SZ
];
1077 /* waiting for expat to finish the buffer we gave it */
1080 switch (ESIElement::IdentifyElement (el
)) {
1082 case ESIElement::ESI_ELEMENT_NONE
:
1083 assert (ellen
< sizeof (localbuf
)); /* prevent unexpected overruns. */
1084 /* Add elements we aren't interested in */
1087 assert (xstrncpy (&localbuf
[2], el
, sizeof(localbuf
) - 3));
1088 position
= localbuf
+ strlen (localbuf
);
1092 addLiteral (localbuf
, position
- localbuf
);
1095 case ESIElement::ESI_ELEMENT_COMMENT
:
1097 case ESIElement::ESI_ELEMENT_INCLUDE
:
1099 case ESIElement::ESI_ELEMENT_REMOVE
:
1101 case ESIElement::ESI_ELEMENT_TRY
:
1103 case ESIElement::ESI_ELEMENT_ATTEMPT
:
1105 case ESIElement::ESI_ELEMENT_EXCEPT
:
1107 case ESIElement::ESI_ELEMENT_VARS
:
1109 case ESIElement::ESI_ELEMENT_CHOOSE
:
1111 case ESIElement::ESI_ELEMENT_WHEN
:
1113 case ESIElement::ESI_ELEMENT_OTHERWISE
:
1115 case ESIElement::ESI_ELEMENT_ASSIGN
:
1116 /* pop of the stack */
1117 parserState
.stack
[--parserState
.stackdepth
] = NULL
;
1120 } /* End of end handler */
1123 ESIContext::parserDefault (const char *s
, int len
)
1128 /* handle any skipped data */
1129 addLiteral (s
, len
);
1133 ESIContext::parserComment (const char *s
)
1138 if (!strncmp(s
, "esi",3)) {
1139 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block encountered");
1140 ESIParser::Pointer tempParser
= ESIParser::NewParser (this);
1142 /* wrap the comment in some tags */
1144 if (!tempParser
->parse("<div>", 5,0) ||
1145 !tempParser
->parse(s
+ 3, strlen(s
) - 3, 0) ||
1146 !tempParser
->parse("</div>",6,1)) {
1147 debugs(86, DBG_CRITICAL
, "ESIContext::parserComment: Parsing fragment '" << s
+ 3 << "' failed.");
1150 snprintf(tempstr
, 1023, "ESIContext::parserComment: Parse error at line %ld:\n%s\n",
1151 tempParser
->lineNumber(),
1152 tempParser
->errorString());
1153 debugs(86, DBG_CRITICAL
, "" << tempstr
<< "");
1155 setErrorMessage(tempstr
);
1158 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block parsed");
1161 char localbuf
[HTTP_REQBUF_SZ
];
1163 debugs(86, 5, "ESIContext::parserComment: Regenerating comment block");
1166 if (len
> sizeof (localbuf
) - 9) {
1167 debugs(86, DBG_CRITICAL
, "ESIContext::parserComment: Truncating long comment");
1168 len
= sizeof (localbuf
) - 9;
1171 xstrncpy(localbuf
, "<!--", 5);
1172 xstrncpy(localbuf
+ 4, s
, len
+ 1);
1173 xstrncpy(localbuf
+ 4 + len
, "-->", 4);
1174 addLiteral (localbuf
,len
+ 7);
1179 ESIContext::addLiteral (const char *s
, int len
)
1181 /* handle any skipped data */
1183 debugs(86, 5, "literal length is " << len
);
1184 /* give a literal to the current element */
1185 assert (parserState
.stackdepth
<11);
1186 ESIElement::Pointer
element (new esiLiteral (this, s
, len
));
1188 if (!parserState
.top()->addElement(element
)) {
1189 debugs(86, DBG_IMPORTANT
, "ESIContext::addLiteral: failed to add esi node, 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() == NULL
&& 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 */
1251 PROF_start(esiParsing
);
1253 while (buffered
.getRaw() && !flags
.error
)
1256 PROF_stop(esiParsing
);
1258 /* Tel the read code to allocate a new buffer */
1261 parserState
.parsing
= 0;
1266 ESIContext::process ()
1269 * read through buffered, skipping plain text, and skipping any
1270 * <...> entry that is not an <esi: entry.
1271 * when it's found, hand an esiLiteral of the preceding data to our current
1275 if (parserState
.parsing
) {
1276 /* in middle of parsing - finish here */
1277 return ESI_PROCESS_PENDING_MAYFAIL
;
1280 assert (flags
.finished
== 0);
1282 assert (!flags
.error
);
1284 if (!hasCachedAST())
1286 else if (!flags
.finishedtemplate
)
1290 debugs(86, 5, "ESIContext::process: Parsing failed");
1292 parserState
.popAll();
1293 return ESI_PROCESS_FAILED
;
1296 if (!flags
.finishedtemplate
&& !incoming
.getRaw() && !cachedASTInUse
) {
1297 buffered
= new ESISegment
;
1298 incoming
= buffered
;
1301 if (!flags
.finishedtemplate
&& !cachedASTInUse
) {
1302 return ESI_PROCESS_PENDING_MAYFAIL
;
1305 assert (flags
.finishedtemplate
|| cachedASTInUse
);
1307 /* ok, we've done all we can with the data. What can we process now?
1310 esiProcessResult_t status
;
1311 PROF_start(esiProcessing
);
1313 status
= tree
->process(0);
1318 case ESI_PROCESS_COMPLETE
:
1319 debugs(86, 5, "esiProcess: tree Processed OK");
1322 case ESI_PROCESS_PENDING_WONTFAIL
:
1323 debugs(86, 5, "esiProcess: tree Processed PENDING OK");
1326 case ESI_PROCESS_PENDING_MAYFAIL
:
1327 debugs(86, 5, "esiProcess: tree Processed PENDING UNKNOWN");
1330 case ESI_PROCESS_FAILED
:
1331 debugs(86, DBG_CRITICAL
, "esiProcess: tree Processed FAILED");
1334 setErrorMessage("esiProcess: ESI template Processing failed.");
1336 PROF_stop(esiProcessing
);
1338 return ESI_PROCESS_FAILED
;
1343 if (status
!= ESI_PROCESS_PENDING_MAYFAIL
&& (flags
.finishedtemplate
|| cachedASTInUse
)) {
1344 /* We've read the entire template, and no nodes will
1347 debugs(86, 5, "esiProcess, request will succeed");
1351 if (status
== ESI_PROCESS_COMPLETE
1352 && (flags
.finishedtemplate
|| cachedASTInUse
)) {
1353 /* we've finished all processing. Render and send. */
1354 debugs(86, 5, "esiProcess, processing complete");
1358 PROF_stop(esiProcessing
);
1359 return status
; /* because we have no callbacks */
1364 ESIContext::ParserState::freeResources()
1371 ESIContext::ParserState::popAll()
1374 stack
[--stackdepth
] = NULL
;
1378 ESIContext::freeResources ()
1380 debugs(86, 5, HERE
<< "Freeing for this=" << this);
1386 if (parserState
.inited()) {
1387 parserState
.freeResources();
1390 parserState
.popAll();
1391 ESISegmentFreeList (buffered
);
1392 ESISegmentFreeList (outbound
);
1393 ESISegmentFreeList (outboundtail
);
1396 /* don't touch incoming, it's a pointer into buffered anyway */
1399 ErrorState
*clientBuildError (err_type
, Http::StatusCode
, char const *, Ip::Address
&, HttpRequest
*);
1401 /* This can ONLY be used before we have sent *any* data to the client */
1405 debugs(86, 5, "ESIContext::fail: this=" << this);
1406 /* check preconditions */
1408 /* cleanup current state */
1410 /* Stop altering thisNode request */
1413 /* don't honour range requests - for errors we send it all */
1415 /* create an error object */
1416 // XXX: with the in-direction on remote IP. does the http->getConn()->clientConnection exist?
1417 ErrorState
* err
= clientBuildError(errorpage
, errorstatus
, NULL
, http
->getConn()->clientConnection
->remote
, http
->request
);
1418 err
->err_msg
= errormessage
;
1419 errormessage
= NULL
;
1420 rep
= err
->BuildHttpReply();
1421 assert (rep
->body
.hasContent());
1422 size_t errorprogress
= rep
->body
.contentSize();
1423 /* Tell esiSend where to start sending from */
1424 outbound_offset
= 0;
1425 /* copy the membuf from the reply to outbound */
1427 while (errorprogress
< (size_t)rep
->body
.contentSize()) {
1428 appendOutboundData(new ESISegment
);
1429 errorprogress
+= outboundtail
->append(rep
->body
.content() + errorprogress
, rep
->body
.contentSize() - errorprogress
);
1432 /* the esiCode now thinks that the error is the outbound,
1433 * and all processing has finished. */
1434 /* Send as much as we can */
1437 /* don't cancel anything. The stream nodes will clean up after
1438 * themselves when the reply is freed - and we don't know what to
1443 /* Implementation of ESIElements */
1446 esiComment::~esiComment()
1448 debugs(86, 5, "esiComment::~esiComment " << this);
1451 esiComment::esiComment()
1455 esiComment::finish()
1459 esiComment::render(ESISegment::Pointer output
)
1461 /* Comments do nothing dude */
1462 debugs(86, 5, "esiCommentRender: Rendering comment " << this << " into " << output
.getRaw());
1466 esiComment::makeCacheable() const
1468 debugs(86, 5, "esiComment::makeCacheable: returning NULL");
1473 esiComment::makeUsable(esiTreeParentPtr
, ESIVarState
&) const
1475 fatal ("esiComment::Usable: unreachable code!\n");
1480 esiLiteral::~esiLiteral()
1482 debugs(86, 5, "esiLiteral::~esiLiteral: " << this);
1483 ESISegmentFreeList (buffer
);
1484 cbdataReferenceDone (varState
);
1487 esiLiteral::esiLiteral(ESISegment::Pointer aSegment
) :
1496 esiLiteral::finish()
1499 /* precondition: the buffer chain has at least start + length bytes of data
1501 esiLiteral::esiLiteral(ESIContext
*context
, const char *s
, int numberOfCharacters
)
1505 buffer
= new ESISegment
;
1506 ESISegment::Pointer local
= buffer
;
1508 int remainingCharacters
= numberOfCharacters
;
1510 while (remainingCharacters
> 0) {
1511 if (local
->len
== sizeof (local
->buf
)) {
1512 local
->next
= new ESISegment
;
1516 size_t len
= local
->append (&s
[start
], remainingCharacters
);
1518 remainingCharacters
-= len
;
1521 varState
= cbdataReference(context
->varState
);
1525 esiLiteral::render (ESISegment::Pointer output
)
1527 debugs(86, 9, "esiLiteral::render: Rendering " << this);
1528 /* append the entire chain */
1529 assert (output
->next
.getRaw() == NULL
);
1530 output
->next
= buffer
;
1535 esiLiteral::process (int dovars
)
1538 return ESI_PROCESS_COMPLETE
;
1541 ESISegment::Pointer temp
= buffer
;
1542 /* Ensure variable state is clean */
1544 while (temp
.getRaw()) {
1545 varState
->feedData(temp
->buf
,temp
->len
);
1549 /* free the pre-processed content */
1550 ESISegmentFreeList (buffer
);
1552 buffer
= varState
->extractList ();
1556 return ESI_PROCESS_COMPLETE
;
1559 esiLiteral::esiLiteral(esiLiteral
const &old
) : buffer (old
.buffer
->cloneList()),
1566 esiLiteral::makeCacheable() const
1568 return new esiLiteral (*this);
1572 esiLiteral::makeUsable(esiTreeParentPtr
, ESIVarState
&newVarState
) const
1574 debugs(86, 5, "esiLiteral::makeUsable: Creating usable literal");
1575 esiLiteral
* result
= new esiLiteral (*this);
1576 result
->varState
= cbdataReference (&newVarState
);
1582 esiRemove::render(ESISegment::Pointer output
)
1584 /* Removes do nothing dude */
1585 debugs(86, 5, "esiRemoveRender: Rendering remove " << this);
1588 /* Accept non-ESI children */
1590 esiRemove::addElement (ESIElement::Pointer element
)
1592 if (!dynamic_cast<esiLiteral
*>(element
.getRaw())) {
1593 debugs(86, 5, "esiRemoveAdd: Failed for " << this);
1601 esiRemove::makeCacheable() const
1603 debugs(86, 5, "esiRemove::makeCacheable: Returning NULL");
1608 esiRemove::makeUsable(esiTreeParentPtr
, ESIVarState
&) const
1610 fatal ("esiRemove::Usable: unreachable code!\n");
1617 debugs(86, 5, "esiTry::~esiTry " << this);
1620 esiTry::esiTry(esiTreeParentPtr aParent
) :
1624 memset(&flags
, 0, sizeof(flags
));
1628 esiTry::render(ESISegment::Pointer output
)
1630 /* Try renders from it's children */
1631 assert (attempt
.getRaw());
1632 assert (except
.getRaw());
1633 debugs(86, 5, "esiTryRender: Rendering Try " << this);
1635 if (flags
.attemptok
) {
1636 attempt
->render(output
);
1637 } else if (flags
.exceptok
) {
1640 if (exceptbuffer
.getRaw())
1641 ESISegment::ListTransfer(exceptbuffer
, output
);
1643 except
->render(output
);
1645 debugs(86, 5, "esiTryRender: Neither except nor attempt succeeded?!?");
1648 /* Accept attempt and except only */
1650 esiTry::addElement(ESIElement::Pointer element
)
1652 debugs(86, 5, "esiTryAdd: Try " << this << " adding element " <<
1655 if (dynamic_cast<esiLiteral
*>(element
.getRaw())) {
1656 /* Swallow whitespace */
1657 debugs(86, 5, "esiTryAdd: Try " << this << " skipping whitespace " << element
.getRaw());
1661 if (dynamic_cast<esiAttempt
*>(element
.getRaw())) {
1662 if (attempt
.getRaw()) {
1663 debugs(86, DBG_IMPORTANT
, "esiTryAdd: Failed for " << this << " - try allready has an attempt node (section 3.4)");
1671 if (dynamic_cast<esiExcept
*>(element
.getRaw())) {
1672 if (except
.getRaw()) {
1673 debugs(86, DBG_IMPORTANT
, "esiTryAdd: Failed for " << this << " - try already has an except node (section 3.4)");
1681 debugs(86, DBG_IMPORTANT
, "esiTryAdd: Failed to add element " << element
.getRaw() << " to try " << this << ", incorrect element type (see section 3.4)");
1686 esiTry::bestAttemptRV() const
1688 if (flags
.attemptfailed
)
1689 return ESI_PROCESS_COMPLETE
;
1691 return ESI_PROCESS_PENDING_MAYFAIL
;
1695 esiTry::process (int dovars
)
1697 esiProcessResult_t rv
= ESI_PROCESS_PENDING_MAYFAIL
;
1699 if (!attempt
.getRaw()) {
1700 debugs(86, DBG_CRITICAL
, "esiTryProcess: Try has no attempt element - ESI template is invalid (section 3.4)");
1701 return ESI_PROCESS_FAILED
;
1704 if (!except
.getRaw()) {
1705 debugs(86, DBG_CRITICAL
, "esiTryProcess: Try has no except element - ESI template is invalid (section 3.4)");
1706 return ESI_PROCESS_FAILED
;
1709 if (!flags
.attemptfailed
)
1710 /* Try the attempt branch */
1711 switch ((rv
= attempt
->process(dovars
))) {
1713 case ESI_PROCESS_COMPLETE
:
1714 debugs(86, 5, "esiTryProcess: attempt Processed OK");
1715 flags
.attemptok
= 1;
1716 return ESI_PROCESS_COMPLETE
;
1718 case ESI_PROCESS_PENDING_WONTFAIL
:
1719 debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
1720 /* We're not done yet, but don't need to test except */
1721 return ESI_PROCESS_PENDING_WONTFAIL
;
1723 case ESI_PROCESS_PENDING_MAYFAIL
:
1724 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
1727 case ESI_PROCESS_FAILED
:
1728 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
1729 flags
.attemptfailed
= 1;
1733 /* attempt is either MAYFAIL or FAILED */
1735 return bestAttemptRV();
1737 /* query except to see if it has a definite result */
1738 if (!flags
.exceptfailed
)
1739 /* Try the except branch */
1740 switch (except
->process(dovars
)) {
1742 case ESI_PROCESS_COMPLETE
:
1743 debugs(86, 5, "esiTryProcess: except Processed OK");
1745 return bestAttemptRV();
1747 case ESI_PROCESS_PENDING_WONTFAIL
:
1748 debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
1749 /* We're not done yet, but can't fail */
1750 return ESI_PROCESS_PENDING_WONTFAIL
;
1752 case ESI_PROCESS_PENDING_MAYFAIL
:
1753 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
1754 /* The except branch fail fail */
1755 return ESI_PROCESS_PENDING_MAYFAIL
;
1757 case ESI_PROCESS_FAILED
:
1758 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
1759 flags
.exceptfailed
= 1;
1763 if (flags
.exceptfailed
&& flags
.attemptfailed
)
1764 return ESI_PROCESS_FAILED
;
1766 /* one of attempt or except returned PENDING MAYFAIL */
1767 return ESI_PROCESS_PENDING_MAYFAIL
;
1771 esiTry::notifyParent()
1773 if (flags
.attemptfailed
) {
1774 if (flags
.exceptok
) {
1775 parent
->provideData (exceptbuffer
, this);
1776 exceptbuffer
= NULL
;
1777 } else if (flags
.exceptfailed
|| except
.getRaw() == NULL
) {
1778 parent
->fail (this, "esi:try - except claused failed, or no except clause found");
1782 /* nothing to do when except fails and attempt hasn't */
1786 esiTry::fail(ESIElement
*source
, char const *anError
)
1789 assert (source
== attempt
|| source
== except
);
1790 debugs(86, 5, "esiTry::fail: this=" << this << ", source=" << source
<< ", message=" << anError
);
1792 if (source
== except
) {
1793 flags
.exceptfailed
= 1;
1795 flags
.attemptfailed
= 1;
1802 esiTry::provideData (ESISegment::Pointer data
, ESIElement
* source
)
1804 if (source
== attempt
) {
1805 flags
.attemptok
= 1;
1806 parent
->provideData (data
, this);
1807 } else if (source
== except
) {
1809 assert (exceptbuffer
== NULL
);
1810 ESISegment::ListTransfer (data
, exceptbuffer
);
1815 esiTry::esiTry(esiTry
const &old
)
1819 flags
.attemptok
= 0;
1821 flags
.attemptfailed
= 0;
1822 flags
.exceptfailed
= 0;
1824 exceptbuffer
= NULL
;
1828 esiTry::makeCacheable() const
1830 debugs(86, 5, "esiTry::makeCacheable: making cachable Try from " << this);
1831 esiTry
*resultT
= new esiTry (*this);
1832 ESIElement::Pointer result
= resultT
;
1834 if (attempt
.getRaw())
1835 resultT
->attempt
= attempt
->makeCacheable();
1837 if (except
.getRaw())
1838 resultT
->except
= except
->makeCacheable();
1844 esiTry::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
1846 debugs(86, 5, "esiTry::makeUsable: making usable Try from " << this);
1847 esiTry
*resultT
= new esiTry (*this);
1848 ESIElement::Pointer result
= resultT
;
1850 resultT
->parent
= newParent
;
1852 if (attempt
.getRaw())
1853 resultT
->attempt
= attempt
->makeUsable(resultT
, newVarState
);
1855 if (except
.getRaw())
1856 resultT
->except
= except
->makeUsable(resultT
, newVarState
);
1866 if (attempt
.getRaw())
1871 if (except
.getRaw())
1878 esiChoose::~esiChoose()
1880 debugs(86, 5, "esiChoose::~esiChoose " << this);
1883 esiChoose::esiChoose(esiTreeParentPtr aParent
) :
1890 esiChoose::render(ESISegment::Pointer output
)
1892 /* append all processed elements, and trim processed and rendered elements */
1893 assert (output
->next
== NULL
);
1894 assert (elements
.size() || otherwise
.getRaw());
1895 debugs(86, 5, "esiChooseRender: rendering");
1897 if (chosenelement
>= 0)
1898 elements
[chosenelement
]->render(output
);
1899 else if (otherwise
.getRaw())
1900 otherwise
->render(output
);
1904 esiChoose::addElement(ESIElement::Pointer element
)
1906 /* add an element to the output list */
1908 if (dynamic_cast<esiLiteral
*>(element
.getRaw())) {
1909 /* Swallow whitespace */
1910 debugs(86, 5, "esiChooseAdd: Choose " << this << " skipping whitespace " << element
.getRaw());
1914 /* Some elements require specific parents */
1915 if (!(dynamic_cast<esiWhen
*>(element
.getRaw()) || dynamic_cast<esiOtherwise
*>(element
.getRaw()))) {
1916 debugs(86, DBG_CRITICAL
, "esiChooseAdd: invalid child node for esi:choose (section 3.3)");
1920 if (dynamic_cast<esiOtherwise
*>(element
.getRaw())) {
1921 if (otherwise
.getRaw()) {
1922 debugs(86, DBG_CRITICAL
, "esiChooseAdd: only one otherwise node allowed for esi:choose (section 3.3)");
1926 otherwise
= element
;
1928 elements
.push_back (element
);
1930 debugs (86,3, "esiChooseAdd: Added a new element, elements = " << elements
.size());
1932 if (chosenelement
== -1) {
1933 const esiWhen
* topElement
=dynamic_cast<esiWhen
*>(element
.getRaw());
1934 if (topElement
&& topElement
->testsTrue()) {
1935 chosenelement
= elements
.size() - 1;
1936 debugs (86,3, "esiChooseAdd: Chose element " << elements
.size());
1945 esiChoose::selectElement()
1947 if (chosenelement
> -1)
1950 for (size_t counter
= 0; counter
< elements
.size(); ++counter
) {
1951 const esiWhen
*el
= dynamic_cast<esiWhen
*>(elements
[counter
].getRaw());
1952 if (el
&& el
->testsTrue()) {
1953 chosenelement
= counter
;
1954 debugs (86,3, "esiChooseAdd: Chose element " << counter
+ 1);
1963 elements
.setNULL(0, elements
.size());
1965 if (otherwise
.getRaw())
1966 otherwise
->finish();
1974 ElementList::setNULL (int start
, int end
)
1976 assert (start
>= 0 && start
<= elementcount
);
1977 assert (end
>= 0 && end
<= elementcount
);
1979 for (int loopPosition
= start
; loopPosition
< end
; ++loopPosition
) {
1980 if (elements
[loopPosition
].getRaw())
1981 elements
[loopPosition
]->finish();
1983 debugs(86, 5, "esiSequence::NULLElements: Setting index " <<
1984 loopPosition
<< ", pointer " <<
1985 elements
[loopPosition
].getRaw() << " to NULL");
1987 elements
[loopPosition
] = NULL
;
1992 esiChoose::NULLUnChosen()
1994 if (chosenelement
>= 0) {
1995 if (otherwise
.getRaw())
1996 otherwise
->finish();
2000 elements
.setNULL (0, chosenelement
);
2002 elements
.setNULL (chosenelement
+ 1, elements
.size());
2003 } else if (otherwise
.getRaw()) {
2004 elements
.setNULL (0, elements
.size());
2009 esiChoose::process (int dovars
)
2011 /* process as much of the list as we can, stopping only on
2014 /* We MUST have a when clause */
2017 if (!elements
.size()) {
2020 if (otherwise
.getRaw())
2021 otherwise
->finish();
2027 return ESI_PROCESS_FAILED
;
2030 if (chosenelement
>= 0) {
2031 return elements
[chosenelement
]->process(dovars
);
2032 } else if (otherwise
.getRaw())
2033 return otherwise
->process(dovars
);
2035 return ESI_PROCESS_COMPLETE
;
2039 esiChoose::checkValidSource (ESIElement::Pointer source
) const
2041 if (!elements
.size())
2042 fatal ("invalid callback = no when clause\n");
2044 if (chosenelement
>= 0)
2045 assert (source
== elements
[chosenelement
]);
2046 else if (otherwise
.getRaw())
2047 assert (source
== otherwise
);
2049 fatal ("esiChoose::checkValidSource: invalid callback - no elements chosen\n");
2053 esiChoose::fail(ESIElement
* source
, char const *anError
)
2055 checkValidSource (source
);
2056 elements
.setNULL (0, elements
.size());
2058 if (otherwise
.getRaw())
2059 otherwise
->finish();
2063 parent
->fail(this, anError
);
2069 esiChoose::provideData (ESISegment::Pointer data
, ESIElement
*source
)
2071 checkValidSource (source
);
2072 parent
->provideData (data
, this);
2075 esiChoose::esiChoose(esiChoose
const &old
) : chosenelement(-1), otherwise (NULL
), parent (NULL
)
2077 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2078 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeCacheable();
2080 if (newElement
.getRaw())
2081 assert (addElement(newElement
));
2086 esiChoose::makeCachableElements(esiChoose
const &old
)
2088 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2089 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeCacheable();
2091 if (newElement
.getRaw())
2092 assert (addElement(newElement
));
2097 esiChoose::makeUsableElements(esiChoose
const &old
, ESIVarState
&newVarState
)
2099 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2100 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeUsable (this, newVarState
);
2102 if (newElement
.getRaw())
2103 assert (addElement(newElement
));
2108 esiChoose::makeCacheable() const
2110 esiChoose
*resultC
= new esiChoose (*this);
2111 ESIElement::Pointer result
= resultC
;
2112 resultC
->makeCachableElements(*this);
2114 if (otherwise
.getRaw())
2115 resultC
->otherwise
= otherwise
->makeCacheable();
2121 esiChoose::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
2123 esiChoose
*resultC
= new esiChoose (*this);
2124 ESIElement::Pointer result
= resultC
;
2125 resultC
->parent
= newParent
;
2126 resultC
->makeUsableElements(*this, newVarState
);
2127 resultC
->selectElement();
2129 if (otherwise
.getRaw())
2130 resultC
->otherwise
= otherwise
->makeUsable(resultC
, newVarState
);
2136 ElementList::ElementList () : elements(NULL
), allocedcount(0), allocedsize(0), elementcount (0)
2139 ElementList::~ElementList()
2141 debugs(86, 5, "ElementList::~ElementList " << this);
2142 setNULL(0, elementcount
);
2145 memFreeBuf (allocedsize
, elements
);
2148 ESIElement::Pointer
&
2149 ElementList::operator [] (int index
)
2151 return elements
[index
];
2154 ESIElement::Pointer
const &
2155 ElementList::operator [] (int index
) const
2157 return elements
[index
];
2161 ElementList::pop_front (size_t const count
)
2166 memmove(elements
, &elements
[count
], (elementcount
- count
) * sizeof (ESIElement::Pointer
));
2168 elementcount
-= count
;
2172 ElementList::push_back(ESIElement::Pointer
&newElement
)
2174 elements
= (ESIElement::Pointer
*)memReallocBuf (elements
, ++elementcount
* sizeof (ESIElement::Pointer
),
2177 allocedcount
= elementcount
;
2178 memset(&elements
[elementcount
- 1], '\0', sizeof (ESIElement::Pointer
));
2179 elements
[elementcount
- 1] = newElement
;
2183 ElementList::size() const
2185 return elementcount
;
2189 esiWhen::esiWhen(esiTreeParentPtr aParent
, int attrcount
, const char **attr
,ESIVarState
*aVar
) :
2190 esiSequence(aParent
),
2192 unevaluatedExpression(NULL
),
2195 char const *expression
= NULL
;
2197 for (int loopCounter
= 0; loopCounter
< attrcount
&& attr
[loopCounter
]; loopCounter
+= 2) {
2198 if (!strcmp(attr
[loopCounter
],"test")) {
2200 debugs(86, 5, "esiWhen::esiWhen: Evaluating '" << attr
[loopCounter
+1] << "'");
2201 /* TODO: warn the user instead of asserting */
2202 assert (expression
== NULL
);
2203 expression
= attr
[loopCounter
+1];
2205 /* ignore mistyped attributes.
2206 * TODO:? error on these for user feedback - config parameter needed
2208 debugs(86, DBG_IMPORTANT
, "Found misttyped attribute on ESI When clause");
2212 /* No expression ? default is not matching */
2216 unevaluatedExpression
= xstrdup(expression
);
2218 varState
= cbdataReference (aVar
);
2225 safe_free (unevaluatedExpression
);
2228 cbdataReferenceDone (varState
);
2234 if (!unevaluatedExpression
)
2239 varState
->feedData(unevaluatedExpression
, strlen (unevaluatedExpression
));
2241 char const *expression
= varState
->extractChar ();
2243 setTestResult(ESIExpression::Evaluate (expression
));
2245 safe_free (expression
);
2248 esiWhen::esiWhen(esiWhen
const &old
) :
2251 unevaluatedExpression(NULL
),
2254 if (old
.unevaluatedExpression
)
2255 unevaluatedExpression
= xstrdup(old
.unevaluatedExpression
);
2259 esiWhen::makeCacheable() const
2261 return new esiWhen(*this);
2265 esiWhen::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
2267 esiWhen
*resultW
= new esiWhen (*this);
2268 ESIElement::Pointer result
= resultW
;
2269 resultW
->parent
= newParent
;
2270 resultW
->makeUsableElements(*this, newVarState
);
2271 resultW
->varState
= cbdataReference (&newVarState
);
2272 resultW
->evaluate();
2276 /* TODO: implement surrogate targeting and control processing */
2278 esiEnableProcessing (HttpReply
*rep
)
2282 if (rep
->surrogate_control
) {
2283 HttpHdrScTarget
*sctusable
=
2284 rep
->surrogate_control
->getMergedTarget(Config
.Accel
.surrogate_id
);
2286 // found something targeted at us
2288 sctusable
->hasContent() &&
2289 sctusable
->content().pos("ESI/1.0")) {
2299 #endif /* USE_SQUID_ESI == 1 */