2 * DEBUG: section 86 ESI processing
3 * AUTHOR: Robert Collins
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 ; but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
31 * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
36 /* MS Visual Studio Projects are monolithic, so we need the following
37 * #if to exclude the ESI code from compile process when not needed.
39 #if (USE_SQUID_ESI == 1)
42 #include "clientStream.h"
43 #include "client_side_request.h"
44 #include "errorpage.h"
45 #include "esi/Segment.h"
46 #include "esi/Element.h"
47 #include "esi/Context.h"
48 #include "HttpHdrSc.h"
49 #include "HttpHdrScTarget.h"
50 #include "HttpReply.h"
51 #include "esi/Attempt.h"
52 #include "esi/Except.h"
53 #include "client_side.h"
54 #include "esi/VarState.h"
55 #include "esi/Assign.h"
56 #include "esi/Expression.h"
57 #include "HttpRequest.h"
59 #include "ip/IpAddress.h"
61 /* quick reference on behaviour here.
62 * The ESI specification 1.0 requires the ESI processor to be able to
63 * return an error code at any point in the processing. To that end
64 * we buffer the incoming esi body until we know we will be able to
65 * satisfy the request. At that point we start streaming the queued
70 class ESIStreamContext
;
72 /* TODO: split this out into separate files ? */
73 /* Parsing: quick and dirty. ESI files are not valid XML, so a generic
74 * XML parser is not much use. Also we need a push parser not a pull
75 * parser, so LibXML is out.
77 * Interpreter methods:
78 * Render: May only ever be called after Process returns PROCESS_COMPLETE.
79 * Renders the resulting content into a ESISegment chain.
80 * Process: returns the status of the node.
81 * COMPLETE - processing is complete, rendering may staret
82 * PENDING_WONTFAIL - process is incomplete, but the element *will*
83 * be able to be rendered given time.
84 * PENDING_MAYFAIL - processing is incomplete, and the element *may*
85 * fail to be able to rendered.
86 * FAILED - processing failed, return an error to the client.
90 * NOT TODO: esi:inline - out of scope.
93 /* make comparisons with refcount pointers easy */
94 bool operator == (ESIElement
const *lhs
, ESIElement::Pointer
const &rhs
)
96 return lhs
== rhs
.getRaw();
99 typedef ESIContext::esiKick_t esiKick_t
;
102 /* some core operators */
106 struct esiComment
: public ESIElement
{
107 MEMPROXY_CLASS(esiComment
);
110 Pointer
makeCacheable() const;
111 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
113 void render(ESISegment::Pointer
);
117 MEMPROXY_CLASS_INLINE(esiComment
) /**DOCS_NOSEMI*/
119 #include "esi/Literal.h"
121 #include "esi/Sequence.h"
123 #include "esi/Include.h"
127 class esiRemove
: public ESIElement
131 void *operator new (size_t byteCount
);
132 void operator delete (void *address
);
135 void render(ESISegment::Pointer
);
136 bool addElement (ESIElement::Pointer
);
137 Pointer
makeCacheable() const;
138 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
142 CBDATA_TYPE (esiRemove
);
143 static FREE esiRemoveFree
;
144 static ESIElement
* esiRemoveNew(void);
149 struct esiTry
: public ESIElement
{
150 MEMPROXY_CLASS(esiTry
);
152 esiTry(esiTreeParentPtr aParent
);
155 void render(ESISegment::Pointer
);
156 bool addElement (ESIElement::Pointer
);
157 void fail(ESIElement
*, char const * = NULL
);
158 esiProcessResult_t
process (int dovars
);
159 void provideData (ESISegment::Pointer data
, ESIElement
* source
);
160 Pointer
makeCacheable() const;
161 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
163 ESIElement::Pointer attempt
;
164 ESIElement::Pointer except
;
167 int attemptok
:1; /* the attempt branch process correctly */
168 int exceptok
:1; /* likewise */
169 int attemptfailed
:1; /* The attempt branch failed */
170 int exceptfailed
:1; /* the except branch failed */
176 esiTreeParentPtr parent
;
177 ESISegment::Pointer exceptbuffer
;
178 esiTry (esiTry
const &);
179 esiProcessResult_t
bestAttemptRV() const;
182 MEMPROXY_CLASS_INLINE(esiTry
) /**DOCS_NOSEMI*/
188 struct esiChoose
: public ESIElement
{
189 MEMPROXY_CLASS(esiChoose
);
191 esiChoose(esiTreeParentPtr
);
194 void render(ESISegment::Pointer
);
195 bool addElement (ESIElement::Pointer
);
196 void fail(ESIElement
*, char const * = NULL
);
197 esiProcessResult_t
process (int dovars
);
199 void provideData (ESISegment::Pointer data
, ESIElement
*source
);
200 void makeCachableElements(esiChoose
const &old
);
201 void makeUsableElements(esiChoose
const &old
, ESIVarState
&);
202 Pointer
makeCacheable() const;
203 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
206 ElementList elements
;
208 ESIElement::Pointer otherwise
;
212 esiChoose(esiChoose
const &);
213 esiTreeParentPtr parent
;
214 void checkValidSource (ESIElement::Pointer source
) const;
215 void selectElement();
218 MEMPROXY_CLASS_INLINE(esiChoose
) /**DOCS_NOSEMI*/
222 struct esiWhen
: public esiSequence
{
223 MEMPROXY_CLASS(esiWhen
);
224 esiWhen(esiTreeParentPtr aParent
, int attributes
, const char **attr
, ESIVarState
*);
226 Pointer
makeCacheable() const;
227 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
229 bool testsTrue() const { return testValue
;}
231 void setTestResult(bool aBool
) {testValue
= aBool
;}
234 esiWhen (esiWhen
const &);
236 char const *unevaluatedExpression
;
237 ESIVarState
*varState
;
241 MEMPROXY_CLASS_INLINE(esiWhen
) /**DOCS_NOSEMI*/
245 struct esiOtherwise
: public esiSequence
{
246 // void *operator new (size_t byteCount);
247 // void operator delete (void *address);
248 esiOtherwise(esiTreeParentPtr aParent
) : esiSequence (aParent
) {}
251 CBDATA_CLASS_INIT(ESIContext
);
253 void ESIContext::startRead()
259 void ESIContext::finishRead()
265 bool ESIContext::reading() const
271 ESIStreamContext::ESIStreamContext() : finished(false), include (NULL
), localbuffer (new ESISegment
), buffer (NULL
)
274 /* Local functions */
276 static ESIContext
*ESIContextNew(HttpReply
*, clientStreamNode
*, ClientHttpRequest
*);
280 ESIContext::operator new(size_t byteCount
)
282 assert (byteCount
== sizeof (ESIContext
));
283 CBDATA_INIT_TYPE(ESIContext
);
284 ESIContext
*result
= cbdataAlloc(ESIContext
);
289 ESIContext::operator delete (void *address
)
291 ESIContext
*t
= static_cast<ESIContext
*>(address
);
296 ESIContext::setError()
299 errorstatus
= HTTP_INTERNAL_SERVER_ERROR
;
304 ESIContext::appendOutboundData(ESISegment::Pointer theData
)
306 if (!outbound
.getRaw()) {
308 outboundtail
= outbound
;
310 assert (outboundtail
->next
.getRaw() == NULL
);
311 outboundtail
->next
= theData
;
315 debugs(86, 9, "ESIContext::appendOutboundData: outbound " << outbound
.getRaw());
319 ESIContext::provideData (ESISegment::Pointer theData
, ESIElement
* source
)
321 debugs(86, 5, "ESIContext::provideData: " << this << " " << theData
.getRaw() << " " << source
);
322 /* No callbacks permitted after finish() called on the tree */
323 assert (tree
.getRaw());
324 assert (source
== tree
);
325 appendOutboundData(theData
);
333 ESIContext::fail (ESIElement
* source
, char const *anError
)
336 setErrorMessage (anError
);
342 ESIContext::fixupOutboundTail()
344 /* TODO: fixup thisNode outboundtail dross a little */
346 if (outboundtail
.getRaw())
347 outboundtail
= outboundtail
->tail();
356 debugs(86, 5, "esiKick: Re-entered whilst in progress");
357 // return ESI_KICK_INPROGRESS;
362 /* we've been detached from - we can't do anything more */
363 return ESI_KICK_FAILED
;
365 /* Something has occured. Process any remaining nodes */
367 /* Process some of our data */
368 switch (process ()) {
370 case ESI_PROCESS_COMPLETE
:
371 debugs(86, 5, "esiKick: esiProcess OK");
374 case ESI_PROCESS_PENDING_WONTFAIL
:
375 debugs(86, 5, "esiKick: esiProcess PENDING OK");
378 case ESI_PROCESS_PENDING_MAYFAIL
:
379 debugs(86, 5, "esiKick: esiProcess PENDING UNKNOWN");
382 case ESI_PROCESS_FAILED
:
383 debugs(86, 2, "esiKick: esiProcess " << this << " FAILED");
384 /* this can not happen - processing can't fail until we have data,
385 * and when we come here we have sent data to the client
393 return ESI_KICK_FAILED
;
396 /* Render if we can to get maximal sent data */
397 assert (tree
.getRaw() || flags
.error
);
399 if (!flags
.finished
&& !outbound
.getRaw()) {
400 outboundtail
= new ESISegment
;
401 outbound
= outboundtail
;
404 if (!flags
.error
&& !flags
.finished
)
405 tree
->render(outboundtail
);
410 /* Is there data to send? */
412 /* some data was sent. we're finished until the next read */
414 return ESI_KICK_SENT
;
418 /* nothing to send */
419 return flags
.error
? ESI_KICK_FAILED
: ESI_KICK_PENDING
;
422 /* request from downstream for more data
425 esiStreamRead (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
427 clientStreamNode
*next
;
428 /* Test preconditions */
429 assert (thisNode
!= NULL
);
430 assert (cbdataReferenceValid (thisNode
));
431 /* we are not in the chain until ESI is detected on a data callback */
432 assert (thisNode
->node
.prev
!= NULL
);
433 assert (thisNode
->node
.next
!= NULL
);
435 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
436 assert (context
.getRaw() != NULL
);
438 if (context
->flags
.passthrough
) {
439 /* passthru mode - read into supplied buffers */
440 next
= thisNode
->next();
441 clientStreamRead (thisNode
, http
, next
->readBuffer
);
445 context
->flags
.clientwantsdata
= 1;
446 debugs(86, 5, "esiStreamRead: Client now wants data");
448 /* Ok, not passing through */
450 switch (context
->kick ()) {
452 case ESIContext::ESI_KICK_FAILED
:
453 /* this can not happen - processing can't fail until we have data,
454 * and when we come here we have sent data to the client
457 case ESIContext::ESI_KICK_SENT
:
459 case ESIContext::ESI_KICK_INPROGRESS
:
462 case ESIContext::ESI_KICK_PENDING
:
466 /* Nothing to send */
468 if (context
->flags
.oktosend
&& (context
->flags
.finishedtemplate
469 || context
->cachedASTInUse
) &&
470 ! context
->flags
.finished
) {
471 /* we've started sending, finished reading, but not finished
472 * processing. stop here, a callback will resume the stream
475 debugs(86, 5, "esiStreamRead: Waiting for async resume of esi processing");
479 if (context
->flags
.oktosend
&& context
->flags
.finished
&& context
->outbound
.getRaw()) {
480 debugs(86, 5, "all processing complete, but outbound data still buffered");
481 assert (!context
->flags
.clientwantsdata
);
482 /* client MUST be processing the last reply */
487 if (context
->flags
.oktosend
&& context
->flags
.finished
) {
488 StoreIOBuffer tempBuffer
;
489 assert (!context
->outbound
.getRaw());
490 /* We've finished processing, and there is no more data buffered */
491 debugs(86, 5, "Telling recipient EOF on READ");
492 clientStreamCallback (thisNode
, http
, NULL
, tempBuffer
);
496 if (context
->reading())
499 /* no data that is ready to send, and still reading? well, lets get some */
500 /* secure a buffer */
501 if (!context
->incoming
.getRaw()) {
502 /* create a new buffer segment */
503 context
->buffered
= new ESISegment
;
504 context
->incoming
= context
->buffered
;
507 assert (context
->incoming
.getRaw() && context
->incoming
->len
!= HTTP_REQBUF_SZ
);
509 StoreIOBuffer tempBuffer
;
510 tempBuffer
.offset
= context
->readpos
;
511 tempBuffer
.length
= context
->incoming
->len
- HTTP_REQBUF_SZ
;
512 tempBuffer
.data
= &context
->incoming
->buf
[context
->incoming
->len
];
513 context
->startRead();
514 clientStreamRead (thisNode
, http
, tempBuffer
);
518 clientStream_status_t
519 esiStreamStatus (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
521 /* Test preconditions */
522 assert (thisNode
!= NULL
);
523 assert (cbdataReferenceValid (thisNode
));
524 /* we are not in the chain until ESI is detected on a data callback */
525 assert (thisNode
->node
.prev
!= NULL
);
526 assert (thisNode
->node
.next
!= NULL
);
528 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
529 assert (context
.getRaw() != NULL
);
531 if (context
->flags
.passthrough
)
532 return clientStreamStatus (thisNode
, http
);
534 if (context
->flags
.oktosend
&& context
->flags
.finished
&&
535 !(context
->outbound
.getRaw() && context
->outbound_offset
< context
->outbound
->len
)) {
536 debugs(86, 5, "Telling recipient EOF on STATUS");
537 return STREAM_UNPLANNED_COMPLETE
; /* we don't know lengths in advance */
540 /* ?? RC: we can't be aborted / fail ? */
545 esiAlwaysPassthrough(http_status sline
)
551 case HTTP_CONTINUE
: /* Should never reach us... but squid needs to alter to accomodate this */
553 case HTTP_SWITCHING_PROTOCOLS
: /* Ditto */
555 case HTTP_PROCESSING
: /* Unknown - some extension */
557 case HTTP_NO_CONTENT
: /* no body, no esi */
559 case HTTP_NOT_MODIFIED
: /* ESI does not affect assembled page headers, so 304s are valid */
572 ESIContext::trimBlanks()
574 /* trim leading empty buffers ? */
576 while (outbound
.getRaw() && outbound
->next
.getRaw() && !outbound
->len
) {
577 debugs(86, 5, "ESIContext::trimBlanks: " << this <<
578 " skipping segment " << outbound
.getRaw());
579 outbound
= outbound
->next
;
582 if (outboundtail
.getRaw())
583 assert (outbound
.getRaw());
586 /* Send data downstream
587 * Returns 0 if nothing was sent. Non-zero if data was sent.
592 debugs(86, 5, "ESIContext::send: this=" << this);
593 /* send any processed data */
597 if (!flags
.clientwantsdata
) {
598 debugs(86, 5, "ESIContext::send: Client does not want data - not sending anything");
602 if (tree
.getRaw() && tree
->mayFail()) {
603 debugs(86, 5, "ESIContext::send: Tree may fail. Not sending.");
610 if (!flags
.oktosend
) {
612 fatal("ESIContext::send: Not OK to send.\n");
618 if (!(rep
|| (outbound
.getRaw() &&
619 outbound
->len
&& (outbound_offset
<= outbound
->len
)))) {
620 debugs(86, 5, "ESIContext::send: Nothing to send.");
624 debugs(86, 5, "ESIContext::send: Sending something...");
625 /* Yes! Send it without asking for more upstream */
626 /* memcopying because the client provided the buffer */
627 /* TODO: skip data until pos == next->readoff; */
628 assert (thisNode
->data
== this);
629 clientStreamNode
*next
= thisNode
->next();
630 ESIContext
*templock
= cbdataReference (this);
633 if (outbound
.getRaw())
634 len
= min (next
->readBuffer
.length
, outbound
->len
- outbound_offset
);
636 /* prevent corruption on range requests, even though we don't support them yet */
637 assert (pos
== next
->readBuffer
.offset
);
639 /* We must send data or a reply */
640 assert (len
!= 0 || rep
!= NULL
);
643 xmemcpy (next
->readBuffer
.data
, &outbound
->buf
[outbound_offset
], len
);
645 if (len
+ outbound_offset
== outbound
->len
) {
646 ESISegment::Pointer temp
= outbound
->next
;
647 /* remove the used buffer */
654 if (!outbound
.getRaw())
660 flags
.clientwantsdata
= 0;
661 debugs(86, 5, "ESIContext::send: this=" << this << " Client no longer wants data ");
662 /* Deal with re-entrancy */
663 HttpReply
*temprep
= rep
;
664 rep
= NULL
; /* freed downstream */
666 if (temprep
&& varState
)
667 varState
->buildVary (temprep
);
670 StoreIOBuffer tempBuffer
;
671 tempBuffer
.length
= len
;
672 tempBuffer
.offset
= pos
- len
;
673 tempBuffer
.data
= next
->readBuffer
.data
;
674 clientStreamCallback (thisNode
, http
, temprep
, tempBuffer
);
678 len
= 1; /* tell the caller we sent something (because we sent headers */
681 cbdataReferenceDone (templock
);
683 debugs (86,5,"ESIContext::send: this=" << this << " sent " << len
);
689 ESIContext::finishChildren()
697 /* Detach event from a client Stream */
699 esiStreamDetach (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
701 /* if we have pending callbacks, tell them we're done. */
702 /* test preconditions */
703 assert (thisNode
!= NULL
);
704 assert (cbdataReferenceValid (thisNode
));
705 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
706 assert (context
.getRaw() != NULL
);
707 /* detach from the stream */
708 clientStreamDetach (thisNode
,http
);
709 /* if we have pending callbacks (from subincludes), tell them we're done. */
710 context
->thisNode
= NULL
;
711 context
->flags
.detached
= 1;
712 context
->finishChildren();
713 /* HACK for parser stack not being emptied */
714 context
->parserState
.stack
[0] = NULL
;
715 /* allow refcount logic to trigger */
716 context
->cbdataLocker
= NULL
;
719 /* Process incoming data for ESI tags */
720 /* ESI TODO: Long term: we should have a framework to parse html/xml and
721 * callback to a set of processors like thisNode, to prevent multiple parsing
722 * overhead. More thoughts on thisNode: We have to parse multiple times, because
723 * the output of one processor may create a very different tree. What we could
724 * do is something like DOM and pass that down to a final renderer. This is
725 * getting into web server territory though...
728 * This is not the last node in the stream.
729 * ESI processing has been enabled.
730 * There is context data or a reply structure
733 esiProcessStream (clientStreamNode
*thisNode
, ClientHttpRequest
*http
, HttpReply
*rep
, StoreIOBuffer receivedData
)
735 /* test preconditions */
736 assert (thisNode
!= NULL
);
737 /* ESI TODO: handle thisNode rather than asserting - it should only ever
738 * happen if we cause an abort and the callback chain
739 * loops back to here, so we can simply return. However, that itself
740 * shouldn't happen, so it stays as an assert for now. */
741 assert (cbdataReferenceValid (thisNode
));
743 * if data is NULL thisNode is the first entrance. If rep is also NULL,
744 * something is wrong.
746 assert (thisNode
->data
.getRaw() != NULL
|| rep
);
747 assert (thisNode
->node
.next
!= NULL
);
749 if (!thisNode
->data
.getRaw())
750 /* setup ESI context from reply headers */
751 thisNode
->data
= ESIContextNew(rep
, thisNode
, http
);
753 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
755 assert (context
.getRaw() != NULL
);
757 context
->finishRead();
759 /* Skipping all ESI processing. All remaining data gets untouched.
760 * Mainly used when an error or other non-ESI processable entity
761 * has been detected to prevent ESI processing the error body
763 if (context
->flags
.passthrough
) {
764 clientStreamCallback (thisNode
, http
, rep
, receivedData
);
768 debugs(86, 3, "esiProcessStream: Processing thisNode " << thisNode
<<
769 " context " << context
.getRaw() << " offset " <<
770 (int) receivedData
.offset
<< " length " <<
771 (unsigned int)receivedData
.length
);
773 /* once we finish the template, we *cannot* return here */
774 assert (!context
->flags
.finishedtemplate
);
775 assert (!context
->cachedASTInUse
);
777 /* Can we generate any data ?*/
779 if (receivedData
.data
) {
780 /* Increase our buffer area with incoming data */
781 assert (receivedData
.length
<= HTTP_REQBUF_SZ
);
782 assert (thisNode
->readBuffer
.offset
== receivedData
.offset
);
783 debugs (86,5, "esiProcessStream found " << receivedData
.length
<< " bytes of body data at offset " << receivedData
.offset
);
784 /* secure the data for later use */
786 if (!context
->incoming
.getRaw()) {
787 /* create a new buffer segment */
788 debugs(86, 5, "esiProcessStream: Setting up incoming buffer");
789 context
->buffered
= new ESISegment
;
790 context
->incoming
= context
->buffered
;
793 if (receivedData
.data
!= &context
->incoming
->buf
[context
->incoming
->len
]) {
794 /* We have to copy the data out because we didn't supply thisNode buffer */
795 size_t space
= HTTP_REQBUF_SZ
- context
->incoming
->len
;
796 size_t len
= min (space
, receivedData
.length
);
797 debugs(86, 5, "Copying data from " << receivedData
.data
<< " to " <<
798 &context
->incoming
->buf
[context
->incoming
->len
] <<
799 " because our buffer was not used");
801 xmemcpy (&context
->incoming
->buf
[context
->incoming
->len
], receivedData
.data
, len
);
802 context
->incoming
->len
+= len
;
804 if (context
->incoming
->len
== HTTP_REQBUF_SZ
) {
805 /* append another buffer */
806 context
->incoming
->next
= new ESISegment
;
807 context
->incoming
= context
->incoming
->next
;
810 if (len
!= receivedData
.length
) {
811 /* capture the remnants */
812 xmemcpy (context
->incoming
->buf
, &receivedData
.data
[len
], receivedData
.length
- len
);
813 context
->incoming
->len
= receivedData
.length
- len
;
816 /* and note where we are up to */
817 context
->readpos
+= receivedData
.length
;
819 /* update our position counters, and if needed assign a new buffer */
820 context
->incoming
->len
+= receivedData
.length
;
821 assert (context
->incoming
->len
<= HTTP_REQBUF_SZ
);
823 if (context
->incoming
->len
> HTTP_REQBUF_SZ
* 3 / 4) {
824 /* allocate a new buffer - to stop us asking for ridiculously small amounts */
825 context
->incoming
->next
= new ESISegment
;
826 context
->incoming
= context
->incoming
->next
;
829 context
->readpos
+= receivedData
.length
;
833 /* EOF / Read error / aborted entry */
834 if (rep
== NULL
&& receivedData
.data
== NULL
&& receivedData
.length
== 0 && !context
->flags
.finishedtemplate
) {
835 /* TODO: get stream status to test the entry for aborts */
836 /* else flush the esi processor */
837 debugs(86, 5, "esiProcess: " << context
.getRaw() << " Finished reading upstream data");
838 /* This is correct */
839 context
->flags
.finishedtemplate
= 1;
842 switch (context
->kick()) {
844 case ESIContext::ESI_KICK_FAILED
:
845 /* thisNode can not happen - processing can't fail until we have data,
846 * and when we come here we have sent data to the client
850 case ESIContext::ESI_KICK_SENT
:
852 case ESIContext::ESI_KICK_INPROGRESS
:
855 case ESIContext::ESI_KICK_PENDING
:
859 /* ok.. no data sent, try to pull more data in from upstream.
860 * FIXME: Don't try thisNode if we have finished reading the template
862 if (!context
->flags
.finishedtemplate
&& !context
->reading()
863 && !context
->cachedASTInUse
) {
864 StoreIOBuffer tempBuffer
;
865 assert (context
->incoming
.getRaw() && context
->incoming
->len
< HTTP_REQBUF_SZ
);
866 tempBuffer
.offset
= context
->readpos
;
867 tempBuffer
.length
= HTTP_REQBUF_SZ
- context
->incoming
->len
;
868 tempBuffer
.data
= &context
->incoming
->buf
[context
->incoming
->len
];
869 context
->startRead();
870 clientStreamRead (thisNode
, http
, tempBuffer
);
874 debugs(86, 3, "esiProcessStream: no data to send, no data to read, awaiting a callback");
877 ESIContext::~ESIContext()
880 /* Not freed by freeresources because esi::fail needs it */
881 safe_free (errormessage
);
882 debugs(86, 3, "ESIContext::~ESIContext: Freed " << this);
886 ESIContextNew (HttpReply
*rep
, clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
889 ESIContext
*rv
= new ESIContext
;
891 rv
->cbdataLocker
= rv
;
893 if (esiAlwaysPassthrough(rep
->sline
.status
)) {
894 rv
->flags
.passthrough
= 1;
896 /* remove specific headers for ESI to prevent
897 * downstream cache confusion */
898 HttpHeader
*hdr
= &rep
->header
;
899 hdr
->delById(HDR_ACCEPT_RANGES
);
900 hdr
->delById(HDR_ETAG
);
901 hdr
->delById(HDR_CONTENT_LENGTH
);
902 hdr
->delById(HDR_CONTENT_MD5
);
903 rv
->tree
= new esiSequence (rv
, true);
904 rv
->thisNode
= thisNode
;
906 rv
->flags
.clientwantsdata
= 1;
907 rv
->varState
= new ESIVarState (&http
->request
->header
, http
->uri
);
908 debugs(86, 5, "ESIContextNew: Client wants data (always created during reply cycle");
911 debugs(86, 5, "ESIContextNew: Create context " << rv
);
915 ESIElement::ESIElementType_t
916 ESIElement::IdentifyElement (const char *el
)
922 return ESI_ELEMENT_NONE
;
924 if (!strncmp (el
, "esi:", 4))
926 else if (!strncmp (el
, "http://www.edge-delivery.org/esi/1.0|", 37))
929 return ESI_ELEMENT_NONE
;
931 if (!strncmp (el
+ offset
, "otherwise", 9))
932 return ESI_ELEMENT_OTHERWISE
;
934 if (!strncmp (el
+ offset
, "comment", 7))
935 return ESI_ELEMENT_COMMENT
;
937 if (!strncmp (el
+ offset
, "include", 7))
938 return ESI_ELEMENT_INCLUDE
;
940 if (!strncmp (el
+ offset
, "attempt", 7))
941 return ESI_ELEMENT_ATTEMPT
;
943 if (!strncmp (el
+ offset
, "assign", 6))
944 return ESI_ELEMENT_ASSIGN
;
946 if (!strncmp (el
+ offset
, "remove", 6))
947 return ESI_ELEMENT_REMOVE
;
949 if (!strncmp (el
+ offset
, "except", 6))
950 return ESI_ELEMENT_EXCEPT
;
952 if (!strncmp (el
+ offset
, "choose", 6))
953 return ESI_ELEMENT_CHOOSE
;
955 if (!strncmp (el
+ offset
, "vars", 4))
956 return ESI_ELEMENT_VARS
;
958 if (!strncmp (el
+ offset
, "when", 4))
959 return ESI_ELEMENT_WHEN
;
961 if (!strncmp (el
+ offset
, "try", 3))
962 return ESI_ELEMENT_TRY
;
964 return ESI_ELEMENT_NONE
;
968 ESIContext::ParserState::top()
970 return stack
[stackdepth
-1];
973 ESIContext::ParserState::ParserState() : inited_ (false)
977 ESIContext::ParserState::inited() const
983 ESIContext::addStackElement (ESIElement::Pointer element
)
985 /* Put on the stack to allow skipping of 'invalid' markup */
986 assert (parserState
.stackdepth
<11);
988 debugs(86, 5, "ESIContext::addStackElement: About to add ESI Node " << element
.getRaw());
990 if (!parserState
.top()->addElement(element
)) {
991 debugs(86, 1, "ESIContext::addStackElement: failed to add esi node, probable error in ESI template");
994 /* added ok, push onto the stack */
995 parserState
.stack
[parserState
.stackdepth
++] = element
;
1000 ESIContext::start(const char *el
, const char **attr
, size_t attrCount
)
1003 unsigned int ellen
= strlen (el
);
1004 char localbuf
[HTTP_REQBUF_SZ
];
1005 ESIElement::Pointer element
;
1006 int specifiedattcount
= attrCount
* 2;
1008 assert (ellen
< sizeof (localbuf
)); /* prevent unexpected overruns. */
1010 debugs(86, 5, "ESIContext::Start: element '" << el
<< "' with " << specifiedattcount
<< " tags");
1013 /* waiting for expat to finish the buffer we gave it */
1016 switch (ESIElement::IdentifyElement (el
)) {
1018 case ESIElement::ESI_ELEMENT_NONE
:
1019 /* Spit out elements we aren't interested in */
1022 assert (xstrncpy (&localbuf
[1], el
, sizeof(localbuf
) - 2));
1023 pos
= localbuf
+ strlen (localbuf
);
1025 for (i
= 0; i
< specifiedattcount
&& attr
[i
]; i
+= 2) {
1027 /* TODO: handle thisNode gracefully */
1028 assert (xstrncpy (pos
, attr
[i
], sizeof(localbuf
) + (pos
- localbuf
)));
1029 pos
+= strlen (pos
);
1032 assert (xstrncpy (pos
, attr
[i
+ 1], sizeof(localbuf
) + (pos
- localbuf
)));
1033 pos
+= strlen (pos
);
1040 addLiteral (localbuf
, pos
- localbuf
);
1041 debugs(86, 5, "esi stack depth " << parserState
.stackdepth
);
1045 case ESIElement::ESI_ELEMENT_COMMENT
:
1046 /* Put on the stack to allow skipping of 'invalid' markup */
1047 element
= new esiComment ();
1050 case ESIElement::ESI_ELEMENT_INCLUDE
:
1051 /* Put on the stack to allow skipping of 'invalid' markup */
1052 element
= new ESIInclude (parserState
.top().getRaw(), specifiedattcount
, attr
, this);
1055 case ESIElement::ESI_ELEMENT_REMOVE
:
1056 /* Put on the stack to allow skipping of 'invalid' markup */
1057 element
= esiRemoveNew ();
1060 case ESIElement::ESI_ELEMENT_TRY
:
1061 /* Put on the stack to allow skipping of 'invalid' markup */
1062 element
= new esiTry (parserState
.top().getRaw());
1065 case ESIElement::ESI_ELEMENT_ATTEMPT
:
1066 /* Put on the stack to allow skipping of 'invalid' markup */
1067 element
= new esiAttempt (parserState
.top().getRaw());
1070 case ESIElement::ESI_ELEMENT_EXCEPT
:
1071 /* Put on the stack to allow skipping of 'invalid' markup */
1072 element
= new esiExcept (parserState
.top().getRaw());
1075 case ESIElement::ESI_ELEMENT_VARS
:
1076 /* Put on the stack to allow skipping of 'invalid' markup */
1077 element
= new ESIVar (parserState
.top().getRaw());
1080 case ESIElement::ESI_ELEMENT_CHOOSE
:
1081 /* Put on the stack to allow skipping of 'invalid' markup */
1082 element
= new esiChoose (parserState
.top().getRaw());
1085 case ESIElement::ESI_ELEMENT_WHEN
:
1086 /* Put on the stack to allow skipping of 'invalid' markup */
1087 element
= new esiWhen (parserState
.top().getRaw(), specifiedattcount
, attr
, varState
);
1090 case ESIElement::ESI_ELEMENT_OTHERWISE
:
1091 /* Put on the stack to allow skipping of 'invalid' markup */
1092 element
= new esiOtherwise (parserState
.top().getRaw());
1095 case ESIElement::ESI_ELEMENT_ASSIGN
:
1096 /* Put on the stack to allow skipping of 'invalid' markup */
1097 element
= new ESIAssign (parserState
.top().getRaw(), specifiedattcount
, attr
, this);
1101 addStackElement(element
);
1103 debugs(86, 5, "esi stack depth " << parserState
.stackdepth
);
1105 } /* End of start handler */
1108 ESIContext::end(const char *el
)
1110 unsigned int ellen
= strlen (el
);
1111 char localbuf
[HTTP_REQBUF_SZ
];
1115 /* waiting for expat to finish the buffer we gave it */
1118 switch (ESIElement::IdentifyElement (el
)) {
1120 case ESIElement::ESI_ELEMENT_NONE
:
1121 assert (ellen
< sizeof (localbuf
)); /* prevent unexpected overruns. */
1122 /* Add elements we aren't interested in */
1125 assert (xstrncpy (&localbuf
[2], el
, sizeof(localbuf
) - 3));
1126 pos
= localbuf
+ strlen (localbuf
);
1129 addLiteral (localbuf
, pos
- localbuf
);
1132 case ESIElement::ESI_ELEMENT_COMMENT
:
1134 case ESIElement::ESI_ELEMENT_INCLUDE
:
1136 case ESIElement::ESI_ELEMENT_REMOVE
:
1138 case ESIElement::ESI_ELEMENT_TRY
:
1140 case ESIElement::ESI_ELEMENT_ATTEMPT
:
1142 case ESIElement::ESI_ELEMENT_EXCEPT
:
1144 case ESIElement::ESI_ELEMENT_VARS
:
1146 case ESIElement::ESI_ELEMENT_CHOOSE
:
1148 case ESIElement::ESI_ELEMENT_WHEN
:
1150 case ESIElement::ESI_ELEMENT_OTHERWISE
:
1152 case ESIElement::ESI_ELEMENT_ASSIGN
:
1153 /* pop of the stack */
1154 parserState
.stack
[--parserState
.stackdepth
] = NULL
;
1157 } /* End of end handler */
1160 ESIContext::parserDefault (const char *s
, int len
)
1165 /* handle any skipped data */
1166 addLiteral (s
, len
);
1170 ESIContext::parserComment (const char *s
)
1175 if (!strncmp(s
, "esi",3)) {
1176 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block encountered");
1177 ESIParser::Pointer tempParser
= ESIParser::NewParser (this);
1179 /* wrap the comment in some tags */
1181 if (!tempParser
->parse("<div>", 5,0) ||
1182 !tempParser
->parse(s
+ 3, strlen(s
) - 3, 0) ||
1183 !tempParser
->parse("</div>",6,1)) {
1184 debugs(86, 0, "ESIContext::parserComment: Parsing fragment '" << s
+ 3 << "' failed.");
1187 snprintf(tempstr
, 1023, "ESIContext::parserComment: Parse error at line %ld:\n%s\n",
1188 tempParser
->lineNumber(),
1189 tempParser
->errorString());
1190 debugs(86, 0, "" << tempstr
<< "");
1192 setErrorMessage(tempstr
);
1195 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block parsed");
1198 char localbuf
[HTTP_REQBUF_SZ
];
1200 debugs(86, 5, "ESIContext::parserComment: Regenerating comment block");
1203 if (len
> sizeof (localbuf
) - 9) {
1204 debugs(86, 0, "ESIContext::parserComment: Truncating long comment");
1205 len
= sizeof (localbuf
) - 9;
1208 xstrncpy(localbuf
, "<!--", 5);
1209 xstrncpy(localbuf
+ 4, s
, len
+ 1);
1210 xstrncpy(localbuf
+ 4 + len
, "-->", 4);
1211 addLiteral (localbuf
,len
+ 7);
1216 ESIContext::addLiteral (const char *s
, int len
)
1218 /* handle any skipped data */
1220 debugs(86, 5, "literal length is " << len
);
1221 /* give a literal to the current element */
1222 assert (parserState
.stackdepth
<11);
1223 ESIElement::Pointer
element (new esiLiteral (this, s
, len
));
1225 if (!parserState
.top()->addElement(element
)) {
1226 debugs(86, 1, "ESIContext::addLiteral: failed to add esi node, probable error in ESI template");
1232 ESIContext::ParserState::init(ESIParserClient
*userData
)
1234 theParser
= ESIParser::NewParser (userData
);
1239 ESIContext::parseOneBuffer()
1241 assert (buffered
.getRaw());
1243 debugs (86,9,"ESIContext::parseOneBuffer: " << buffered
->len
<< " bytes");
1244 bool lastBlock
= buffered
->next
.getRaw() == NULL
&& flags
.finishedtemplate
? true : false;
1246 if (! parserState
.theParser
->parse(buffered
->buf
, buffered
->len
, lastBlock
)) {
1249 snprintf (tempstr
, 1023, "esiProcess: Parse error at line %ld:\n%s\n",
1250 parserState
.theParser
->lineNumber(),
1251 parserState
.theParser
->errorString());
1252 debugs(86, 0, "" << tempstr
<< "");
1254 setErrorMessage(tempstr
);
1256 assert (flags
.error
);
1266 ESISegment::Pointer temp
= buffered
;
1267 buffered
= temp
->next
;
1273 if (!parserState
.stackdepth
) {
1274 debugs(86, 5, "empty parser stack, inserting the top level node");
1275 assert (tree
.getRaw());
1276 parserState
.stack
[parserState
.stackdepth
++] = tree
;
1279 if (rep
&& !parserState
.inited())
1280 parserState
.init(this);
1283 if (buffered
.getRaw()) {
1284 parserState
.parsing
= 1;
1285 /* we don't keep any data around */
1287 PROF_start(esiParsing
);
1289 while (buffered
.getRaw() && !flags
.error
)
1292 PROF_stop(esiParsing
);
1294 /* Tel the read code to allocate a new buffer */
1297 parserState
.parsing
= 0;
1302 ESIContext::process ()
1305 * read through buffered, skipping plain text, and skipping any
1306 * <...> entry that is not an <esi: entry.
1307 * when it's found, hand an esiLiteral of the preceeding data to our current
1311 if (parserState
.parsing
) {
1312 /* in middle of parsing - finish here */
1313 return ESI_PROCESS_PENDING_MAYFAIL
;
1316 assert (flags
.finished
== 0);
1318 assert (!flags
.error
);
1320 if (!hasCachedAST())
1322 else if (!flags
.finishedtemplate
)
1326 debugs(86, 5, "ESIContext::process: Parsing failed");
1328 parserState
.popAll();
1329 return ESI_PROCESS_FAILED
;
1332 if (!flags
.finishedtemplate
&& !incoming
.getRaw() && !cachedASTInUse
) {
1333 buffered
= new ESISegment
;
1334 incoming
= buffered
;
1337 if (!flags
.finishedtemplate
&& !cachedASTInUse
) {
1338 return ESI_PROCESS_PENDING_MAYFAIL
;
1341 assert (flags
.finishedtemplate
|| cachedASTInUse
);
1343 /* ok, we've done all we can with the data. What can we process now?
1346 esiProcessResult_t status
;
1347 PROF_start(esiProcessing
);
1349 status
= tree
->process(0);
1354 case ESI_PROCESS_COMPLETE
:
1355 debugs(86, 5, "esiProcess: tree Processed OK");
1358 case ESI_PROCESS_PENDING_WONTFAIL
:
1359 debugs(86, 5, "esiProcess: tree Processed PENDING OK");
1362 case ESI_PROCESS_PENDING_MAYFAIL
:
1363 debugs(86, 5, "esiProcess: tree Processed PENDING UNKNOWN");
1366 case ESI_PROCESS_FAILED
:
1367 debugs(86, 0, "esiProcess: tree Processed FAILED");
1370 setErrorMessage("esiProcess: ESI template Processing failed.");
1372 PROF_stop(esiProcessing
);
1374 return ESI_PROCESS_FAILED
;
1379 if (status
!= ESI_PROCESS_PENDING_MAYFAIL
&& (flags
.finishedtemplate
|| cachedASTInUse
)) {
1380 /* We've read the entire template, and no nodes will
1383 debugs(86, 5, "esiProcess, request will succeed");
1387 if (status
== ESI_PROCESS_COMPLETE
1388 && (flags
.finishedtemplate
|| cachedASTInUse
)) {
1389 /* we've finished all processing. Render and send. */
1390 debugs(86, 5, "esiProcess, processing complete");
1394 PROF_stop(esiProcessing
);
1395 return status
; /* because we have no callbacks */
1400 ESIContext::ParserState::freeResources()
1407 ESIContext::ParserState::popAll()
1410 stack
[--stackdepth
] = NULL
;
1414 ESIContext::freeResources ()
1416 debugs(86, 5, HERE
<< "Freeing for this=" << this);
1422 if (parserState
.inited()) {
1423 parserState
.freeResources();
1426 parserState
.popAll();
1427 ESISegmentFreeList (buffered
);
1428 ESISegmentFreeList (outbound
);
1429 ESISegmentFreeList (outboundtail
);
1432 /* don't touch incoming, it's a pointer into buffered anyway */
1435 extern ErrorState
*clientBuildError (err_type
, http_status
, char const *, IpAddress
&, HttpRequest
*);
1438 /* This can ONLY be used before we have sent *any* data to the client */
1442 debugs(86, 5, "ESIContext::fail: this=" << this);
1443 /* check preconditions */
1445 /* cleanup current state */
1447 /* Stop altering thisNode request */
1450 /* don't honour range requests - for errors we send it all */
1452 /* create an error object */
1453 ErrorState
* err
= clientBuildError(errorpage
, errorstatus
, NULL
, http
->getConn()->peer
, http
->request
);
1454 err
->err_msg
= errormessage
;
1455 errormessage
= NULL
;
1456 rep
= err
->BuildHttpReply();
1457 assert (rep
->body
.mb
->contentSize() >= 0);
1458 size_t errorprogress
= rep
->body
.mb
->contentSize();
1459 /* Tell esiSend where to start sending from */
1460 outbound_offset
= 0;
1461 /* copy the membuf from the reply to outbound */
1463 while (errorprogress
< (size_t)rep
->body
.mb
->contentSize()) {
1464 appendOutboundData(new ESISegment
);
1465 errorprogress
+= outboundtail
->append(rep
->body
.mb
->content() + errorprogress
, rep
->body
.mb
->contentSize() - errorprogress
);
1468 /* the esiCode now thinks that the error is the outbound,
1469 * and all processing has finished. */
1470 /* Send as much as we can */
1473 /* don't cancel anything. The stream nodes will clean up after
1474 * themselves when the reply is freed - and we don't know what to
1479 /* Implementation of ESIElements */
1482 esiComment::~esiComment()
1484 debugs(86, 5, "esiComment::~esiComment " << this);
1487 esiComment::esiComment()
1491 esiComment::finish()
1495 esiComment::render(ESISegment::Pointer output
)
1497 /* Comments do nothing dude */
1498 debugs(86, 5, "esiCommentRender: Rendering comment " << this << " into " << output
.getRaw());
1502 esiComment::makeCacheable() const
1504 debugs(86, 5, "esiComment::makeCacheable: returning NULL");
1509 esiComment::makeUsable(esiTreeParentPtr
, ESIVarState
&) const
1511 fatal ("esiComment::Usable: unreachable code!\n");
1516 esiLiteral::~esiLiteral()
1518 debugs(86, 5, "esiLiteral::~esiLiteral: " << this);
1519 ESISegmentFreeList (buffer
);
1520 cbdataReferenceDone (varState
);
1523 esiLiteral::esiLiteral(ESISegment::Pointer aSegment
)
1526 /* we've been handed a complete, processed string */
1533 esiLiteral::finish()
1536 /* precondition: the buffer chain has at least start + length bytes of data
1538 esiLiteral::esiLiteral(ESIContext
*context
, const char *s
, int numberOfCharacters
)
1541 buffer
= new ESISegment
;
1542 ESISegment::Pointer local
= buffer
;
1544 int remainingCharacters
= numberOfCharacters
;
1546 while (remainingCharacters
> 0) {
1547 if (local
->len
== sizeof (local
->buf
)) {
1548 local
->next
= new ESISegment
;
1552 size_t len
= local
->append (&s
[start
], remainingCharacters
);
1554 remainingCharacters
-= len
;
1557 varState
= cbdataReference (context
->varState
);
1561 esiLiteral::render (ESISegment::Pointer output
)
1563 debugs(86, 9, "esiLiteral::render: Rendering " << this);
1564 /* append the entire chain */
1565 assert (output
->next
.getRaw() == NULL
);
1566 output
->next
= buffer
;
1571 esiLiteral::process (int dovars
)
1574 return ESI_PROCESS_COMPLETE
;
1577 ESISegment::Pointer temp
= buffer
;
1578 /* Ensure variable state is clean */
1580 while (temp
.getRaw()) {
1581 varState
->feedData(temp
->buf
,temp
->len
);
1585 /* free the pre-processed content */
1586 ESISegmentFreeList (buffer
);
1588 buffer
= varState
->extractList ();
1592 return ESI_PROCESS_COMPLETE
;
1595 esiLiteral::esiLiteral(esiLiteral
const &old
) : buffer (old
.buffer
->cloneList()),
1602 esiLiteral::makeCacheable() const
1604 return new esiLiteral (*this);
1608 esiLiteral::makeUsable(esiTreeParentPtr
, ESIVarState
&newVarState
) const
1610 debugs(86, 5, "esiLiteral::makeUsable: Creating usable literal");
1611 esiLiteral
* result
= new esiLiteral (*this);
1612 result
->varState
= cbdataReference (&newVarState
);
1618 esiRemoveFree (void *data
)
1620 esiRemove
*thisNode
= (esiRemove
*)data
;
1621 debugs(86, 5, "esiRemoveFree " << thisNode
);
1625 esiRemove::operator new(size_t byteCount
)
1627 assert (byteCount
== sizeof (esiRemove
));
1629 CBDATA_INIT_TYPE_FREECB(esiRemove
, esiRemoveFree
);
1630 rv
= (void *)cbdataAlloc (esiRemove
);
1635 esiRemove::operator delete (void *address
)
1637 cbdataFree (address
);
1643 return new esiRemove
;
1646 esiRemove::esiRemove()
1654 esiRemove::render(ESISegment::Pointer output
)
1656 /* Removes do nothing dude */
1657 debugs(86, 5, "esiRemoveRender: Rendering remove " << this);
1660 /* Accept non-ESI children */
1662 esiRemove::addElement (ESIElement::Pointer element
)
1664 if (!dynamic_cast<esiLiteral
*>(element
.getRaw())) {
1665 debugs(86, 5, "esiRemoveAdd: Failed for " << this);
1673 esiRemove::makeCacheable() const
1675 debugs(86, 5, "esiRemove::makeCacheable: Returning NULL");
1680 esiRemove::makeUsable(esiTreeParentPtr
, ESIVarState
&) const
1682 fatal ("esiRemove::Usable: unreachable code!\n");
1689 debugs(86, 5, "esiTry::~esiTry " << this);
1692 esiTry::esiTry(esiTreeParentPtr aParent
) : parent (aParent
) , exceptbuffer(NULL
)
1696 esiTry::render (ESISegment::Pointer output
)
1698 /* Try renders from it's children */
1700 assert (attempt
.getRaw());
1701 assert (except
.getRaw());
1702 debugs(86, 5, "esiTryRender: Rendering Try " << this);
1704 if (flags
.attemptok
) {
1705 attempt
->render(output
);
1706 } else if (flags
.exceptok
) {
1709 if (exceptbuffer
.getRaw())
1710 ESISegment::ListTransfer(exceptbuffer
, output
);
1712 except
->render(output
);
1714 debugs(86, 5, "esiTryRender: Neither except nor attempt succeeded?!?");
1717 /* Accept attempt and except only */
1719 esiTry::addElement(ESIElement::Pointer element
)
1721 debugs(86, 5, "esiTryAdd: Try " << this << " adding element " <<
1724 if (dynamic_cast<esiLiteral
*>(element
.getRaw())) {
1725 /* Swallow whitespace */
1726 debugs(86, 5, "esiTryAdd: Try " << this << " skipping whitespace " << element
.getRaw());
1730 if (dynamic_cast<esiAttempt
*>(element
.getRaw())) {
1731 if (attempt
.getRaw()) {
1732 debugs(86, 1, "esiTryAdd: Failed for " << this << " - try allready has an attempt node (section 3.4)");
1740 if (dynamic_cast<esiExcept
*>(element
.getRaw())) {
1741 if (except
.getRaw()) {
1742 debugs(86, 1, "esiTryAdd: Failed for " << this << " - try already has an except node (section 3.4)");
1750 debugs(86, 1, "esiTryAdd: Failed to add element " << element
.getRaw() << " to try " << this << ", incorrect element type (see section 3.4)");
1755 esiTry::bestAttemptRV() const
1757 if (flags
.attemptfailed
)
1758 return ESI_PROCESS_COMPLETE
;
1760 return ESI_PROCESS_PENDING_MAYFAIL
;
1764 esiTry::process (int dovars
)
1766 esiProcessResult_t rv
= ESI_PROCESS_PENDING_MAYFAIL
;
1769 if (!attempt
.getRaw()) {
1770 debugs(86, 0, "esiTryProcess: Try has no attempt element - ESI template is invalid (section 3.4)");
1771 return ESI_PROCESS_FAILED
;
1774 if (!except
.getRaw()) {
1775 debugs(86, 0, "esiTryProcess: Try has no except element - ESI template is invalid (section 3.4)");
1776 return ESI_PROCESS_FAILED
;
1779 if (!flags
.attemptfailed
)
1780 /* Try the attempt branch */
1781 switch ((rv
= attempt
->process(dovars
))) {
1783 case ESI_PROCESS_COMPLETE
:
1784 debugs(86, 5, "esiTryProcess: attempt Processed OK");
1785 flags
.attemptok
= 1;
1786 return ESI_PROCESS_COMPLETE
;
1788 case ESI_PROCESS_PENDING_WONTFAIL
:
1789 debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
1790 /* We're not done yet, but don't need to test except */
1791 return ESI_PROCESS_PENDING_WONTFAIL
;
1793 case ESI_PROCESS_PENDING_MAYFAIL
:
1794 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
1797 case ESI_PROCESS_FAILED
:
1798 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
1799 flags
.attemptfailed
= 1;
1803 /* attempt is either MAYFAIL or FAILED */
1805 return bestAttemptRV();
1807 /* query except to see if it has a definite result */
1808 if (!flags
.exceptfailed
)
1809 /* Try the except branch */
1810 switch (except
->process(dovars
)) {
1812 case ESI_PROCESS_COMPLETE
:
1813 debugs(86, 5, "esiTryProcess: except Processed OK");
1815 return bestAttemptRV();
1817 case ESI_PROCESS_PENDING_WONTFAIL
:
1818 debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
1819 /* We're not done yet, but can't fail */
1820 return ESI_PROCESS_PENDING_WONTFAIL
;
1822 case ESI_PROCESS_PENDING_MAYFAIL
:
1823 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
1824 /* The except branch fail fail */
1825 return ESI_PROCESS_PENDING_MAYFAIL
;
1827 case ESI_PROCESS_FAILED
:
1828 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
1829 flags
.exceptfailed
= 1;
1833 if (flags
.exceptfailed
&& flags
.attemptfailed
)
1834 return ESI_PROCESS_FAILED
;
1836 /* one of attempt or except returned PENDING MAYFAIL */
1837 return ESI_PROCESS_PENDING_MAYFAIL
;
1841 esiTry::notifyParent()
1843 if (flags
.attemptfailed
) {
1844 if (flags
.exceptok
) {
1845 parent
->provideData (exceptbuffer
, this);
1846 exceptbuffer
= NULL
;
1847 } else if (flags
.exceptfailed
|| except
.getRaw() == NULL
) {
1848 parent
->fail (this, "esi:try - except claused failed, or no except clause found");
1852 /* nothing to do when except fails and attempt hasn't */
1856 esiTry::fail(ESIElement
*source
, char const *anError
)
1859 assert (source
== attempt
|| source
== except
);
1860 debugs(86, 5, "esiTry::fail: this=" << this << ", source=" << source
<< ", message=" << anError
);
1862 if (source
== except
) {
1863 flags
.exceptfailed
= 1;
1865 flags
.attemptfailed
= 1;
1872 esiTry::provideData (ESISegment::Pointer data
, ESIElement
* source
)
1874 if (source
== attempt
) {
1875 flags
.attemptok
= 1;
1876 parent
->provideData (data
, this);
1877 } else if (source
== except
) {
1879 assert (exceptbuffer
== NULL
);
1880 ESISegment::ListTransfer (data
, exceptbuffer
);
1885 esiTry::esiTry(esiTry
const &old
)
1889 flags
.attemptok
= 0;
1891 flags
.attemptfailed
= 0;
1892 flags
.exceptfailed
= 0;
1894 exceptbuffer
= NULL
;
1898 esiTry::makeCacheable() const
1900 debugs(86, 5, "esiTry::makeCacheable: making cachable Try from " << this);
1901 esiTry
*resultT
= new esiTry (*this);
1902 ESIElement::Pointer result
= resultT
;
1904 if (attempt
.getRaw())
1905 resultT
->attempt
= attempt
->makeCacheable();
1907 if (except
.getRaw())
1908 resultT
->except
= except
->makeCacheable();
1914 esiTry::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
1916 debugs(86, 5, "esiTry::makeUsable: making usable Try from " << this);
1917 esiTry
*resultT
= new esiTry (*this);
1918 ESIElement::Pointer result
= resultT
;
1920 resultT
->parent
= newParent
;
1922 if (attempt
.getRaw())
1923 resultT
->attempt
= attempt
->makeUsable(resultT
, newVarState
);
1925 if (except
.getRaw())
1926 resultT
->except
= except
->makeUsable(resultT
, newVarState
);
1936 if (attempt
.getRaw())
1941 if (except
.getRaw())
1950 esiAttempt::operator new(size_t byteCount
)
1952 assert (byteCount
== sizeof (esiAttempt
));
1957 esiAttempt::operator delete (void *address
)
1959 cbdataFree (address
);
1967 esiExcept::operator new(size_t byteCount
)
1969 assert (byteCount
== sizeof (esiExcept
));
1971 CBDATA_INIT_TYPE_FREECB(esiExcept
, esiSequence::Free
);
1972 rv
= (void *)cbdataAlloc (esiExcept
);
1977 esiExcept::operator delete (void *address
)
1979 cbdataFree (address
);
1987 esiVar::operator new(size_t byteCount
)
1989 assert (byteCount
== sizeof (esiVar
));
1991 CBDATA_INIT_TYPE_FREECB(esiVar
, esiSequence::Free
);
1992 rv
= (void *)cbdataAlloc (esiVar
);
1997 esiVar::operator delete (void *address
)
1999 cbdataFree (address
);
2005 esiChoose::~esiChoose()
2007 debugs(86, 5, "esiChoose::~esiChoose " << this);
2010 esiChoose::esiChoose(esiTreeParentPtr aParent
) : elements (), chosenelement (-1),parent (aParent
)
2014 esiChoose::render(ESISegment::Pointer output
)
2016 /* append all processed elements, and trim processed and rendered elements */
2017 assert (output
->next
== NULL
);
2018 assert (elements
.size() || otherwise
.getRaw());
2019 debugs(86, 5, "esiChooseRender: rendering");
2021 if (chosenelement
>= 0)
2022 elements
[chosenelement
]->render(output
);
2023 else if (otherwise
.getRaw())
2024 otherwise
->render(output
);
2028 esiChoose::addElement(ESIElement::Pointer element
)
2030 /* add an element to the output list */
2032 if (dynamic_cast<esiLiteral
*>(element
.getRaw())) {
2033 /* Swallow whitespace */
2034 debugs(86, 5, "esiChooseAdd: Choose " << this << " skipping whitespace " << element
.getRaw());
2038 /* Some elements require specific parents */
2039 if (!(dynamic_cast<esiWhen
*>(element
.getRaw()) || dynamic_cast<esiOtherwise
*>(element
.getRaw()))) {
2040 debugs(86, 0, "esiChooseAdd: invalid child node for esi:choose (section 3.3)");
2044 if (dynamic_cast<esiOtherwise
*>(element
.getRaw())) {
2045 if (otherwise
.getRaw()) {
2046 debugs(86, 0, "esiChooseAdd: only one otherwise node allowed for esi:choose (section 3.3)");
2050 otherwise
= element
;
2052 elements
.push_back (element
);
2054 debugs (86,3, "esiChooseAdd: Added a new element, elements = " << elements
.size());
2056 if (chosenelement
== -1)
2057 if ((dynamic_cast<esiWhen
*>(element
.getRaw()))->
2059 chosenelement
= elements
.size() - 1;
2060 debugs (86,3, "esiChooseAdd: Chose element " << elements
.size());
2068 esiChoose::selectElement()
2070 if (chosenelement
> -1)
2073 for (size_t counter
= 0; counter
< elements
.size(); ++counter
) {
2074 if ((dynamic_cast<esiWhen
*>(elements
[counter
].getRaw()))->
2076 chosenelement
= counter
;
2077 debugs (86,3, "esiChooseAdd: Chose element " << counter
+ 1);
2086 elements
.setNULL(0, elements
.size());
2088 if (otherwise
.getRaw())
2089 otherwise
->finish();
2097 ElementList::setNULL (int start
, int end
)
2099 assert (start
>= 0 && start
<= elementcount
);
2100 assert (end
>= 0 && end
<= elementcount
);
2102 for (int loopPosition
= start
; loopPosition
< end
; ++loopPosition
) {
2103 if (elements
[loopPosition
].getRaw())
2104 elements
[loopPosition
]->finish();
2106 debugs(86, 5, "esiSequence::NULLElements: Setting index " <<
2107 loopPosition
<< ", pointer " <<
2108 elements
[loopPosition
].getRaw() << " to NULL");
2110 elements
[loopPosition
] = NULL
;
2115 esiChoose::NULLUnChosen()
2117 if (chosenelement
>= 0) {
2118 if (otherwise
.getRaw())
2119 otherwise
->finish();
2123 elements
.setNULL (0, chosenelement
);
2125 elements
.setNULL (chosenelement
+ 1, elements
.size());
2126 } else if (otherwise
.getRaw()) {
2127 elements
.setNULL (0, elements
.size());
2132 esiChoose::process (int dovars
)
2134 /* process as much of the list as we can, stopping only on
2137 /* We MUST have a when clause */
2140 if (!elements
.size()) {
2143 if (otherwise
.getRaw())
2144 otherwise
->finish();
2150 return ESI_PROCESS_FAILED
;
2153 if (chosenelement
>= 0) {
2154 return elements
[chosenelement
]->process(dovars
);
2155 } else if (otherwise
.getRaw())
2156 return otherwise
->process(dovars
);
2158 return ESI_PROCESS_COMPLETE
;
2162 esiChoose::checkValidSource (ESIElement::Pointer source
) const
2164 if (!elements
.size())
2165 fatal ("invalid callback = no when clause\n");
2167 if (chosenelement
>= 0)
2168 assert (source
== elements
[chosenelement
]);
2169 else if (otherwise
.getRaw())
2170 assert (source
== otherwise
);
2172 fatal ("esiChoose::checkValidSource: invalid callback - no elements chosen\n");
2176 esiChoose::fail(ESIElement
* source
, char const *anError
)
2178 checkValidSource (source
);
2179 elements
.setNULL (0, elements
.size());
2181 if (otherwise
.getRaw())
2182 otherwise
->finish();
2186 parent
->fail(this, anError
);
2192 esiChoose::provideData (ESISegment::Pointer data
, ESIElement
*source
)
2194 checkValidSource (source
);
2195 parent
->provideData (data
, this);
2199 esiChoose::esiChoose(esiChoose
const &old
) : chosenelement(-1), otherwise (NULL
), parent (NULL
)
2201 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2202 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeCacheable();
2204 if (newElement
.getRaw())
2205 assert (addElement(newElement
));
2210 esiChoose::makeCachableElements(esiChoose
const &old
)
2212 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2213 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeCacheable();
2215 if (newElement
.getRaw())
2216 assert (addElement(newElement
));
2221 esiChoose::makeUsableElements(esiChoose
const &old
, ESIVarState
&newVarState
)
2223 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2224 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeUsable (this, newVarState
);
2226 if (newElement
.getRaw())
2227 assert (addElement(newElement
));
2232 esiChoose::makeCacheable() const
2234 esiChoose
*resultC
= new esiChoose (*this);
2235 ESIElement::Pointer result
= resultC
;
2236 resultC
->makeCachableElements(*this);
2238 if (otherwise
.getRaw())
2239 resultC
->otherwise
= otherwise
->makeCacheable();
2245 esiChoose::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
2247 esiChoose
*resultC
= new esiChoose (*this);
2248 ESIElement::Pointer result
= resultC
;
2249 resultC
->parent
= newParent
;
2250 resultC
->makeUsableElements(*this, newVarState
);
2251 resultC
->selectElement();
2253 if (otherwise
.getRaw())
2254 resultC
->otherwise
= otherwise
->makeUsable(resultC
, newVarState
);
2260 ElementList::ElementList () : elements(NULL
), allocedcount(0), allocedsize(0), elementcount (0)
2263 ElementList::~ElementList()
2265 debugs(86, 5, "ElementList::~ElementList " << this);
2266 setNULL(0, elementcount
);
2269 memFreeBuf (allocedsize
, elements
);
2272 ESIElement::Pointer
&
2273 ElementList::operator [] (int index
)
2275 return elements
[index
];
2278 ESIElement::Pointer
const &
2279 ElementList::operator [] (int index
) const
2281 return elements
[index
];
2285 ElementList::pop_front (size_t const count
)
2290 xmemmove (elements
, &elements
[count
], (elementcount
- count
) * sizeof (ESIElement::Pointer
));
2292 elementcount
-= count
;
2296 ElementList::push_back(ESIElement::Pointer
&newElement
)
2298 elements
= (ESIElement::Pointer
*)memReallocBuf (elements
, ++elementcount
* sizeof (ESIElement::Pointer
),
2301 allocedcount
= elementcount
;
2302 memset(&elements
[elementcount
- 1], '\0', sizeof (ESIElement::Pointer
));
2303 elements
[elementcount
- 1] = newElement
;
2307 ElementList::size() const
2309 return elementcount
;
2313 esiWhen::esiWhen (esiTreeParentPtr aParent
, int attrcount
, const char **attr
,ESIVarState
*aVar
) : esiSequence (aParent
)
2316 char const *expression
= NULL
;
2318 for (int loopCounter
= 0; loopCounter
< attrcount
&& attr
[loopCounter
]; loopCounter
+= 2) {
2319 if (!strcmp(attr
[loopCounter
],"test")) {
2321 debugs(86, 5, "esiWhen::esiWhen: Evaluating '" << attr
[loopCounter
+1] << "'");
2322 /* TODO: warn the user instead of asserting */
2323 assert (expression
== NULL
);
2324 expression
= attr
[loopCounter
+1];
2326 /* ignore mistyped attributes.
2327 * TODO:? error on these for user feedback - config parameter needed
2329 debugs(86, 1, "Found misttyped attribute on ESI When clause");
2333 /* No expression ? default is not matching */
2337 unevaluatedExpression
= xstrdup(expression
);
2339 varState
= cbdataReference (aVar
);
2346 safe_free (unevaluatedExpression
);
2349 cbdataReferenceDone (varState
);
2355 if (!unevaluatedExpression
)
2360 varState
->feedData(unevaluatedExpression
, strlen (unevaluatedExpression
));
2362 char const *expression
= varState
->extractChar ();
2364 setTestResult(ESIExpression::Evaluate (expression
));
2366 safe_free (expression
);
2369 esiWhen::esiWhen(esiWhen
const &old
) : esiSequence (old
)
2371 unevaluatedExpression
= NULL
;
2373 if (old
.unevaluatedExpression
)
2374 unevaluatedExpression
= xstrdup(old
.unevaluatedExpression
);
2380 esiWhen::makeCacheable() const
2382 return new esiWhen(*this);
2386 esiWhen::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
2388 esiWhen
*resultW
= new esiWhen (*this);
2389 ESIElement::Pointer result
= resultW
;
2390 resultW
->parent
= newParent
;
2391 resultW
->makeUsableElements(*this, newVarState
);
2392 resultW
->varState
= cbdataReference (&newVarState
);
2393 resultW
->evaluate();
2400 esiOtherwise::operator new(size_t byteCount
)
2402 assert (byteCount
== sizeof (esiOtherwise
));
2404 CBDATA_INIT_TYPE_FREECB(esiOtherwise
, esiSequence::Free
);
2405 rv
= (void *)cbdataAlloc (esiOtherwise
);
2410 esiOtherwise::operator delete (void *address
)
2412 cbdataFree (address
);
2417 /* TODO: implement surrogate targeting and control processing */
2419 esiEnableProcessing (HttpReply
*rep
)
2423 if (rep
->header
.has(HDR_SURROGATE_CONTROL
)) {
2424 HttpHdrScTarget
*sctusable
= httpHdrScGetMergedTarget (rep
->surrogate_control
,
2425 Config
.Accel
.surrogate_id
);
2427 if (!sctusable
|| sctusable
->content
.size() == 0)
2428 /* Nothing generic or targeted at us, or no
2429 * content processing requested
2433 if (sctusable
->content
.pos("ESI/1.0") != NULL
)
2436 httpHdrScTargetDestroy (sctusable
);
2442 #endif /* USE_SQUID_ESI == 1 */