3 * $Id: ESI.cc,v 1.27 2007/12/14 23:11:45 amosjeffries Exp $
5 * DEBUG: section 86 ESI processing
6 * AUTHOR: Robert Collins
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 ; but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
34 * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
39 #include "clientStream.h"
40 #include "client_side_request.h"
41 #include "errorpage.h"
42 #include "ESISegment.h"
43 #include "ESIElement.h"
44 #include "ESIContext.h"
45 #include "HttpHdrSc.h"
46 #include "HttpHdrScTarget.h"
47 #include "HttpReply.h"
48 #include "ESIAttempt.h"
49 #include "ESIExcept.h"
50 #include "client_side.h"
51 #include "ESIVarState.h"
52 #include "ESIAssign.h"
53 #include "ESIExpression.h"
54 #include "HttpRequest.h"
56 #include "IPAddress.h"
58 /* quick reference on behaviour here.
59 * The ESI specification 1.0 requires the ESI processor to be able to
60 * return an error code at any point in the processing. To that end
61 * we buffer the incoming esi body until we know we will be able to
62 * satisfy the request. At that point we start streaming the queued
67 class ESIStreamContext
;
69 /* TODO: split this out into separate files ? */
70 /* Parsing: quick and dirty. ESI files are not valid XML, so a generic
71 * XML parser is not much use. Also we need a push parser not a pull
72 * parser, so LibXML is out.
74 * Interpreter methods:
75 * Render: May only ever be called after Process returns PROCESS_COMPLETE.
76 * Renders the resulting content into a ESISegment chain.
77 * Process: returns the status of the node.
78 * COMPLETE - processing is complete, rendering may staret
79 * PENDING_WONTFAIL - process is incomplete, but the element *will*
80 * be able to be rendered given time.
81 * PENDING_MAYFAIL - processing is incomplete, and the element *may*
82 * fail to be able to rendered.
83 * FAILED - processing failed, return an error to the client.
87 * NOT TODO: esi:inline - out of scope.
90 /* make comparisons with refcount pointers easy */
91 bool operator == (ESIElement
const *lhs
, ESIElement::Pointer
const &rhs
)
93 return lhs
== rhs
.getRaw();
96 typedef ESIContext::esiKick_t esiKick_t
;
99 /* some core operators */
103 struct esiComment
: public ESIElement
105 MEMPROXY_CLASS(esiComment
);
108 Pointer
makeCacheable() const;
109 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
111 void render(ESISegment::Pointer
);
115 MEMPROXY_CLASS_INLINE(esiComment
)
117 #include "ESILiteral.h"
119 #include "ESISequence.h"
121 #include "ESIInclude.h"
125 class esiRemove
: public ESIElement
129 void *operator new (size_t byteCount
);
130 void operator delete (void *address
);
133 void render(ESISegment::Pointer
);
134 bool addElement (ESIElement::Pointer
);
135 Pointer
makeCacheable() const;
136 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
140 CBDATA_TYPE (esiRemove
);
141 static FREE esiRemoveFree
;
142 static ESIElement
* esiRemoveNew(void);
147 struct esiTry
: public ESIElement
149 MEMPROXY_CLASS(esiTry
);
151 esiTry(esiTreeParentPtr aParent
);
154 void render(ESISegment::Pointer
);
155 bool addElement (ESIElement::Pointer
);
156 void fail(ESIElement
*, char const * = NULL
);
157 esiProcessResult_t
process (int dovars
);
158 void provideData (ESISegment::Pointer data
, ESIElement
* source
);
159 Pointer
makeCacheable() const;
160 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
162 ESIElement::Pointer attempt
;
163 ESIElement::Pointer except
;
169 1; /* the attempt branch process correctly */
175 1; /* The attempt branch failed */
178 1; /* the except branch failed */
186 esiTreeParentPtr parent
;
187 ESISegment::Pointer exceptbuffer
;
188 esiTry (esiTry
const &);
189 esiProcessResult_t
bestAttemptRV() const;
192 MEMPROXY_CLASS_INLINE(esiTry
)
198 struct esiChoose
: public ESIElement
200 MEMPROXY_CLASS(esiChoose
);
202 esiChoose(esiTreeParentPtr
);
205 void render(ESISegment::Pointer
);
206 bool addElement (ESIElement::Pointer
);
207 void fail(ESIElement
*, char const * = NULL
);
208 esiProcessResult_t
process (int dovars
);
210 void provideData (ESISegment::Pointer data
, ESIElement
*source
);
211 void makeCachableElements(esiChoose
const &old
);
212 void makeUsableElements(esiChoose
const &old
, ESIVarState
&);
213 Pointer
makeCacheable() const;
214 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
217 ElementList elements
;
219 ESIElement::Pointer otherwise
;
223 esiChoose(esiChoose
const &);
224 esiTreeParentPtr parent
;
225 void checkValidSource (ESIElement::Pointer source
) const;
226 void selectElement();
229 MEMPROXY_CLASS_INLINE(esiChoose
)
233 struct esiWhen
: public esiSequence
235 MEMPROXY_CLASS(esiWhen
);
236 esiWhen(esiTreeParentPtr aParent
, int attributes
, const char **attr
, ESIVarState
*);
238 Pointer
makeCacheable() const;
239 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
241 bool testsTrue() const { return testValue
;}
243 void setTestResult(bool aBool
) {testValue
= aBool
;}
246 esiWhen (esiWhen
const &);
248 char const *unevaluatedExpression
;
249 ESIVarState
*varState
;
253 MEMPROXY_CLASS_INLINE(esiWhen
)
257 struct esiOtherwise
: public esiSequence
259 // void *operator new (size_t byteCount);
260 // void operator delete (void *address);
261 esiOtherwise(esiTreeParentPtr aParent
) : esiSequence (aParent
) {}}
265 CBDATA_CLASS_INIT(ESIContext
);
267 void ESIContext::startRead()
273 void ESIContext::finishRead()
279 bool ESIContext::reading() const
285 ESIStreamContext::ESIStreamContext() : finished(false), include (NULL
), localbuffer (new ESISegment
), buffer (NULL
)
288 /* Local functions */
290 static ESIContext
*ESIContextNew(HttpReply
*, clientStreamNode
*, ClientHttpRequest
*);
294 ESIContext::operator new(size_t byteCount
)
296 assert (byteCount
== sizeof (ESIContext
));
297 CBDATA_INIT_TYPE(ESIContext
);
298 ESIContext
*result
= cbdataAlloc(ESIContext
);
303 ESIContext::operator delete (void *address
)
305 ESIContext
*t
= static_cast<ESIContext
*>(address
);
310 ESIContext::setError()
313 errorstatus
= HTTP_INTERNAL_SERVER_ERROR
;
318 ESIContext::appendOutboundData(ESISegment::Pointer theData
)
320 if (!outbound
.getRaw()) {
322 outboundtail
= outbound
;
324 assert (outboundtail
->next
.getRaw() == NULL
);
325 outboundtail
->next
= theData
;
329 debugs(86, 9, "ESIContext::appendOutboundData: outbound " << outbound
.getRaw());
333 ESIContext::provideData (ESISegment::Pointer theData
, ESIElement
* source
)
335 debugs(86, 5, "ESIContext::provideData: " << this << " " << theData
.getRaw() << " " << source
);
336 /* No callbacks permitted after finish() called on the tree */
337 assert (tree
.getRaw());
338 assert (source
== tree
);
339 appendOutboundData(theData
);
347 ESIContext::fail (ESIElement
* source
, char const *anError
)
350 setErrorMessage (anError
);
356 ESIContext::fixupOutboundTail()
358 /* TODO: fixup thisNode outboundtail dross a little */
360 if (outboundtail
.getRaw())
361 outboundtail
= outboundtail
->tail();
370 debugs(86, 5, "esiKick: Re-entered whilst in progress");
371 // return ESI_KICK_INPROGRESS;
376 /* we've been detached from - we can't do anything more */
377 return ESI_KICK_FAILED
;
379 /* Something has occured. Process any remaining nodes */
381 /* Process some of our data */
382 switch (process ()) {
384 case ESI_PROCESS_COMPLETE
:
385 debugs(86, 5, "esiKick: esiProcess OK");
388 case ESI_PROCESS_PENDING_WONTFAIL
:
389 debugs(86, 5, "esiKick: esiProcess PENDING OK");
392 case ESI_PROCESS_PENDING_MAYFAIL
:
393 debugs(86, 5, "esiKick: esiProcess PENDING UNKNOWN");
396 case ESI_PROCESS_FAILED
:
397 debugs(86, 2, "esiKick: esiProcess " << this << " 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
407 return ESI_KICK_FAILED
;
410 /* Render if we can to get maximal sent data */
411 assert (tree
.getRaw() || flags
.error
);
413 if (!flags
.finished
&& !outbound
.getRaw()) {
414 outboundtail
= new ESISegment
;
415 outbound
= outboundtail
;
418 if (!flags
.error
&& !flags
.finished
)
419 tree
->render(outboundtail
);
424 /* Is there data to send? */
426 /* some data was sent. we're finished until the next read */
428 return ESI_KICK_SENT
;
432 /* nothing to send */
433 return flags
.error
? ESI_KICK_FAILED
: ESI_KICK_PENDING
;
436 /* request from downstream for more data
439 esiStreamRead (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
441 clientStreamNode
*next
;
442 /* Test preconditions */
443 assert (thisNode
!= NULL
);
444 assert (cbdataReferenceValid (thisNode
));
445 /* we are not in the chain until ESI is detected on a data callback */
446 assert (thisNode
->node
.prev
!= NULL
);
447 assert (thisNode
->node
.next
!= NULL
);
449 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
450 assert (context
.getRaw() != NULL
);
452 if (context
->flags
.passthrough
) {
453 /* passthru mode - read into supplied buffers */
454 next
= thisNode
->next();
455 clientStreamRead (thisNode
, http
, next
->readBuffer
);
459 context
->flags
.clientwantsdata
= 1;
460 debugs(86, 5, "esiStreamRead: Client now wants data");
462 /* Ok, not passing through */
464 switch (context
->kick ()) {
466 case ESIContext::ESI_KICK_FAILED
:
467 /* this can not happen - processing can't fail until we have data,
468 * and when we come here we have sent data to the client
471 case ESIContext::ESI_KICK_SENT
:
473 case ESIContext::ESI_KICK_INPROGRESS
:
476 case ESIContext::ESI_KICK_PENDING
:
480 /* Nothing to send */
482 if (context
->flags
.oktosend
&& (context
->flags
.finishedtemplate
483 || context
->cachedASTInUse
) &&
484 ! context
->flags
.finished
) {
485 /* we've started sending, finished reading, but not finished
486 * processing. stop here, a callback will resume the stream
489 debugs(86, 5, "esiStreamRead: Waiting for async resume of esi processing");
493 if (context
->flags
.oktosend
&& context
->flags
.finished
&& context
->outbound
.getRaw()) {
494 debugs(86, 5, "all processing complete, but outbound data still buffered");
495 assert (!context
->flags
.clientwantsdata
);
496 /* client MUST be processing the last reply */
501 if (context
->flags
.oktosend
&& context
->flags
.finished
) {
502 StoreIOBuffer tempBuffer
;
503 assert (!context
->outbound
.getRaw());
504 /* We've finished processing, and there is no more data buffered */
505 debugs(86, 5, "Telling recipient EOF on READ");
506 clientStreamCallback (thisNode
, http
, NULL
, tempBuffer
);
510 if (context
->reading())
513 /* no data that is ready to send, and still reading? well, lets get some */
514 /* secure a buffer */
515 if (!context
->incoming
.getRaw()) {
516 /* create a new buffer segment */
517 context
->buffered
= new ESISegment
;
518 context
->incoming
= context
->buffered
;
521 assert (context
->incoming
.getRaw() && context
->incoming
->len
!= HTTP_REQBUF_SZ
);
523 StoreIOBuffer tempBuffer
;
524 tempBuffer
.offset
= context
->readpos
;
525 tempBuffer
.length
= context
->incoming
->len
- HTTP_REQBUF_SZ
;
526 tempBuffer
.data
= &context
->incoming
->buf
[context
->incoming
->len
];
527 context
->startRead();
528 clientStreamRead (thisNode
, http
, tempBuffer
);
532 clientStream_status_t
533 esiStreamStatus (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
535 /* Test preconditions */
536 assert (thisNode
!= NULL
);
537 assert (cbdataReferenceValid (thisNode
));
538 /* we are not in the chain until ESI is detected on a data callback */
539 assert (thisNode
->node
.prev
!= NULL
);
540 assert (thisNode
->node
.next
!= NULL
);
542 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
543 assert (context
.getRaw() != NULL
);
545 if (context
->flags
.passthrough
)
546 return clientStreamStatus (thisNode
, http
);
548 if (context
->flags
.oktosend
&& context
->flags
.finished
&&
549 !(context
->outbound
.getRaw() && context
->outbound_offset
< context
->outbound
->len
)) {
550 debugs(86, 5, "Telling recipient EOF on STATUS");
551 return STREAM_UNPLANNED_COMPLETE
; /* we don't know lengths in advance */
554 /* ?? RC: we can't be aborted / fail ? */
559 esiAlwaysPassthrough(http_status sline
)
565 case HTTP_CONTINUE
: /* Should never reach us... but squid needs to alter to accomodate this */
567 case HTTP_SWITCHING_PROTOCOLS
: /* Ditto */
569 case HTTP_PROCESSING
: /* Unknown - some extension */
571 case HTTP_NO_CONTENT
: /* no body, no esi */
573 case HTTP_NOT_MODIFIED
: /* ESI does not affect assembled page headers, so 304s are valid */
586 ESIContext::trimBlanks()
588 /* trim leading empty buffers ? */
590 while (outbound
.getRaw() && outbound
->next
.getRaw() && !outbound
->len
) {
591 debugs(86, 5, "ESIContext::trimBlanks: " << this <<
592 " skipping segment " << outbound
.getRaw());
593 outbound
= outbound
->next
;
596 if (outboundtail
.getRaw())
597 assert (outbound
.getRaw());
600 /* Send data downstream
601 * Returns 0 if nothing was sent. Non-zero if data was sent.
606 debugs(86, 5, "ESIContext::send: this=" << this);
607 /* send any processed data */
611 if (!flags
.clientwantsdata
) {
612 debugs(86, 5, "ESIContext::send: Client does not want data - not sending anything");
616 if (tree
.getRaw() && tree
->mayFail()) {
617 debugs(86, 5, "ESIContext::send: Tree may fail. Not sending.");
624 if (!flags
.oktosend
) {
626 fatal("ESIContext::send: Not OK to send.\n");
632 if (!(rep
|| (outbound
.getRaw() &&
633 outbound
->len
&& (outbound_offset
<= outbound
->len
)))) {
634 debugs(86, 5, "ESIContext::send: Nothing to send.");
638 debugs(86, 5, "ESIContext::send: Sending something...");
639 /* Yes! Send it without asking for more upstream */
640 /* memcopying because the client provided the buffer */
641 /* TODO: skip data until pos == next->readoff; */
642 assert (thisNode
->data
== this);
643 clientStreamNode
*next
= thisNode
->next();
644 ESIContext
*templock
= cbdataReference (this);
647 if (outbound
.getRaw())
648 len
= min (next
->readBuffer
.length
, outbound
->len
- outbound_offset
);
650 /* prevent corruption on range requests, even though we don't support them yet */
651 assert (pos
== next
->readBuffer
.offset
);
653 /* We must send data or a reply */
654 assert (len
!= 0 || rep
!= NULL
);
657 xmemcpy (next
->readBuffer
.data
, &outbound
->buf
[outbound_offset
], len
);
659 if (len
+ outbound_offset
== outbound
->len
) {
660 ESISegment::Pointer temp
= outbound
->next
;
661 /* remove the used buffer */
668 if (!outbound
.getRaw())
674 flags
.clientwantsdata
= 0;
675 debugs(86, 5, "ESIContext::send: this=" << this << " Client no longer wants data ");
676 /* Deal with re-entrancy */
677 HttpReply
*temprep
= rep
;
678 rep
= NULL
; /* freed downstream */
680 if (temprep
&& varState
)
681 varState
->buildVary (temprep
);
684 StoreIOBuffer tempBuffer
;
685 tempBuffer
.length
= len
;
686 tempBuffer
.offset
= pos
- len
;
687 tempBuffer
.data
= next
->readBuffer
.data
;
688 clientStreamCallback (thisNode
, http
, temprep
, tempBuffer
);
692 len
= 1; /* tell the caller we sent something (because we sent headers */
695 cbdataReferenceDone (templock
);
697 debugs (86,5,"ESIContext::send: this=" << this << " sent " << len
);
703 ESIContext::finishChildren()
711 /* Detach event from a client Stream */
713 esiStreamDetach (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
715 /* if we have pending callbacks, tell them we're done. */
716 /* test preconditions */
717 assert (thisNode
!= NULL
);
718 assert (cbdataReferenceValid (thisNode
));
719 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
720 assert (context
.getRaw() != NULL
);
721 /* detach from the stream */
722 clientStreamDetach (thisNode
,http
);
723 /* if we have pending callbacks (from subincludes), tell them we're done. */
724 context
->thisNode
= NULL
;
725 context
->flags
.detached
= 1;
726 context
->finishChildren();
727 /* HACK for parser stack not being emptied */
728 context
->parserState
.stack
[0] = NULL
;
729 /* allow refcount logic to trigger */
730 context
->cbdataLocker
= NULL
;
733 /* Process incoming data for ESI tags */
734 /* ESI TODO: Long term: we should have a framework to parse html/xml and
735 * callback to a set of processors like thisNode, to prevent multiple parsing
736 * overhead. More thoughts on thisNode: We have to parse multiple times, because
737 * the output of one processor may create a very different tree. What we could
738 * do is something like DOM and pass that down to a final renderer. This is
739 * getting into web server territory though...
742 * This is not the last node in the stream.
743 * ESI processing has been enabled.
744 * There is context data or a reply structure
747 esiProcessStream (clientStreamNode
*thisNode
, ClientHttpRequest
*http
, HttpReply
*rep
, StoreIOBuffer receivedData
)
749 /* test preconditions */
750 assert (thisNode
!= NULL
);
751 /* ESI TODO: handle thisNode rather than asserting - it should only ever
752 * happen if we cause an abort and the callback chain
753 * loops back to here, so we can simply return. However, that itself
754 * shouldn't happen, so it stays as an assert for now. */
755 assert (cbdataReferenceValid (thisNode
));
757 * if data is NULL thisNode is the first entrance. If rep is also NULL,
758 * something is wrong.
760 assert (thisNode
->data
.getRaw() != NULL
|| rep
);
761 assert (thisNode
->node
.next
!= NULL
);
763 if (!thisNode
->data
.getRaw())
764 /* setup ESI context from reply headers */
765 thisNode
->data
= ESIContextNew(rep
, thisNode
, http
);
767 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
769 assert (context
.getRaw() != NULL
);
771 context
->finishRead();
773 /* Skipping all ESI processing. All remaining data gets untouched.
774 * Mainly used when an error or other non-ESI processable entity
775 * has been detected to prevent ESI processing the error body
777 if (context
->flags
.passthrough
) {
778 clientStreamCallback (thisNode
, http
, rep
, receivedData
);
782 debugs(86, 3, "esiProcessStream: Processing thisNode " << thisNode
<<
783 " context " << context
.getRaw() << " offset " <<
784 (int) receivedData
.offset
<< " length " <<
785 (unsigned int)receivedData
.length
);
787 /* once we finish the template, we *cannot* return here */
788 assert (!context
->flags
.finishedtemplate
);
789 assert (!context
->cachedASTInUse
);
791 /* Can we generate any data ?*/
793 if (receivedData
.data
) {
794 /* Increase our buffer area with incoming data */
795 assert (receivedData
.length
<= HTTP_REQBUF_SZ
);
796 assert (thisNode
->readBuffer
.offset
== receivedData
.offset
);
797 debugs (86,5, "esiProcessStream found " << receivedData
.length
<< " bytes of body data at offset " << receivedData
.offset
);
798 /* secure the data for later use */
800 if (!context
->incoming
.getRaw()) {
801 /* create a new buffer segment */
802 debugs(86, 5, "esiProcessStream: Setting up incoming buffer");
803 context
->buffered
= new ESISegment
;
804 context
->incoming
= context
->buffered
;
807 if (receivedData
.data
!= &context
->incoming
->buf
[context
->incoming
->len
]) {
808 /* We have to copy the data out because we didn't supply thisNode buffer */
809 size_t space
= HTTP_REQBUF_SZ
- context
->incoming
->len
;
810 size_t len
= min (space
, receivedData
.length
);
811 debugs(86, 5, "Copying data from " << receivedData
.data
<< " to " <<
812 &context
->incoming
->buf
[context
->incoming
->len
] <<
813 " because our buffer was not used");
815 xmemcpy (&context
->incoming
->buf
[context
->incoming
->len
], receivedData
.data
, len
);
816 context
->incoming
->len
+= len
;
818 if (context
->incoming
->len
== HTTP_REQBUF_SZ
) {
819 /* append another buffer */
820 context
->incoming
->next
= new ESISegment
;
821 context
->incoming
= context
->incoming
->next
;
824 if (len
!= receivedData
.length
) {
825 /* capture the remnants */
826 xmemcpy (context
->incoming
->buf
, &receivedData
.data
[len
], receivedData
.length
- len
);
827 context
->incoming
->len
= receivedData
.length
- len
;
830 /* and note where we are up to */
831 context
->readpos
+= receivedData
.length
;
833 /* update our position counters, and if needed assign a new buffer */
834 context
->incoming
->len
+= receivedData
.length
;
835 assert (context
->incoming
->len
<= HTTP_REQBUF_SZ
);
837 if (context
->incoming
->len
> HTTP_REQBUF_SZ
* 3 / 4) {
838 /* allocate a new buffer - to stop us asking for ridiculously small amounts */
839 context
->incoming
->next
= new ESISegment
;
840 context
->incoming
= context
->incoming
->next
;
843 context
->readpos
+= receivedData
.length
;
847 /* EOF / Read error / aborted entry */
848 if (rep
== NULL
&& receivedData
.data
== NULL
&& receivedData
.length
== 0 && !context
->flags
.finishedtemplate
) {
849 /* TODO: get stream status to test the entry for aborts */
850 /* else flush the esi processor */
851 debugs(86, 5, "esiProcess: " << context
.getRaw() << " Finished reading upstream data");
852 /* This is correct */
853 context
->flags
.finishedtemplate
= 1;
856 switch (context
->kick()) {
858 case ESIContext::ESI_KICK_FAILED
:
859 /* thisNode can not happen - processing can't fail until we have data,
860 * and when we come here we have sent data to the client
864 case ESIContext::ESI_KICK_SENT
:
866 case ESIContext::ESI_KICK_INPROGRESS
:
869 case ESIContext::ESI_KICK_PENDING
:
873 /* ok.. no data sent, try to pull more data in from upstream.
874 * FIXME: Don't try thisNode if we have finished reading the template
876 if (!context
->flags
.finishedtemplate
&& !context
->reading()
877 && !context
->cachedASTInUse
) {
878 StoreIOBuffer tempBuffer
;
879 assert (context
->incoming
.getRaw() && context
->incoming
->len
< HTTP_REQBUF_SZ
);
880 tempBuffer
.offset
= context
->readpos
;
881 tempBuffer
.length
= HTTP_REQBUF_SZ
- context
->incoming
->len
;
882 tempBuffer
.data
= &context
->incoming
->buf
[context
->incoming
->len
];
883 context
->startRead();
884 clientStreamRead (thisNode
, http
, tempBuffer
);
888 debugs(86, 3, "esiProcessStream: no data to send, no data to read, awaiting a callback");
891 ESIContext::~ESIContext()
894 /* Not freed by freeresources because esi::fail needs it */
895 safe_free (errormessage
);
896 debugs(86, 3, "ESIContext::~ESIContext: Freed " << this);
900 ESIContextNew (HttpReply
*rep
, clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
903 ESIContext
*rv
= new ESIContext
;
905 rv
->cbdataLocker
= rv
;
907 if (esiAlwaysPassthrough(rep
->sline
.status
)) {
908 rv
->flags
.passthrough
= 1;
910 /* remove specific headers for ESI to prevent
911 * downstream cache confusion */
912 HttpHeader
*hdr
= &rep
->header
;
913 hdr
->delById(HDR_ACCEPT_RANGES
);
914 hdr
->delById(HDR_ETAG
);
915 hdr
->delById(HDR_CONTENT_LENGTH
);
916 hdr
->delById(HDR_CONTENT_MD5
);
917 rv
->tree
= new esiSequence (rv
, true);
918 rv
->thisNode
= thisNode
;
920 rv
->flags
.clientwantsdata
= 1;
921 rv
->varState
= new ESIVarState (&http
->request
->header
, http
->uri
);
922 debugs(86, 5, "ESIContextNew: Client wants data (always created during reply cycle");
925 debugs(86, 5, "ESIContextNew: Create context " << rv
);
929 ESIElement::ESIElementType_t
930 ESIElement::IdentifyElement (const char *el
)
936 return ESI_ELEMENT_NONE
;
938 if (!strncmp (el
, "esi:", 4))
940 else if (!strncmp (el
, "http://www.edge-delivery.org/esi/1.0|", 37))
943 return ESI_ELEMENT_NONE
;
945 if (!strncmp (el
+ offset
, "otherwise", 9))
946 return ESI_ELEMENT_OTHERWISE
;
948 if (!strncmp (el
+ offset
, "comment", 7))
949 return ESI_ELEMENT_COMMENT
;
951 if (!strncmp (el
+ offset
, "include", 7))
952 return ESI_ELEMENT_INCLUDE
;
954 if (!strncmp (el
+ offset
, "attempt", 7))
955 return ESI_ELEMENT_ATTEMPT
;
957 if (!strncmp (el
+ offset
, "assign", 6))
958 return ESI_ELEMENT_ASSIGN
;
960 if (!strncmp (el
+ offset
, "remove", 6))
961 return ESI_ELEMENT_REMOVE
;
963 if (!strncmp (el
+ offset
, "except", 6))
964 return ESI_ELEMENT_EXCEPT
;
966 if (!strncmp (el
+ offset
, "choose", 6))
967 return ESI_ELEMENT_CHOOSE
;
969 if (!strncmp (el
+ offset
, "vars", 4))
970 return ESI_ELEMENT_VARS
;
972 if (!strncmp (el
+ offset
, "when", 4))
973 return ESI_ELEMENT_WHEN
;
975 if (!strncmp (el
+ offset
, "try", 3))
976 return ESI_ELEMENT_TRY
;
978 return ESI_ELEMENT_NONE
;
982 ESIContext::ParserState::top()
984 return stack
[stackdepth
-1];
987 ESIContext::ParserState::ParserState() : inited_ (false)
991 ESIContext::ParserState::inited() const
997 ESIContext::addStackElement (ESIElement::Pointer element
)
999 /* Put on the stack to allow skipping of 'invalid' markup */
1000 assert (parserState
.stackdepth
<11);
1002 debugs(86, 5, "ESIContext::addStackElement: About to add ESI Node " << element
.getRaw());
1004 if (!parserState
.top()->addElement(element
)) {
1005 debugs(86, 1, "ESIContext::addStackElement: failed to add esi node, probable error in ESI template");
1008 /* added ok, push onto the stack */
1009 parserState
.stack
[parserState
.stackdepth
++] = element
;
1014 ESIContext::start(const char *el
, const char **attr
, size_t attrCount
)
1017 unsigned int ellen
= strlen (el
);
1018 char localbuf
[HTTP_REQBUF_SZ
];
1019 ESIElement::Pointer element
;
1020 int specifiedattcount
= attrCount
* 2;
1022 assert (ellen
< sizeof (localbuf
)); /* prevent unexpected overruns. */
1024 debugs(86, 5, "ESIContext::Start: element '" << el
<< "' with " << specifiedattcount
<< " tags");
1027 /* waiting for expat to finish the buffer we gave it */
1030 switch (ESIElement::IdentifyElement (el
)) {
1032 case ESIElement::ESI_ELEMENT_NONE
:
1033 /* Spit out elements we aren't interested in */
1036 assert (xstrncpy (&localbuf
[1], el
, sizeof(localbuf
) - 2));
1037 pos
= localbuf
+ strlen (localbuf
);
1039 for (i
= 0; i
< specifiedattcount
&& attr
[i
]; i
+= 2) {
1041 /* TODO: handle thisNode gracefully */
1042 assert (xstrncpy (pos
, attr
[i
], sizeof(localbuf
) + (pos
- localbuf
)));
1043 pos
+= strlen (pos
);
1046 assert (xstrncpy (pos
, attr
[i
+ 1], sizeof(localbuf
) + (pos
- localbuf
)));
1047 pos
+= strlen (pos
);
1054 addLiteral (localbuf
, pos
- localbuf
);
1055 debugs(86, 5, "esi stack depth " << parserState
.stackdepth
);
1059 case ESIElement::ESI_ELEMENT_COMMENT
:
1060 /* Put on the stack to allow skipping of 'invalid' markup */
1061 element
= new esiComment ();
1064 case ESIElement::ESI_ELEMENT_INCLUDE
:
1065 /* Put on the stack to allow skipping of 'invalid' markup */
1066 element
= new ESIInclude (parserState
.top().getRaw(), specifiedattcount
, attr
, this);
1069 case ESIElement::ESI_ELEMENT_REMOVE
:
1070 /* Put on the stack to allow skipping of 'invalid' markup */
1071 element
= esiRemoveNew ();
1074 case ESIElement::ESI_ELEMENT_TRY
:
1075 /* Put on the stack to allow skipping of 'invalid' markup */
1076 element
= new esiTry (parserState
.top().getRaw());
1079 case ESIElement::ESI_ELEMENT_ATTEMPT
:
1080 /* Put on the stack to allow skipping of 'invalid' markup */
1081 element
= new esiAttempt (parserState
.top().getRaw());
1084 case ESIElement::ESI_ELEMENT_EXCEPT
:
1085 /* Put on the stack to allow skipping of 'invalid' markup */
1086 element
= new esiExcept (parserState
.top().getRaw());
1089 case ESIElement::ESI_ELEMENT_VARS
:
1090 /* Put on the stack to allow skipping of 'invalid' markup */
1091 element
= new ESIVar (parserState
.top().getRaw());
1094 case ESIElement::ESI_ELEMENT_CHOOSE
:
1095 /* Put on the stack to allow skipping of 'invalid' markup */
1096 element
= new esiChoose (parserState
.top().getRaw());
1099 case ESIElement::ESI_ELEMENT_WHEN
:
1100 /* Put on the stack to allow skipping of 'invalid' markup */
1101 element
= new esiWhen (parserState
.top().getRaw(), specifiedattcount
, attr
, varState
);
1104 case ESIElement::ESI_ELEMENT_OTHERWISE
:
1105 /* Put on the stack to allow skipping of 'invalid' markup */
1106 element
= new esiOtherwise (parserState
.top().getRaw());
1109 case ESIElement::ESI_ELEMENT_ASSIGN
:
1110 /* Put on the stack to allow skipping of 'invalid' markup */
1111 element
= new ESIAssign (parserState
.top().getRaw(), specifiedattcount
, attr
, this);
1115 addStackElement(element
);
1117 debugs(86, 5, "esi stack depth " << parserState
.stackdepth
);
1119 } /* End of start handler */
1122 ESIContext::end(const char *el
)
1124 unsigned int ellen
= strlen (el
);
1125 char localbuf
[HTTP_REQBUF_SZ
];
1129 /* waiting for expat to finish the buffer we gave it */
1132 switch (ESIElement::IdentifyElement (el
)) {
1134 case ESIElement::ESI_ELEMENT_NONE
:
1135 assert (ellen
< sizeof (localbuf
)); /* prevent unexpected overruns. */
1136 /* Add elements we aren't interested in */
1139 assert (xstrncpy (&localbuf
[2], el
, sizeof(localbuf
) - 3));
1140 pos
= localbuf
+ strlen (localbuf
);
1143 addLiteral (localbuf
, pos
- localbuf
);
1146 case ESIElement::ESI_ELEMENT_COMMENT
:
1148 case ESIElement::ESI_ELEMENT_INCLUDE
:
1150 case ESIElement::ESI_ELEMENT_REMOVE
:
1152 case ESIElement::ESI_ELEMENT_TRY
:
1154 case ESIElement::ESI_ELEMENT_ATTEMPT
:
1156 case ESIElement::ESI_ELEMENT_EXCEPT
:
1158 case ESIElement::ESI_ELEMENT_VARS
:
1160 case ESIElement::ESI_ELEMENT_CHOOSE
:
1162 case ESIElement::ESI_ELEMENT_WHEN
:
1164 case ESIElement::ESI_ELEMENT_OTHERWISE
:
1166 case ESIElement::ESI_ELEMENT_ASSIGN
:
1167 /* pop of the stack */
1168 parserState
.stack
[--parserState
.stackdepth
] = NULL
;
1171 } /* End of end handler */
1174 ESIContext::parserDefault (const char *s
, int len
)
1179 /* handle any skipped data */
1180 addLiteral (s
, len
);
1184 ESIContext::parserComment (const char *s
)
1189 if (!strncmp(s
, "esi",3)) {
1190 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block encountered");
1191 ESIParser::Pointer tempParser
= ESIParser::NewParser (this);
1193 /* wrap the comment in some tags */
1195 if (!tempParser
->parse("<div>", 5,0) ||
1196 !tempParser
->parse(s
+ 3, strlen(s
) - 3, 0) ||
1197 !tempParser
->parse("</div>",6,1)) {
1198 debugs(86, 0, "ESIContext::parserComment: Parsing fragment '" << s
+ 3 << "' failed.");
1201 snprintf(tempstr
, 1023, "ESIContext::parserComment: Parse error at line %ld:\n%s\n",
1202 tempParser
->lineNumber(),
1203 tempParser
->errorString());
1204 debugs(86, 0, "" << tempstr
<< "");
1206 setErrorMessage(tempstr
);
1209 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block parsed");
1212 char localbuf
[HTTP_REQBUF_SZ
];
1214 debugs(86, 5, "ESIContext::parserComment: Regenerating comment block");
1217 if (len
> sizeof (localbuf
) - 9) {
1218 debugs(86, 0, "ESIContext::parserComment: Truncating long comment");
1219 len
= sizeof (localbuf
) - 9;
1222 xstrncpy(localbuf
, "<!--", 5);
1223 xstrncpy(localbuf
+ 4, s
, len
+ 1);
1224 xstrncpy(localbuf
+ 4 + len
, "-->", 4);
1225 addLiteral (localbuf
,len
+ 7);
1230 ESIContext::addLiteral (const char *s
, int len
)
1232 /* handle any skipped data */
1234 debugs(86, 5, "literal length is " << len
);
1235 /* give a literal to the current element */
1236 assert (parserState
.stackdepth
<11);
1237 ESIElement::Pointer
element (new esiLiteral (this, s
, len
));
1239 if (!parserState
.top()->addElement(element
)) {
1240 debugs(86, 1, "ESIContext::addLiteral: failed to add esi node, probable error in ESI template");
1246 ESIContext::ParserState::init(ESIParserClient
*userData
)
1248 theParser
= ESIParser::NewParser (userData
);
1253 ESIContext::parseOneBuffer()
1255 assert (buffered
.getRaw());
1257 debugs (86,9,"ESIContext::parseOneBuffer: " << buffered
->len
<< " bytes");
1258 bool lastBlock
= buffered
->next
.getRaw() == NULL
&& flags
.finishedtemplate
? true : false;
1260 if (! parserState
.theParser
->parse(buffered
->buf
, buffered
->len
, lastBlock
)) {
1263 snprintf (tempstr
, 1023, "esiProcess: Parse error at line %ld:\n%s\n",
1264 parserState
.theParser
->lineNumber(),
1265 parserState
.theParser
->errorString());
1266 debugs(86, 0, "" << tempstr
<< "");
1268 setErrorMessage(tempstr
);
1270 assert (flags
.error
);
1280 ESISegment::Pointer temp
= buffered
;
1281 buffered
= temp
->next
;
1287 if (!parserState
.stackdepth
) {
1288 debugs(86, 5, "empty parser stack, inserting the top level node");
1289 assert (tree
.getRaw());
1290 parserState
.stack
[parserState
.stackdepth
++] = tree
;
1293 if (rep
&& !parserState
.inited())
1294 parserState
.init(this);
1297 if (buffered
.getRaw()) {
1298 parserState
.parsing
= 1;
1299 /* we don't keep any data around */
1301 PROF_start(esiParsing
);
1303 while (buffered
.getRaw() && !flags
.error
)
1306 PROF_stop(esiParsing
);
1308 /* Tel the read code to allocate a new buffer */
1311 parserState
.parsing
= 0;
1316 ESIContext::process ()
1319 * read through buffered, skipping plain text, and skipping any
1320 * <...> entry that is not an <esi: entry.
1321 * when it's found, hand an esiLiteral of the preceeding data to our current
1325 if (parserState
.parsing
) {
1326 /* in middle of parsing - finish here */
1327 return ESI_PROCESS_PENDING_MAYFAIL
;
1330 assert (flags
.finished
== 0);
1332 assert (!flags
.error
);
1334 if (!hasCachedAST())
1336 else if (!flags
.finishedtemplate
)
1340 debugs(86, 5, "ESIContext::process: Parsing failed");
1342 parserState
.popAll();
1343 return ESI_PROCESS_FAILED
;
1346 if (!flags
.finishedtemplate
&& !incoming
.getRaw() && !cachedASTInUse
) {
1347 buffered
= new ESISegment
;
1348 incoming
= buffered
;
1351 if (!flags
.finishedtemplate
&& !cachedASTInUse
) {
1352 return ESI_PROCESS_PENDING_MAYFAIL
;
1355 assert (flags
.finishedtemplate
|| cachedASTInUse
);
1357 /* ok, we've done all we can with the data. What can we process now?
1360 esiProcessResult_t status
;
1361 PROF_start(esiProcessing
);
1363 status
= tree
->process(0);
1369 case ESI_PROCESS_COMPLETE
:
1370 debugs(86, 5, "esiProcess: tree Processed OK");
1373 case ESI_PROCESS_PENDING_WONTFAIL
:
1374 debugs(86, 5, "esiProcess: tree Processed PENDING OK");
1377 case ESI_PROCESS_PENDING_MAYFAIL
:
1378 debugs(86, 5, "esiProcess: tree Processed PENDING UNKNOWN");
1381 case ESI_PROCESS_FAILED
:
1382 debugs(86, 0, "esiProcess: tree Processed FAILED");
1385 setErrorMessage("esiProcess: ESI template Processing failed.");
1387 PROF_stop(esiProcessing
);
1389 return ESI_PROCESS_FAILED
;
1394 if (status
!= ESI_PROCESS_PENDING_MAYFAIL
&& (flags
.finishedtemplate
|| cachedASTInUse
))
1396 /* We've read the entire template, and no nodes will
1399 debugs(86, 5, "esiProcess, request will succeed");
1403 if (status
== ESI_PROCESS_COMPLETE
1404 && (flags
.finishedtemplate
|| cachedASTInUse
))
1406 /* we've finished all processing. Render and send. */
1407 debugs(86, 5, "esiProcess, processing complete");
1411 PROF_stop(esiProcessing
);
1412 return status
; /* because we have no callbacks */
1417 ESIContext::ParserState::freeResources()
1424 ESIContext::ParserState::popAll()
1427 stack
[--stackdepth
] = NULL
;
1431 ESIContext::freeResources ()
1433 debugs(86, 5, "ESIContext::freeResources: Freeing for this=" << this);
1442 if (parserState
.inited()) {
1443 parserState
.freeResources();
1446 parserState
.popAll();
1447 ESISegmentFreeList (buffered
);
1448 ESISegmentFreeList (outbound
);
1449 ESISegmentFreeList (outboundtail
);
1452 /* don't touch incoming, it's a pointer into buffered anyway */
1455 extern ErrorState
*clientBuildError (err_type
, http_status
, char const *, IPAddress
&, HttpRequest
*);
1458 /* This can ONLY be used before we have sent *any* data to the client */
1462 debugs(86, 5, "ESIContext::fail: this=" << this);
1463 /* check preconditions */
1465 /* cleanup current state */
1467 /* Stop altering thisNode request */
1470 /* don't honour range requests - for errors we send it all */
1472 /* create an error object */
1473 ErrorState
* err
= clientBuildError(errorpage
, errorstatus
, NULL
, http
->getConn()->peer
, http
->request
);
1474 err
->err_msg
= errormessage
;
1475 errormessage
= NULL
;
1476 rep
= errorBuildReply (err
);
1477 assert (rep
->body
.mb
->contentSize() >= 0);
1478 size_t errorprogress
= rep
->body
.mb
->contentSize();
1479 /* Tell esiSend where to start sending from */
1480 outbound_offset
= 0;
1481 /* copy the membuf from the reply to outbound */
1483 while (errorprogress
< (size_t)rep
->body
.mb
->contentSize()) {
1484 appendOutboundData(new ESISegment
);
1485 errorprogress
+= outboundtail
->append(rep
->body
.mb
->content() + errorprogress
, rep
->body
.mb
->contentSize() - errorprogress
);
1488 /* the esiCode now thinks that the error is the outbound,
1489 * and all processing has finished. */
1490 /* Send as much as we can */
1493 /* don't cancel anything. The stream nodes will clean up after
1494 * themselves when the reply is freed - and we don't know what to
1499 /* Implementation of ESIElements */
1502 esiComment::~esiComment()
1504 debugs(86, 5, "esiComment::~esiComment " << this);
1507 esiComment::esiComment()
1511 esiComment::finish()
1515 esiComment::render(ESISegment::Pointer output
)
1517 /* Comments do nothing dude */
1518 debugs(86, 5, "esiCommentRender: Rendering comment " << this << " into " << output
.getRaw());
1522 esiComment::makeCacheable() const
1524 debugs(86, 5, "esiComment::makeCacheable: returning NULL");
1529 esiComment::makeUsable(esiTreeParentPtr
, ESIVarState
&) const
1531 fatal ("esiComment::Usable: unreachable code!\n");
1536 esiLiteral::~esiLiteral()
1538 debugs(86, 5, "esiLiteral::~esiLiteral: " << this);
1539 ESISegmentFreeList (buffer
);
1540 cbdataReferenceDone (varState
);
1543 esiLiteral::esiLiteral(ESISegment::Pointer aSegment
)
1546 /* we've been handed a complete, processed string */
1553 esiLiteral::finish()
1556 /* precondition: the buffer chain has at least start + length bytes of data
1558 esiLiteral::esiLiteral(ESIContext
*context
, const char *s
, int numberOfCharacters
)
1561 buffer
= new ESISegment
;
1562 ESISegment::Pointer local
= buffer
;
1564 int remainingCharacters
= numberOfCharacters
;
1566 while (remainingCharacters
> 0) {
1567 if (local
->len
== sizeof (local
->buf
)) {
1568 local
->next
= new ESISegment
;
1572 size_t len
= local
->append (&s
[start
], remainingCharacters
);
1574 remainingCharacters
-= len
;
1577 varState
= cbdataReference (context
->varState
);
1581 esiLiteral::render (ESISegment::Pointer output
)
1583 debugs(86, 9, "esiLiteral::render: Rendering " << this);
1584 /* append the entire chain */
1585 assert (output
->next
.getRaw() == NULL
);
1586 output
->next
= buffer
;
1591 esiLiteral::process (int dovars
)
1594 return ESI_PROCESS_COMPLETE
;
1597 ESISegment::Pointer temp
= buffer
;
1598 /* Ensure variable state is clean */
1600 while (temp
.getRaw()) {
1601 varState
->feedData(temp
->buf
,temp
->len
);
1605 /* free the pre-processed content */
1606 ESISegmentFreeList (buffer
);
1608 buffer
= varState
->extractList ();
1612 return ESI_PROCESS_COMPLETE
;
1615 esiLiteral::esiLiteral(esiLiteral
const &old
) : buffer (old
.buffer
->cloneList()),
1622 esiLiteral::makeCacheable() const
1624 return new esiLiteral (*this);
1628 esiLiteral::makeUsable(esiTreeParentPtr
, ESIVarState
&newVarState
) const
1630 debugs(86, 5, "esiLiteral::makeUsable: Creating usable literal");
1631 esiLiteral
* result
= new esiLiteral (*this);
1632 result
->varState
= cbdataReference (&newVarState
);
1638 esiRemoveFree (void *data
)
1640 esiRemove
*thisNode
= (esiRemove
*)data
;
1641 debugs(86, 5, "esiRemoveFree " << thisNode
);
1645 esiRemove::operator new(size_t byteCount
)
1647 assert (byteCount
== sizeof (esiRemove
));
1649 CBDATA_INIT_TYPE_FREECB(esiRemove
, esiRemoveFree
);
1650 rv
= (void *)cbdataAlloc (esiRemove
);
1655 esiRemove::operator delete (void *address
)
1657 cbdataFree (address
);
1663 return new esiRemove
;
1666 esiRemove::esiRemove()
1674 esiRemove::render(ESISegment::Pointer output
)
1676 /* Removes do nothing dude */
1677 debugs(86, 5, "esiRemoveRender: Rendering remove " << this);
1680 /* Accept non-ESI children */
1682 esiRemove::addElement (ESIElement::Pointer element
)
1684 if (!dynamic_cast<esiLiteral
*>(element
.getRaw())) {
1685 debugs(86, 5, "esiRemoveAdd: Failed for " << this);
1693 esiRemove::makeCacheable() const
1695 debugs(86, 5, "esiRemove::makeCacheable: Returning NULL");
1700 esiRemove::makeUsable(esiTreeParentPtr
, ESIVarState
&) const
1702 fatal ("esiRemove::Usable: unreachable code!\n");
1709 debugs(86, 5, "esiTry::~esiTry " << this);
1712 esiTry::esiTry(esiTreeParentPtr aParent
) : parent (aParent
) , exceptbuffer(NULL
)
1716 esiTry::render (ESISegment::Pointer output
)
1718 /* Try renders from it's children */
1720 assert (attempt
.getRaw());
1721 assert (except
.getRaw());
1722 debugs(86, 5, "esiTryRender: Rendering Try " << this);
1724 if (flags
.attemptok
) {
1725 attempt
->render(output
);
1726 } else if (flags
.exceptok
) {
1729 if (exceptbuffer
.getRaw())
1730 ESISegment::ListTransfer(exceptbuffer
, output
);
1732 except
->render(output
);
1734 debugs(86, 5, "esiTryRender: Neither except nor attempt succeeded?!?");
1737 /* Accept attempt and except only */
1739 esiTry::addElement(ESIElement::Pointer element
)
1741 debugs(86, 5, "esiTryAdd: Try " << this << " adding element " <<
1744 if (dynamic_cast<esiLiteral
*>(element
.getRaw())) {
1745 /* Swallow whitespace */
1746 debugs(86, 5, "esiTryAdd: Try " << this << " skipping whitespace " << element
.getRaw());
1750 if (dynamic_cast<esiAttempt
*>(element
.getRaw())) {
1751 if (attempt
.getRaw()) {
1752 debugs(86, 1, "esiTryAdd: Failed for " << this << " - try allready has an attempt node (section 3.4)");
1760 if (dynamic_cast<esiExcept
*>(element
.getRaw())) {
1761 if (except
.getRaw()) {
1762 debugs(86, 1, "esiTryAdd: Failed for " << this << " - try already has an except node (section 3.4)");
1770 debugs(86, 1, "esiTryAdd: Failed to add element " << element
.getRaw() << " to try " << this << ", incorrect element type (see section 3.4)");
1775 esiTry::bestAttemptRV() const
1777 if (flags
.attemptfailed
)
1778 return ESI_PROCESS_COMPLETE
;
1780 return ESI_PROCESS_PENDING_MAYFAIL
;
1784 esiTry::process (int dovars
)
1786 esiProcessResult_t rv
= ESI_PROCESS_PENDING_MAYFAIL
;
1789 if (!attempt
.getRaw()) {
1790 debugs(86, 0, "esiTryProcess: Try has no attempt element - ESI template is invalid (section 3.4)");
1791 return ESI_PROCESS_FAILED
;
1794 if (!except
.getRaw()) {
1795 debugs(86, 0, "esiTryProcess: Try has no except element - ESI template is invalid (section 3.4)");
1796 return ESI_PROCESS_FAILED
;
1799 if (!flags
.attemptfailed
)
1800 /* Try the attempt branch */
1801 switch ((rv
= attempt
->process(dovars
))) {
1803 case ESI_PROCESS_COMPLETE
:
1804 debugs(86, 5, "esiTryProcess: attempt Processed OK");
1805 flags
.attemptok
= 1;
1806 return ESI_PROCESS_COMPLETE
;
1808 case ESI_PROCESS_PENDING_WONTFAIL
:
1809 debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
1810 /* We're not done yet, but don't need to test except */
1811 return ESI_PROCESS_PENDING_WONTFAIL
;
1813 case ESI_PROCESS_PENDING_MAYFAIL
:
1814 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
1817 case ESI_PROCESS_FAILED
:
1818 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
1819 flags
.attemptfailed
= 1;
1823 /* attempt is either MAYFAIL or FAILED */
1825 return bestAttemptRV();
1827 /* query except to see if it has a definite result */
1828 if (!flags
.exceptfailed
)
1829 /* Try the except branch */
1830 switch (except
->process(dovars
)) {
1832 case ESI_PROCESS_COMPLETE
:
1833 debugs(86, 5, "esiTryProcess: except Processed OK");
1835 return bestAttemptRV();
1837 case ESI_PROCESS_PENDING_WONTFAIL
:
1838 debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
1839 /* We're not done yet, but can't fail */
1840 return ESI_PROCESS_PENDING_WONTFAIL
;
1842 case ESI_PROCESS_PENDING_MAYFAIL
:
1843 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
1844 /* The except branch fail fail */
1845 return ESI_PROCESS_PENDING_MAYFAIL
;
1847 case ESI_PROCESS_FAILED
:
1848 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
1849 flags
.exceptfailed
= 1;
1853 if (flags
.exceptfailed
&& flags
.attemptfailed
)
1854 return ESI_PROCESS_FAILED
;
1856 /* one of attempt or except returned PENDING MAYFAIL */
1857 return ESI_PROCESS_PENDING_MAYFAIL
;
1861 esiTry::notifyParent()
1863 if (flags
.attemptfailed
) {
1864 if (flags
.exceptok
) {
1865 parent
->provideData (exceptbuffer
, this);
1866 exceptbuffer
= NULL
;
1867 } else if (flags
.exceptfailed
|| except
.getRaw() == NULL
) {
1868 parent
->fail (this, "esi:try - except claused failed, or no except clause found");
1872 /* nothing to do when except fails and attempt hasn't */
1876 esiTry::fail(ESIElement
*source
, char const *anError
)
1879 assert (source
== attempt
|| source
== except
);
1880 debugs(86, 5, "esiTry::fail: this=" << this << ", source=" << source
<< ", message=" << anError
);
1882 if (source
== except
) {
1883 flags
.exceptfailed
= 1;
1885 flags
.attemptfailed
= 1;
1892 esiTry::provideData (ESISegment::Pointer data
, ESIElement
* source
)
1894 if (source
== attempt
) {
1895 flags
.attemptok
= 1;
1896 parent
->provideData (data
, this);
1897 } else if (source
== except
) {
1899 assert (exceptbuffer
== NULL
);
1900 ESISegment::ListTransfer (data
, exceptbuffer
);
1905 esiTry::esiTry(esiTry
const &old
)
1909 flags
.attemptok
= 0;
1911 flags
.attemptfailed
= 0;
1912 flags
.exceptfailed
= 0;
1914 exceptbuffer
= NULL
;
1918 esiTry::makeCacheable() const
1920 debugs(86, 5, "esiTry::makeCacheable: making cachable Try from " << this);
1921 esiTry
*resultT
= new esiTry (*this);
1922 ESIElement::Pointer result
= resultT
;
1924 if (attempt
.getRaw())
1925 resultT
->attempt
= attempt
->makeCacheable();
1927 if (except
.getRaw())
1928 resultT
->except
= except
->makeCacheable();
1934 esiTry::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
1936 debugs(86, 5, "esiTry::makeUsable: making usable Try from " << this);
1937 esiTry
*resultT
= new esiTry (*this);
1938 ESIElement::Pointer result
= resultT
;
1940 resultT
->parent
= newParent
;
1942 if (attempt
.getRaw())
1943 resultT
->attempt
= attempt
->makeUsable(resultT
, newVarState
);
1945 if (except
.getRaw())
1946 resultT
->except
= except
->makeUsable(resultT
, newVarState
);
1956 if (attempt
.getRaw())
1961 if (except
.getRaw())
1970 esiAttempt::operator new(size_t byteCount
)
1972 assert (byteCount
== sizeof (esiAttempt
));
1977 esiAttempt::operator delete (void *address
)
1979 cbdataFree (address
);
1987 esiExcept::operator new(size_t byteCount
)
1989 assert (byteCount
== sizeof (esiExcept
));
1991 CBDATA_INIT_TYPE_FREECB(esiExcept
, esiSequence::Free
);
1992 rv
= (void *)cbdataAlloc (esiExcept
);
1997 esiExcept::operator delete (void *address
)
1999 cbdataFree (address
);
2007 esiVar::operator new(size_t byteCount
)
2009 assert (byteCount
== sizeof (esiVar
));
2011 CBDATA_INIT_TYPE_FREECB(esiVar
, esiSequence::Free
);
2012 rv
= (void *)cbdataAlloc (esiVar
);
2017 esiVar::operator delete (void *address
)
2019 cbdataFree (address
);
2025 esiChoose::~esiChoose()
2027 debugs(86, 5, "esiChoose::~esiChoose " << this);
2030 esiChoose::esiChoose(esiTreeParentPtr aParent
) : elements (), chosenelement (-1),parent (aParent
)
2034 esiChoose::render(ESISegment::Pointer output
)
2036 /* append all processed elements, and trim processed and rendered elements */
2037 assert (output
->next
== NULL
);
2038 assert (elements
.size() || otherwise
.getRaw());
2039 debugs(86, 5, "esiChooseRender: rendering");
2041 if (chosenelement
>= 0)
2042 elements
[chosenelement
]->render(output
);
2043 else if (otherwise
.getRaw())
2044 otherwise
->render(output
);
2048 esiChoose::addElement(ESIElement::Pointer element
)
2050 /* add an element to the output list */
2052 if (dynamic_cast<esiLiteral
*>(element
.getRaw())) {
2053 /* Swallow whitespace */
2054 debugs(86, 5, "esiChooseAdd: Choose " << this << " skipping whitespace " << element
.getRaw());
2058 /* Some elements require specific parents */
2059 if (!(dynamic_cast<esiWhen
*>(element
.getRaw()) || dynamic_cast<esiOtherwise
*>(element
.getRaw()))) {
2060 debugs(86, 0, "esiChooseAdd: invalid child node for esi:choose (section 3.3)");
2064 if (dynamic_cast<esiOtherwise
*>(element
.getRaw())) {
2065 if (otherwise
.getRaw()) {
2066 debugs(86, 0, "esiChooseAdd: only one otherwise node allowed for esi:choose (section 3.3)");
2070 otherwise
= element
;
2072 elements
.push_back (element
);
2074 debugs (86,3, "esiChooseAdd: Added a new element, elements = " << elements
.size());
2076 if (chosenelement
== -1)
2077 if ((dynamic_cast<esiWhen
*>(element
.getRaw()))->
2079 chosenelement
= elements
.size() - 1;
2080 debugs (86,3, "esiChooseAdd: Chose element " << elements
.size());
2088 esiChoose::selectElement()
2090 if (chosenelement
> -1)
2093 for (size_t counter
= 0; counter
< elements
.size(); ++counter
) {
2094 if ((dynamic_cast<esiWhen
*>(elements
[counter
].getRaw()))->
2096 chosenelement
= counter
;
2097 debugs (86,3, "esiChooseAdd: Chose element " << counter
+ 1);
2106 elements
.setNULL(0, elements
.size());
2108 if (otherwise
.getRaw())
2109 otherwise
->finish();
2117 ElementList::setNULL (int start
, int end
)
2119 assert (start
>= 0 && start
<= elementcount
);
2120 assert (end
>= 0 && end
<= elementcount
);
2122 for (int loopPosition
= start
; loopPosition
< end
; ++loopPosition
) {
2123 if (elements
[loopPosition
].getRaw())
2124 elements
[loopPosition
]->finish();
2126 debugs(86, 5, "esiSequence::NULLElements: Setting index " <<
2127 loopPosition
<< ", pointer " <<
2128 elements
[loopPosition
].getRaw() << " to NULL");
2130 elements
[loopPosition
] = NULL
;
2135 esiChoose::NULLUnChosen()
2137 if (chosenelement
>= 0) {
2138 if (otherwise
.getRaw())
2139 otherwise
->finish();
2143 elements
.setNULL (0, chosenelement
);
2145 elements
.setNULL (chosenelement
+ 1, elements
.size());
2146 } else if (otherwise
.getRaw()) {
2147 elements
.setNULL (0, elements
.size());
2152 esiChoose::process (int dovars
)
2154 /* process as much of the list as we can, stopping only on
2157 /* We MUST have a when clause */
2160 if (!elements
.size()) {
2163 if (otherwise
.getRaw())
2164 otherwise
->finish();
2170 return ESI_PROCESS_FAILED
;
2173 if (chosenelement
>= 0) {
2174 return elements
[chosenelement
]->process(dovars
);
2175 } else if (otherwise
.getRaw())
2176 return otherwise
->process(dovars
);
2178 return ESI_PROCESS_COMPLETE
;
2182 esiChoose::checkValidSource (ESIElement::Pointer source
) const
2184 if (!elements
.size())
2185 fatal ("invalid callback = no when clause\n");
2187 if (chosenelement
>= 0)
2188 assert (source
== elements
[chosenelement
]);
2189 else if (otherwise
.getRaw())
2190 assert (source
== otherwise
);
2192 fatal ("esiChoose::checkValidSource: invalid callback - no elements chosen\n");
2196 esiChoose::fail(ESIElement
* source
, char const *anError
)
2198 checkValidSource (source
);
2199 elements
.setNULL (0, elements
.size());
2201 if (otherwise
.getRaw())
2202 otherwise
->finish();
2206 parent
->fail(this, anError
);
2212 esiChoose::provideData (ESISegment::Pointer data
, ESIElement
*source
)
2214 checkValidSource (source
);
2215 parent
->provideData (data
, this);
2219 esiChoose::esiChoose(esiChoose
const &old
) : chosenelement(-1), otherwise (NULL
), parent (NULL
)
2221 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2222 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeCacheable();
2224 if (newElement
.getRaw())
2225 assert (addElement(newElement
));
2230 esiChoose::makeCachableElements(esiChoose
const &old
)
2232 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2233 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeCacheable();
2235 if (newElement
.getRaw())
2236 assert (addElement(newElement
));
2241 esiChoose::makeUsableElements(esiChoose
const &old
, ESIVarState
&newVarState
)
2243 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2244 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeUsable (this, newVarState
);
2246 if (newElement
.getRaw())
2247 assert (addElement(newElement
));
2252 esiChoose::makeCacheable() const
2254 esiChoose
*resultC
= new esiChoose (*this);
2255 ESIElement::Pointer result
= resultC
;
2256 resultC
->makeCachableElements(*this);
2258 if (otherwise
.getRaw())
2259 resultC
->otherwise
= otherwise
->makeCacheable();
2265 esiChoose::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
2267 esiChoose
*resultC
= new esiChoose (*this);
2268 ESIElement::Pointer result
= resultC
;
2269 resultC
->parent
= newParent
;
2270 resultC
->makeUsableElements(*this, newVarState
);
2271 resultC
->selectElement();
2273 if (otherwise
.getRaw())
2274 resultC
->otherwise
= otherwise
->makeUsable(resultC
, newVarState
);
2280 ElementList::ElementList () : elements(NULL
), allocedcount(0), allocedsize(0), elementcount (0)
2283 ElementList::~ElementList()
2285 debugs(86, 5, "ElementList::~ElementList " << this);
2286 setNULL(0, elementcount
);
2289 memFreeBuf (allocedsize
, elements
);
2292 ESIElement::Pointer
&
2293 ElementList::operator [] (int index
)
2295 return elements
[index
];
2298 ESIElement::Pointer
const &
2299 ElementList::operator [] (int index
) const
2301 return elements
[index
];
2305 ElementList::pop_front (size_t const count
)
2310 xmemmove (elements
, &elements
[count
], (elementcount
- count
) * sizeof (ESIElement::Pointer
));
2312 elementcount
-= count
;
2316 ElementList::push_back(ESIElement::Pointer
&newElement
)
2318 elements
= (ESIElement::Pointer
*)memReallocBuf (elements
, ++elementcount
* sizeof (ESIElement::Pointer
),
2321 allocedcount
= elementcount
;
2322 memset(&elements
[elementcount
- 1], '\0', sizeof (ESIElement::Pointer
));
2323 elements
[elementcount
- 1] = newElement
;
2327 ElementList::size() const
2329 return elementcount
;
2333 esiWhen::esiWhen (esiTreeParentPtr aParent
, int attrcount
, const char **attr
,ESIVarState
*aVar
) : esiSequence (aParent
)
2336 char const *expression
= NULL
;
2338 for (int loopCounter
= 0; loopCounter
< attrcount
&& attr
[loopCounter
]; loopCounter
+= 2) {
2339 if (!strcmp(attr
[loopCounter
],"test")) {
2341 debugs(86, 5, "esiWhen::esiWhen: Evaluating '" << attr
[loopCounter
+1] << "'");
2342 /* TODO: warn the user instead of asserting */
2343 assert (expression
== NULL
);
2344 expression
= attr
[loopCounter
+1];
2346 /* ignore mistyped attributes.
2347 * TODO:? error on these for user feedback - config parameter needed
2349 debugs(86, 1, "Found misttyped attribute on ESI When clause");
2353 /* No expression ? default is not matching */
2357 unevaluatedExpression
= xstrdup(expression
);
2359 varState
= cbdataReference (aVar
);
2366 safe_free (unevaluatedExpression
);
2369 cbdataReferenceDone (varState
);
2375 if (!unevaluatedExpression
)
2380 varState
->feedData(unevaluatedExpression
, strlen (unevaluatedExpression
));
2382 char const *expression
= varState
->extractChar ();
2384 setTestResult(ESIExpression::Evaluate (expression
));
2386 safe_free (expression
);
2389 esiWhen::esiWhen(esiWhen
const &old
) : esiSequence (old
)
2391 unevaluatedExpression
= NULL
;
2393 if (old
.unevaluatedExpression
)
2394 unevaluatedExpression
= xstrdup(old
.unevaluatedExpression
);
2400 esiWhen::makeCacheable() const
2402 return new esiWhen(*this);
2406 esiWhen::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
2408 esiWhen
*resultW
= new esiWhen (*this);
2409 ESIElement::Pointer result
= resultW
;
2410 resultW
->parent
= newParent
;
2411 resultW
->makeUsableElements(*this, newVarState
);
2412 resultW
->varState
= cbdataReference (&newVarState
);
2413 resultW
->evaluate();
2420 esiOtherwise::operator new(size_t byteCount
)
2422 assert (byteCount
== sizeof (esiOtherwise
));
2424 CBDATA_INIT_TYPE_FREECB(esiOtherwise
, esiSequence::Free
);
2425 rv
= (void *)cbdataAlloc (esiOtherwise
);
2430 esiOtherwise::operator delete (void *address
)
2432 cbdataFree (address
);
2437 /* TODO: implement surrogate targeting and control processing */
2439 esiEnableProcessing (HttpReply
*rep
)
2443 if (rep
->header
.has(HDR_SURROGATE_CONTROL
)) {
2444 HttpHdrScTarget
*sctusable
= httpHdrScGetMergedTarget (rep
->surrogate_control
,
2445 Config
.Accel
.surrogate_id
);
2447 if (!sctusable
|| sctusable
->content
.size() == 0)
2448 /* Nothing generic or targeted at us, or no
2449 * content processing requested
2453 if (strstr (sctusable
->content
.buf(), "ESI/1.0"))
2456 httpHdrScTargetDestroy (sctusable
);