2 * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 86 ESI processing */
13 /* MS Visual Studio Projects are monolithic, so we need the following
14 * #if to exclude the ESI code from compile process when not needed.
16 #if (USE_SQUID_ESI == 1)
18 #include "client_side.h"
19 #include "client_side_request.h"
20 #include "clientStream.h"
21 #include "comm/Connection.h"
22 #include "errorpage.h"
23 #include "esi/Assign.h"
24 #include "esi/Attempt.h"
25 #include "esi/Context.h"
26 #include "esi/Element.h"
28 #include "esi/Except.h"
29 #include "esi/Expression.h"
30 #include "esi/Segment.h"
31 #include "esi/VarState.h"
32 #include "HttpHdrSc.h"
33 #include "HttpHdrScTarget.h"
34 #include "HttpReply.h"
35 #include "HttpRequest.h"
36 #include "ip/Address.h"
38 #include "profiler/Profiler.h"
39 #include "SquidConfig.h"
41 /* quick reference on behaviour here.
42 * The ESI specification 1.0 requires the ESI processor to be able to
43 * return an error code at any point in the processing. To that end
44 * we buffer the incoming esi body until we know we will be able to
45 * satisfy the request. At that point we start streaming the queued
50 class ESIStreamContext
;
52 /* TODO: split this out into separate files ? */
53 /* Parsing: quick and dirty. ESI files are not valid XML, so a generic
54 * XML parser is not much use. Also we need a push parser not a pull
55 * parser, so LibXML is out.
57 * Interpreter methods:
58 * Render: May only ever be called after Process returns PROCESS_COMPLETE.
59 * Renders the resulting content into a ESISegment chain.
60 * Process: returns the status of the node.
61 * COMPLETE - processing is complete, rendering may staret
62 * PENDING_WONTFAIL - process is incomplete, but the element *will*
63 * be able to be rendered given time.
64 * PENDING_MAYFAIL - processing is incomplete, and the element *may*
65 * fail to be able to rendered.
66 * FAILED - processing failed, return an error to the client.
70 * NOT TODO: esi:inline - out of scope.
73 /* make comparisons with refcount pointers easy */
74 bool operator == (ESIElement
const *lhs
, ESIElement::Pointer
const &rhs
)
76 return lhs
== rhs
.getRaw();
79 typedef ESIContext::esiKick_t esiKick_t
;
81 /* some core operators */
83 class esiComment
: public ESIElement
85 MEMPROXY_CLASS(esiComment
);
90 Pointer
makeCacheable() const;
91 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
93 void render(ESISegment::Pointer
);
97 #include "esi/Literal.h"
99 #include "esi/Sequence.h"
101 #include "esi/Include.h"
105 class esiRemove
: public ESIElement
109 void *operator new (size_t byteCount
);
110 void operator delete (void *address
);
113 void render(ESISegment::Pointer
);
114 bool addElement (ESIElement::Pointer
);
115 Pointer
makeCacheable() const;
116 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
120 CBDATA_TYPE (esiRemove
);
121 static FREE esiRemoveFree
;
122 static ESIElement
* esiRemoveNew(void);
124 class esiTry
: public ESIElement
126 MEMPROXY_CLASS(esiTry
);
129 esiTry(esiTreeParentPtr aParent
);
132 void render(ESISegment::Pointer
);
133 bool addElement (ESIElement::Pointer
);
134 void fail(ESIElement
*, char const * = NULL
);
135 esiProcessResult_t
process (int dovars
);
136 void provideData (ESISegment::Pointer data
, ESIElement
* source
);
137 Pointer
makeCacheable() const;
138 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
140 ESIElement::Pointer attempt
;
141 ESIElement::Pointer except
;
144 int attemptok
:1; /* the attempt branch process correctly */
145 int exceptok
:1; /* likewise */
146 int attemptfailed
:1; /* The attempt branch failed */
147 int exceptfailed
:1; /* the except branch failed */
153 esiTreeParentPtr parent
;
154 ESISegment::Pointer exceptbuffer
;
155 esiTry (esiTry
const &);
156 esiProcessResult_t
bestAttemptRV() const;
161 class esiChoose
: public ESIElement
163 MEMPROXY_CLASS(esiChoose
);
166 esiChoose(esiTreeParentPtr
);
169 void render(ESISegment::Pointer
);
170 bool addElement (ESIElement::Pointer
);
171 void fail(ESIElement
*, char const * = NULL
);
172 esiProcessResult_t
process (int dovars
);
174 void provideData (ESISegment::Pointer data
, ESIElement
*source
);
175 void makeCachableElements(esiChoose
const &old
);
176 void makeUsableElements(esiChoose
const &old
, ESIVarState
&);
177 Pointer
makeCacheable() const;
178 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
181 ElementList elements
;
183 ESIElement::Pointer otherwise
;
187 esiChoose(esiChoose
const &);
188 esiTreeParentPtr parent
;
189 void checkValidSource (ESIElement::Pointer source
) const;
190 void selectElement();
193 class esiWhen
: public esiSequence
195 MEMPROXY_CLASS(esiWhen
);
198 esiWhen(esiTreeParentPtr aParent
, int attributes
, const char **attr
, ESIVarState
*);
200 Pointer
makeCacheable() const;
201 Pointer
makeUsable(esiTreeParentPtr
, ESIVarState
&) const;
203 bool testsTrue() const { return testValue
;}
205 void setTestResult(bool aBool
) {testValue
= aBool
;}
208 esiWhen (esiWhen
const &);
210 char const *unevaluatedExpression
;
211 ESIVarState
*varState
;
215 struct esiOtherwise
: public esiSequence
{
216 // void *operator new (size_t byteCount);
217 // void operator delete (void *address);
218 esiOtherwise(esiTreeParentPtr aParent
) : esiSequence (aParent
) {}
221 CBDATA_CLASS_INIT(ESIContext
);
223 void ESIContext::startRead()
229 void ESIContext::finishRead()
235 bool ESIContext::reading() const
240 ESIStreamContext::ESIStreamContext() : finished(false), include (NULL
), localbuffer (new ESISegment
), buffer (NULL
)
243 /* Local functions */
245 static ESIContext
*ESIContextNew(HttpReply
*, clientStreamNode
*, ClientHttpRequest
*);
248 ESIContext::setError()
251 errorstatus
= Http::scInternalServerError
;
256 ESIContext::appendOutboundData(ESISegment::Pointer theData
)
258 if (!outbound
.getRaw()) {
260 outboundtail
= outbound
;
262 assert (outboundtail
->next
.getRaw() == NULL
);
263 outboundtail
->next
= theData
;
267 debugs(86, 9, "ESIContext::appendOutboundData: outbound " << outbound
.getRaw());
271 ESIContext::provideData (ESISegment::Pointer theData
, ESIElement
* source
)
273 debugs(86, 5, "ESIContext::provideData: " << this << " " << theData
.getRaw() << " " << source
);
274 /* No callbacks permitted after finish() called on the tree */
275 assert (tree
.getRaw());
276 assert (source
== tree
);
277 appendOutboundData(theData
);
285 ESIContext::fail (ESIElement
* source
, char const *anError
)
288 setErrorMessage (anError
);
294 ESIContext::fixupOutboundTail()
296 /* TODO: fixup thisNode outboundtail dross a little */
298 if (outboundtail
.getRaw())
299 outboundtail
= outboundtail
->tail();
308 debugs(86, 5, "esiKick: Re-entered whilst in progress");
309 // return ESI_KICK_INPROGRESS;
314 /* we've been detached from - we can't do anything more */
315 return ESI_KICK_FAILED
;
317 /* Something has occured. Process any remaining nodes */
319 /* Process some of our data */
320 switch (process ()) {
322 case ESI_PROCESS_COMPLETE
:
323 debugs(86, 5, "esiKick: esiProcess OK");
326 case ESI_PROCESS_PENDING_WONTFAIL
:
327 debugs(86, 5, "esiKick: esiProcess PENDING OK");
330 case ESI_PROCESS_PENDING_MAYFAIL
:
331 debugs(86, 5, "esiKick: esiProcess PENDING UNKNOWN");
334 case ESI_PROCESS_FAILED
:
335 debugs(86, 2, "esiKick: esiProcess " << this << " FAILED");
336 /* this can not happen - processing can't fail until we have data,
337 * and when we come here we have sent data to the client
345 return ESI_KICK_FAILED
;
348 /* Render if we can to get maximal sent data */
349 assert (tree
.getRaw() || flags
.error
);
351 if (!flags
.finished
&& !outbound
.getRaw()) {
352 outboundtail
= new ESISegment
;
353 outbound
= outboundtail
;
356 if (!flags
.error
&& !flags
.finished
)
357 tree
->render(outboundtail
);
362 /* Is there data to send? */
364 /* some data was sent. we're finished until the next read */
366 return ESI_KICK_SENT
;
370 /* nothing to send */
371 return flags
.error
? ESI_KICK_FAILED
: ESI_KICK_PENDING
;
374 /* request from downstream for more data
377 esiStreamRead (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
379 clientStreamNode
*next
;
380 /* Test preconditions */
381 assert (thisNode
!= NULL
);
382 assert (cbdataReferenceValid (thisNode
));
383 /* we are not in the chain until ESI is detected on a data callback */
384 assert (thisNode
->node
.prev
!= NULL
);
385 assert (thisNode
->node
.next
!= NULL
);
387 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
388 assert (context
.getRaw() != NULL
);
390 if (context
->flags
.passthrough
) {
391 /* passthru mode - read into supplied buffers */
392 next
= thisNode
->next();
393 clientStreamRead (thisNode
, http
, next
->readBuffer
);
397 context
->flags
.clientwantsdata
= 1;
398 debugs(86, 5, "esiStreamRead: Client now wants data");
400 /* Ok, not passing through */
402 switch (context
->kick ()) {
404 case ESIContext::ESI_KICK_FAILED
:
405 /* this can not happen - processing can't fail until we have data,
406 * and when we come here we have sent data to the client
409 case ESIContext::ESI_KICK_SENT
:
411 case ESIContext::ESI_KICK_INPROGRESS
:
414 case ESIContext::ESI_KICK_PENDING
:
418 /* Nothing to send */
420 if (context
->flags
.oktosend
&& (context
->flags
.finishedtemplate
421 || context
->cachedASTInUse
) &&
422 ! context
->flags
.finished
) {
423 /* we've started sending, finished reading, but not finished
424 * processing. stop here, a callback will resume the stream
427 debugs(86, 5, "esiStreamRead: Waiting for async resume of esi processing");
431 if (context
->flags
.oktosend
&& context
->flags
.finished
&& context
->outbound
.getRaw()) {
432 debugs(86, 5, "all processing complete, but outbound data still buffered");
433 assert (!context
->flags
.clientwantsdata
);
434 /* client MUST be processing the last reply */
438 if (context
->flags
.oktosend
&& context
->flags
.finished
) {
439 StoreIOBuffer tempBuffer
;
440 assert (!context
->outbound
.getRaw());
441 /* We've finished processing, and there is no more data buffered */
442 debugs(86, 5, "Telling recipient EOF on READ");
443 clientStreamCallback (thisNode
, http
, NULL
, tempBuffer
);
447 if (context
->reading())
450 /* no data that is ready to send, and still reading? well, lets get some */
451 /* secure a buffer */
452 if (!context
->incoming
.getRaw()) {
453 /* create a new buffer segment */
454 context
->buffered
= new ESISegment
;
455 context
->incoming
= context
->buffered
;
458 assert (context
->incoming
.getRaw() && context
->incoming
->len
!= HTTP_REQBUF_SZ
);
460 StoreIOBuffer tempBuffer
;
461 tempBuffer
.offset
= context
->readpos
;
462 tempBuffer
.length
= context
->incoming
->len
- HTTP_REQBUF_SZ
;
463 tempBuffer
.data
= &context
->incoming
->buf
[context
->incoming
->len
];
464 context
->startRead();
465 clientStreamRead (thisNode
, http
, tempBuffer
);
469 clientStream_status_t
470 esiStreamStatus (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
472 /* Test preconditions */
473 assert (thisNode
!= NULL
);
474 assert (cbdataReferenceValid (thisNode
));
475 /* we are not in the chain until ESI is detected on a data callback */
476 assert (thisNode
->node
.prev
!= NULL
);
477 assert (thisNode
->node
.next
!= NULL
);
479 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
480 assert (context
.getRaw() != NULL
);
482 if (context
->flags
.passthrough
)
483 return clientStreamStatus (thisNode
, http
);
485 if (context
->flags
.oktosend
&& context
->flags
.finished
&&
486 !(context
->outbound
.getRaw() && context
->outbound_offset
< context
->outbound
->len
)) {
487 debugs(86, 5, "Telling recipient EOF on STATUS");
488 return STREAM_UNPLANNED_COMPLETE
; /* we don't know lengths in advance */
491 /* ?? RC: we can't be aborted / fail ? */
496 esiAlwaysPassthrough(Http::StatusCode sline
)
502 case Http::scContinue
: /* Should never reach us... but squid needs to alter to accomodate this */
504 case Http::scSwitchingProtocols
: /* Ditto */
506 case Http::scProcessing
: /* Unknown - some extension */
508 case Http::scNoContent
: /* no body, no esi */
510 case Http::scNotModified
: /* ESI does not affect assembled page headers, so 304s are valid */
523 ESIContext::trimBlanks()
525 /* trim leading empty buffers ? */
527 while (outbound
.getRaw() && outbound
->next
.getRaw() && !outbound
->len
) {
528 debugs(86, 5, "ESIContext::trimBlanks: " << this <<
529 " skipping segment " << outbound
.getRaw());
530 outbound
= outbound
->next
;
533 if (outboundtail
.getRaw())
534 assert (outbound
.getRaw());
537 /* Send data downstream
538 * Returns 0 if nothing was sent. Non-zero if data was sent.
543 debugs(86, 5, "ESIContext::send: this=" << this);
544 /* send any processed data */
548 if (!flags
.clientwantsdata
) {
549 debugs(86, 5, "ESIContext::send: Client does not want data - not sending anything");
553 if (tree
.getRaw() && tree
->mayFail()) {
554 debugs(86, 5, "ESIContext::send: Tree may fail. Not sending.");
561 if (!flags
.oktosend
) {
563 fatal("ESIContext::send: Not OK to send.\n");
569 if (!(rep
|| (outbound
.getRaw() &&
570 outbound
->len
&& (outbound_offset
<= outbound
->len
)))) {
571 debugs(86, 5, "ESIContext::send: Nothing to send.");
575 debugs(86, 5, "ESIContext::send: Sending something...");
576 /* Yes! Send it without asking for more upstream */
577 /* memcopying because the client provided the buffer */
578 /* TODO: skip data until pos == next->readoff; */
579 assert (thisNode
->data
== this);
580 clientStreamNode
*next
= thisNode
->next();
581 ESIContext
*templock
= cbdataReference (this);
584 if (outbound
.getRaw())
585 len
= min (next
->readBuffer
.length
, outbound
->len
- outbound_offset
);
587 /* prevent corruption on range requests, even though we don't support them yet */
588 assert (pos
== next
->readBuffer
.offset
);
590 /* We must send data or a reply */
591 assert (len
!= 0 || rep
!= NULL
);
594 memcpy(next
->readBuffer
.data
, &outbound
->buf
[outbound_offset
], len
);
596 if (len
+ outbound_offset
== outbound
->len
) {
597 ESISegment::Pointer temp
= outbound
->next
;
598 /* remove the used buffer */
605 if (!outbound
.getRaw())
611 flags
.clientwantsdata
= 0;
612 debugs(86, 5, "ESIContext::send: this=" << this << " Client no longer wants data ");
613 /* Deal with re-entrancy */
614 HttpReply
*temprep
= rep
;
615 rep
= NULL
; /* freed downstream */
617 if (temprep
&& varState
)
618 varState
->buildVary (temprep
);
621 StoreIOBuffer tempBuffer
;
622 tempBuffer
.length
= len
;
623 tempBuffer
.offset
= pos
- len
;
624 tempBuffer
.data
= next
->readBuffer
.data
;
625 clientStreamCallback (thisNode
, http
, temprep
, tempBuffer
);
629 len
= 1; /* tell the caller we sent something (because we sent headers */
631 cbdataReferenceDone (templock
);
633 debugs (86,5,"ESIContext::send: this=" << this << " sent " << len
);
639 ESIContext::finishChildren()
647 /* Detach event from a client Stream */
649 esiStreamDetach (clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
651 /* if we have pending callbacks, tell them we're done. */
652 /* test preconditions */
653 assert (thisNode
!= NULL
);
654 assert (cbdataReferenceValid (thisNode
));
655 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
656 assert (context
.getRaw() != NULL
);
657 /* detach from the stream */
658 clientStreamDetach (thisNode
,http
);
659 /* if we have pending callbacks (from subincludes), tell them we're done. */
660 context
->thisNode
= NULL
;
661 context
->flags
.detached
= 1;
662 context
->finishChildren();
663 /* HACK for parser stack not being emptied */
664 context
->parserState
.stack
[0] = NULL
;
665 /* allow refcount logic to trigger */
666 context
->cbdataLocker
= NULL
;
669 /* Process incoming data for ESI tags */
670 /* ESI TODO: Long term: we should have a framework to parse html/xml and
671 * callback to a set of processors like thisNode, to prevent multiple parsing
672 * overhead. More thoughts on thisNode: We have to parse multiple times, because
673 * the output of one processor may create a very different tree. What we could
674 * do is something like DOM and pass that down to a final renderer. This is
675 * getting into web server territory though...
678 * This is not the last node in the stream.
679 * ESI processing has been enabled.
680 * There is context data or a reply structure
683 esiProcessStream (clientStreamNode
*thisNode
, ClientHttpRequest
*http
, HttpReply
*rep
, StoreIOBuffer receivedData
)
685 /* test preconditions */
686 assert (thisNode
!= NULL
);
687 /* ESI TODO: handle thisNode rather than asserting - it should only ever
688 * happen if we cause an abort and the callback chain
689 * loops back to here, so we can simply return. However, that itself
690 * shouldn't happen, so it stays as an assert for now. */
691 assert (cbdataReferenceValid (thisNode
));
693 * if data is NULL thisNode is the first entrance. If rep is also NULL,
694 * something is wrong.
696 assert (thisNode
->data
.getRaw() != NULL
|| rep
);
697 assert (thisNode
->node
.next
!= NULL
);
699 if (!thisNode
->data
.getRaw())
700 /* setup ESI context from reply headers */
701 thisNode
->data
= ESIContextNew(rep
, thisNode
, http
);
703 ESIContext::Pointer context
= dynamic_cast<ESIContext
*>(thisNode
->data
.getRaw());
705 assert (context
.getRaw() != NULL
);
707 context
->finishRead();
709 /* Skipping all ESI processing. All remaining data gets untouched.
710 * Mainly used when an error or other non-ESI processable entity
711 * has been detected to prevent ESI processing the error body
713 if (context
->flags
.passthrough
) {
714 clientStreamCallback (thisNode
, http
, rep
, receivedData
);
718 debugs(86, 3, "esiProcessStream: Processing thisNode " << thisNode
<<
719 " context " << context
.getRaw() << " offset " <<
720 (int) receivedData
.offset
<< " length " <<
721 (unsigned int)receivedData
.length
);
723 /* once we finish the template, we *cannot* return here */
724 assert (!context
->flags
.finishedtemplate
);
725 assert (!context
->cachedASTInUse
);
727 /* Can we generate any data ?*/
729 if (receivedData
.data
) {
730 /* Increase our buffer area with incoming data */
731 assert (receivedData
.length
<= HTTP_REQBUF_SZ
);
732 assert (thisNode
->readBuffer
.offset
== receivedData
.offset
);
733 debugs (86,5, "esiProcessStream found " << receivedData
.length
<< " bytes of body data at offset " << receivedData
.offset
);
734 /* secure the data for later use */
736 if (!context
->incoming
.getRaw()) {
737 /* create a new buffer segment */
738 debugs(86, 5, "esiProcessStream: Setting up incoming buffer");
739 context
->buffered
= new ESISegment
;
740 context
->incoming
= context
->buffered
;
743 if (receivedData
.data
!= &context
->incoming
->buf
[context
->incoming
->len
]) {
744 /* We have to copy the data out because we didn't supply thisNode buffer */
745 size_t space
= HTTP_REQBUF_SZ
- context
->incoming
->len
;
746 size_t len
= min (space
, receivedData
.length
);
747 debugs(86, 5, "Copying data from " << receivedData
.data
<< " to " <<
748 &context
->incoming
->buf
[context
->incoming
->len
] <<
749 " because our buffer was not used");
751 memcpy(&context
->incoming
->buf
[context
->incoming
->len
], receivedData
.data
, len
);
752 context
->incoming
->len
+= len
;
754 if (context
->incoming
->len
== HTTP_REQBUF_SZ
) {
755 /* append another buffer */
756 context
->incoming
->next
= new ESISegment
;
757 context
->incoming
= context
->incoming
->next
;
760 if (len
!= receivedData
.length
) {
761 /* capture the remnants */
762 memcpy(context
->incoming
->buf
, &receivedData
.data
[len
], receivedData
.length
- len
);
763 context
->incoming
->len
= receivedData
.length
- len
;
766 /* and note where we are up to */
767 context
->readpos
+= receivedData
.length
;
769 /* update our position counters, and if needed assign a new buffer */
770 context
->incoming
->len
+= receivedData
.length
;
771 assert (context
->incoming
->len
<= HTTP_REQBUF_SZ
);
773 if (context
->incoming
->len
> HTTP_REQBUF_SZ
* 3 / 4) {
774 /* allocate a new buffer - to stop us asking for ridiculously small amounts */
775 context
->incoming
->next
= new ESISegment
;
776 context
->incoming
= context
->incoming
->next
;
779 context
->readpos
+= receivedData
.length
;
783 /* EOF / Read error / aborted entry */
784 if (rep
== NULL
&& receivedData
.data
== NULL
&& receivedData
.length
== 0 && !context
->flags
.finishedtemplate
) {
785 /* TODO: get stream status to test the entry for aborts */
786 /* else flush the esi processor */
787 debugs(86, 5, "esiProcess: " << context
.getRaw() << " Finished reading upstream data");
788 /* This is correct */
789 context
->flags
.finishedtemplate
= 1;
792 switch (context
->kick()) {
794 case ESIContext::ESI_KICK_FAILED
:
795 /* thisNode can not happen - processing can't fail until we have data,
796 * and when we come here we have sent data to the client
800 case ESIContext::ESI_KICK_SENT
:
802 case ESIContext::ESI_KICK_INPROGRESS
:
805 case ESIContext::ESI_KICK_PENDING
:
809 /* ok.. no data sent, try to pull more data in from upstream.
810 * FIXME: Don't try thisNode if we have finished reading the template
812 if (!context
->flags
.finishedtemplate
&& !context
->reading()
813 && !context
->cachedASTInUse
) {
814 StoreIOBuffer tempBuffer
;
815 assert (context
->incoming
.getRaw() && context
->incoming
->len
< HTTP_REQBUF_SZ
);
816 tempBuffer
.offset
= context
->readpos
;
817 tempBuffer
.length
= HTTP_REQBUF_SZ
- context
->incoming
->len
;
818 tempBuffer
.data
= &context
->incoming
->buf
[context
->incoming
->len
];
819 context
->startRead();
820 clientStreamRead (thisNode
, http
, tempBuffer
);
824 debugs(86, 3, "esiProcessStream: no data to send, no data to read, awaiting a callback");
827 ESIContext::~ESIContext()
830 /* Not freed by freeresources because esi::fail needs it */
831 safe_free (errormessage
);
832 debugs(86, 3, "ESIContext::~ESIContext: Freed " << this);
836 ESIContextNew (HttpReply
*rep
, clientStreamNode
*thisNode
, ClientHttpRequest
*http
)
839 ESIContext
*rv
= new ESIContext
;
841 rv
->cbdataLocker
= rv
;
843 if (esiAlwaysPassthrough(rep
->sline
.status())) {
844 rv
->flags
.passthrough
= 1;
846 /* remove specific headers for ESI to prevent
847 * downstream cache confusion */
848 HttpHeader
*hdr
= &rep
->header
;
849 hdr
->delById(HDR_ACCEPT_RANGES
);
850 hdr
->delById(HDR_ETAG
);
851 hdr
->delById(HDR_CONTENT_LENGTH
);
852 hdr
->delById(HDR_CONTENT_MD5
);
853 rv
->tree
= new esiSequence (rv
, true);
854 rv
->thisNode
= thisNode
;
856 rv
->flags
.clientwantsdata
= 1;
857 rv
->varState
= new ESIVarState (&http
->request
->header
, http
->uri
);
858 debugs(86, 5, "ESIContextNew: Client wants data (always created during reply cycle");
861 debugs(86, 5, "ESIContextNew: Create context " << rv
);
865 ESIElement::ESIElementType_t
866 ESIElement::IdentifyElement (const char *el
)
872 return ESI_ELEMENT_NONE
;
874 if (!strncmp (el
, "esi:", 4))
876 else if (!strncmp (el
, "http://www.edge-delivery.org/esi/1.0|", 37))
879 return ESI_ELEMENT_NONE
;
881 if (!strncmp (el
+ offset
, "otherwise", 9))
882 return ESI_ELEMENT_OTHERWISE
;
884 if (!strncmp (el
+ offset
, "comment", 7))
885 return ESI_ELEMENT_COMMENT
;
887 if (!strncmp (el
+ offset
, "include", 7))
888 return ESI_ELEMENT_INCLUDE
;
890 if (!strncmp (el
+ offset
, "attempt", 7))
891 return ESI_ELEMENT_ATTEMPT
;
893 if (!strncmp (el
+ offset
, "assign", 6))
894 return ESI_ELEMENT_ASSIGN
;
896 if (!strncmp (el
+ offset
, "remove", 6))
897 return ESI_ELEMENT_REMOVE
;
899 if (!strncmp (el
+ offset
, "except", 6))
900 return ESI_ELEMENT_EXCEPT
;
902 if (!strncmp (el
+ offset
, "choose", 6))
903 return ESI_ELEMENT_CHOOSE
;
905 if (!strncmp (el
+ offset
, "vars", 4))
906 return ESI_ELEMENT_VARS
;
908 if (!strncmp (el
+ offset
, "when", 4))
909 return ESI_ELEMENT_WHEN
;
911 if (!strncmp (el
+ offset
, "try", 3))
912 return ESI_ELEMENT_TRY
;
914 return ESI_ELEMENT_NONE
;
918 ESIContext::ParserState::top()
920 return stack
[stackdepth
-1];
923 ESIContext::ParserState::ParserState() :
930 ESIContext::ParserState::inited() const
936 ESIContext::addStackElement (ESIElement::Pointer element
)
938 /* Put on the stack to allow skipping of 'invalid' markup */
939 assert (parserState
.stackdepth
<11);
941 debugs(86, 5, "ESIContext::addStackElement: About to add ESI Node " << element
.getRaw());
943 if (!parserState
.top()->addElement(element
)) {
944 debugs(86, DBG_IMPORTANT
, "ESIContext::addStackElement: failed to add esi node, probable error in ESI template");
947 /* added ok, push onto the stack */
948 parserState
.stack
[parserState
.stackdepth
] = element
;
949 ++parserState
.stackdepth
;
954 ESIContext::start(const char *el
, const char **attr
, size_t attrCount
)
957 unsigned int ellen
= strlen (el
);
958 char localbuf
[HTTP_REQBUF_SZ
];
959 ESIElement::Pointer element
;
960 int specifiedattcount
= attrCount
* 2;
962 assert (ellen
< sizeof (localbuf
)); /* prevent unexpected overruns. */
964 debugs(86, 5, "ESIContext::Start: element '" << el
<< "' with " << specifiedattcount
<< " tags");
967 /* waiting for expat to finish the buffer we gave it */
970 switch (ESIElement::IdentifyElement (el
)) {
972 case ESIElement::ESI_ELEMENT_NONE
:
973 /* Spit out elements we aren't interested in */
976 assert (xstrncpy (&localbuf
[1], el
, sizeof(localbuf
) - 2));
977 position
= localbuf
+ strlen (localbuf
);
979 for (i
= 0; i
< specifiedattcount
&& attr
[i
]; i
+= 2) {
982 /* TODO: handle thisNode gracefully */
983 assert (xstrncpy (position
, attr
[i
], sizeof(localbuf
) + (position
- localbuf
)));
984 position
+= strlen (position
);
989 const char *chPtr
= attr
[i
+ 1];
991 while ((ch
= *chPtr
++) != '\0') {
993 assert( xstrncpy(position
, """, sizeof(localbuf
) + (position
-localbuf
)) );
1000 position
+= strlen (position
);
1009 addLiteral (localbuf
, position
- localbuf
);
1010 debugs(86, 5, "esi stack depth " << parserState
.stackdepth
);
1014 case ESIElement::ESI_ELEMENT_COMMENT
:
1015 /* Put on the stack to allow skipping of 'invalid' markup */
1016 element
= new esiComment ();
1019 case ESIElement::ESI_ELEMENT_INCLUDE
:
1020 /* Put on the stack to allow skipping of 'invalid' markup */
1021 element
= new ESIInclude (parserState
.top().getRaw(), specifiedattcount
, attr
, this);
1024 case ESIElement::ESI_ELEMENT_REMOVE
:
1025 /* Put on the stack to allow skipping of 'invalid' markup */
1026 element
= esiRemoveNew ();
1029 case ESIElement::ESI_ELEMENT_TRY
:
1030 /* Put on the stack to allow skipping of 'invalid' markup */
1031 element
= new esiTry (parserState
.top().getRaw());
1034 case ESIElement::ESI_ELEMENT_ATTEMPT
:
1035 /* Put on the stack to allow skipping of 'invalid' markup */
1036 element
= new esiAttempt (parserState
.top().getRaw());
1039 case ESIElement::ESI_ELEMENT_EXCEPT
:
1040 /* Put on the stack to allow skipping of 'invalid' markup */
1041 element
= new esiExcept (parserState
.top().getRaw());
1044 case ESIElement::ESI_ELEMENT_VARS
:
1045 /* Put on the stack to allow skipping of 'invalid' markup */
1046 element
= new ESIVar (parserState
.top().getRaw());
1049 case ESIElement::ESI_ELEMENT_CHOOSE
:
1050 /* Put on the stack to allow skipping of 'invalid' markup */
1051 element
= new esiChoose (parserState
.top().getRaw());
1054 case ESIElement::ESI_ELEMENT_WHEN
:
1055 /* Put on the stack to allow skipping of 'invalid' markup */
1056 element
= new esiWhen (parserState
.top().getRaw(), specifiedattcount
, attr
, varState
);
1059 case ESIElement::ESI_ELEMENT_OTHERWISE
:
1060 /* Put on the stack to allow skipping of 'invalid' markup */
1061 element
= new esiOtherwise (parserState
.top().getRaw());
1064 case ESIElement::ESI_ELEMENT_ASSIGN
:
1065 /* Put on the stack to allow skipping of 'invalid' markup */
1066 element
= new ESIAssign (parserState
.top().getRaw(), specifiedattcount
, attr
, this);
1070 addStackElement(element
);
1072 debugs(86, 5, "esi stack depth " << parserState
.stackdepth
);
1074 } /* End of start handler */
1077 ESIContext::end(const char *el
)
1079 unsigned int ellen
= strlen (el
);
1080 char localbuf
[HTTP_REQBUF_SZ
];
1084 /* waiting for expat to finish the buffer we gave it */
1087 switch (ESIElement::IdentifyElement (el
)) {
1089 case ESIElement::ESI_ELEMENT_NONE
:
1090 assert (ellen
< sizeof (localbuf
)); /* prevent unexpected overruns. */
1091 /* Add elements we aren't interested in */
1094 assert (xstrncpy (&localbuf
[2], el
, sizeof(localbuf
) - 3));
1095 position
= localbuf
+ strlen (localbuf
);
1099 addLiteral (localbuf
, position
- localbuf
);
1102 case ESIElement::ESI_ELEMENT_COMMENT
:
1104 case ESIElement::ESI_ELEMENT_INCLUDE
:
1106 case ESIElement::ESI_ELEMENT_REMOVE
:
1108 case ESIElement::ESI_ELEMENT_TRY
:
1110 case ESIElement::ESI_ELEMENT_ATTEMPT
:
1112 case ESIElement::ESI_ELEMENT_EXCEPT
:
1114 case ESIElement::ESI_ELEMENT_VARS
:
1116 case ESIElement::ESI_ELEMENT_CHOOSE
:
1118 case ESIElement::ESI_ELEMENT_WHEN
:
1120 case ESIElement::ESI_ELEMENT_OTHERWISE
:
1122 case ESIElement::ESI_ELEMENT_ASSIGN
:
1123 /* pop of the stack */
1124 parserState
.stack
[--parserState
.stackdepth
] = NULL
;
1127 } /* End of end handler */
1130 ESIContext::parserDefault (const char *s
, int len
)
1135 /* handle any skipped data */
1136 addLiteral (s
, len
);
1140 ESIContext::parserComment (const char *s
)
1145 if (!strncmp(s
, "esi",3)) {
1146 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block encountered");
1147 ESIParser::Pointer tempParser
= ESIParser::NewParser (this);
1149 /* wrap the comment in some tags */
1151 if (!tempParser
->parse("<div>", 5,0) ||
1152 !tempParser
->parse(s
+ 3, strlen(s
) - 3, 0) ||
1153 !tempParser
->parse("</div>",6,1)) {
1154 debugs(86, DBG_CRITICAL
, "ESIContext::parserComment: Parsing fragment '" << s
+ 3 << "' failed.");
1157 snprintf(tempstr
, 1023, "ESIContext::parserComment: Parse error at line %ld:\n%s\n",
1158 tempParser
->lineNumber(),
1159 tempParser
->errorString());
1160 debugs(86, DBG_CRITICAL
, "" << tempstr
<< "");
1162 setErrorMessage(tempstr
);
1165 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block parsed");
1168 char localbuf
[HTTP_REQBUF_SZ
];
1170 debugs(86, 5, "ESIContext::parserComment: Regenerating comment block");
1173 if (len
> sizeof (localbuf
) - 9) {
1174 debugs(86, DBG_CRITICAL
, "ESIContext::parserComment: Truncating long comment");
1175 len
= sizeof (localbuf
) - 9;
1178 xstrncpy(localbuf
, "<!--", 5);
1179 xstrncpy(localbuf
+ 4, s
, len
+ 1);
1180 xstrncpy(localbuf
+ 4 + len
, "-->", 4);
1181 addLiteral (localbuf
,len
+ 7);
1186 ESIContext::addLiteral (const char *s
, int len
)
1188 /* handle any skipped data */
1190 debugs(86, 5, "literal length is " << len
);
1191 /* give a literal to the current element */
1192 assert (parserState
.stackdepth
<11);
1193 ESIElement::Pointer
element (new esiLiteral (this, s
, len
));
1195 if (!parserState
.top()->addElement(element
)) {
1196 debugs(86, DBG_IMPORTANT
, "ESIContext::addLiteral: failed to add esi node, probable error in ESI template");
1202 ESIContext::ParserState::init(ESIParserClient
*userData
)
1204 theParser
= ESIParser::NewParser (userData
);
1209 ESIContext::parseOneBuffer()
1211 assert (buffered
.getRaw());
1213 debugs (86,9,"ESIContext::parseOneBuffer: " << buffered
->len
<< " bytes");
1214 bool lastBlock
= buffered
->next
.getRaw() == NULL
&& flags
.finishedtemplate
? true : false;
1216 if (! parserState
.theParser
->parse(buffered
->buf
, buffered
->len
, lastBlock
)) {
1219 snprintf (tempstr
, 1023, "esiProcess: Parse error at line %ld:\n%s\n",
1220 parserState
.theParser
->lineNumber(),
1221 parserState
.theParser
->errorString());
1222 debugs(86, DBG_CRITICAL
, "" << tempstr
<< "");
1224 setErrorMessage(tempstr
);
1226 assert (flags
.error
);
1236 ESISegment::Pointer temp
= buffered
;
1237 buffered
= temp
->next
;
1243 if (!parserState
.stackdepth
) {
1244 debugs(86, 5, "empty parser stack, inserting the top level node");
1245 assert (tree
.getRaw());
1246 parserState
.stack
[parserState
.stackdepth
] = tree
;
1247 ++parserState
.stackdepth
;
1250 if (rep
&& !parserState
.inited())
1251 parserState
.init(this);
1254 if (buffered
.getRaw()) {
1255 parserState
.parsing
= 1;
1256 /* we don't keep any data around */
1258 PROF_start(esiParsing
);
1260 while (buffered
.getRaw() && !flags
.error
)
1263 PROF_stop(esiParsing
);
1265 /* Tel the read code to allocate a new buffer */
1268 parserState
.parsing
= 0;
1273 ESIContext::process ()
1276 * read through buffered, skipping plain text, and skipping any
1277 * <...> entry that is not an <esi: entry.
1278 * when it's found, hand an esiLiteral of the preceding data to our current
1282 if (parserState
.parsing
) {
1283 /* in middle of parsing - finish here */
1284 return ESI_PROCESS_PENDING_MAYFAIL
;
1287 assert (flags
.finished
== 0);
1289 assert (!flags
.error
);
1291 if (!hasCachedAST())
1293 else if (!flags
.finishedtemplate
)
1297 debugs(86, 5, "ESIContext::process: Parsing failed");
1299 parserState
.popAll();
1300 return ESI_PROCESS_FAILED
;
1303 if (!flags
.finishedtemplate
&& !incoming
.getRaw() && !cachedASTInUse
) {
1304 buffered
= new ESISegment
;
1305 incoming
= buffered
;
1308 if (!flags
.finishedtemplate
&& !cachedASTInUse
) {
1309 return ESI_PROCESS_PENDING_MAYFAIL
;
1312 assert (flags
.finishedtemplate
|| cachedASTInUse
);
1314 /* ok, we've done all we can with the data. What can we process now?
1317 esiProcessResult_t status
;
1318 PROF_start(esiProcessing
);
1320 status
= tree
->process(0);
1325 case ESI_PROCESS_COMPLETE
:
1326 debugs(86, 5, "esiProcess: tree Processed OK");
1329 case ESI_PROCESS_PENDING_WONTFAIL
:
1330 debugs(86, 5, "esiProcess: tree Processed PENDING OK");
1333 case ESI_PROCESS_PENDING_MAYFAIL
:
1334 debugs(86, 5, "esiProcess: tree Processed PENDING UNKNOWN");
1337 case ESI_PROCESS_FAILED
:
1338 debugs(86, DBG_CRITICAL
, "esiProcess: tree Processed FAILED");
1341 setErrorMessage("esiProcess: ESI template Processing failed.");
1343 PROF_stop(esiProcessing
);
1345 return ESI_PROCESS_FAILED
;
1350 if (status
!= ESI_PROCESS_PENDING_MAYFAIL
&& (flags
.finishedtemplate
|| cachedASTInUse
)) {
1351 /* We've read the entire template, and no nodes will
1354 debugs(86, 5, "esiProcess, request will succeed");
1358 if (status
== ESI_PROCESS_COMPLETE
1359 && (flags
.finishedtemplate
|| cachedASTInUse
)) {
1360 /* we've finished all processing. Render and send. */
1361 debugs(86, 5, "esiProcess, processing complete");
1365 PROF_stop(esiProcessing
);
1366 return status
; /* because we have no callbacks */
1371 ESIContext::ParserState::freeResources()
1378 ESIContext::ParserState::popAll()
1381 stack
[--stackdepth
] = NULL
;
1385 ESIContext::freeResources ()
1387 debugs(86, 5, HERE
<< "Freeing for this=" << this);
1393 if (parserState
.inited()) {
1394 parserState
.freeResources();
1397 parserState
.popAll();
1398 ESISegmentFreeList (buffered
);
1399 ESISegmentFreeList (outbound
);
1400 ESISegmentFreeList (outboundtail
);
1403 /* don't touch incoming, it's a pointer into buffered anyway */
1406 ErrorState
*clientBuildError (err_type
, Http::StatusCode
, char const *, Ip::Address
&, HttpRequest
*);
1408 /* This can ONLY be used before we have sent *any* data to the client */
1412 debugs(86, 5, "ESIContext::fail: this=" << this);
1413 /* check preconditions */
1415 /* cleanup current state */
1417 /* Stop altering thisNode request */
1420 /* don't honour range requests - for errors we send it all */
1422 /* create an error object */
1423 // XXX: with the in-direction on remote IP. does the http->getConn()->clientConnection exist?
1424 ErrorState
* err
= clientBuildError(errorpage
, errorstatus
, NULL
, http
->getConn()->clientConnection
->remote
, http
->request
);
1425 err
->err_msg
= errormessage
;
1426 errormessage
= NULL
;
1427 rep
= err
->BuildHttpReply();
1428 assert (rep
->body
.hasContent());
1429 size_t errorprogress
= rep
->body
.contentSize();
1430 /* Tell esiSend where to start sending from */
1431 outbound_offset
= 0;
1432 /* copy the membuf from the reply to outbound */
1434 while (errorprogress
< (size_t)rep
->body
.contentSize()) {
1435 appendOutboundData(new ESISegment
);
1436 errorprogress
+= outboundtail
->append(rep
->body
.content() + errorprogress
, rep
->body
.contentSize() - errorprogress
);
1439 /* the esiCode now thinks that the error is the outbound,
1440 * and all processing has finished. */
1441 /* Send as much as we can */
1444 /* don't cancel anything. The stream nodes will clean up after
1445 * themselves when the reply is freed - and we don't know what to
1450 /* Implementation of ESIElements */
1453 esiComment::~esiComment()
1455 debugs(86, 5, "esiComment::~esiComment " << this);
1458 esiComment::esiComment()
1462 esiComment::finish()
1466 esiComment::render(ESISegment::Pointer output
)
1468 /* Comments do nothing dude */
1469 debugs(86, 5, "esiCommentRender: Rendering comment " << this << " into " << output
.getRaw());
1473 esiComment::makeCacheable() const
1475 debugs(86, 5, "esiComment::makeCacheable: returning NULL");
1480 esiComment::makeUsable(esiTreeParentPtr
, ESIVarState
&) const
1482 fatal ("esiComment::Usable: unreachable code!\n");
1487 esiLiteral::~esiLiteral()
1489 debugs(86, 5, "esiLiteral::~esiLiteral: " << this);
1490 ESISegmentFreeList (buffer
);
1491 cbdataReferenceDone (varState
);
1494 esiLiteral::esiLiteral(ESISegment::Pointer aSegment
)
1497 /* we've been handed a complete, processed string */
1504 esiLiteral::finish()
1507 /* precondition: the buffer chain has at least start + length bytes of data
1509 esiLiteral::esiLiteral(ESIContext
*context
, const char *s
, int numberOfCharacters
)
1513 buffer
= new ESISegment
;
1514 ESISegment::Pointer local
= buffer
;
1516 int remainingCharacters
= numberOfCharacters
;
1518 while (remainingCharacters
> 0) {
1519 if (local
->len
== sizeof (local
->buf
)) {
1520 local
->next
= new ESISegment
;
1524 size_t len
= local
->append (&s
[start
], remainingCharacters
);
1526 remainingCharacters
-= len
;
1529 varState
= cbdataReference(context
->varState
);
1533 esiLiteral::render (ESISegment::Pointer output
)
1535 debugs(86, 9, "esiLiteral::render: Rendering " << this);
1536 /* append the entire chain */
1537 assert (output
->next
.getRaw() == NULL
);
1538 output
->next
= buffer
;
1543 esiLiteral::process (int dovars
)
1546 return ESI_PROCESS_COMPLETE
;
1549 ESISegment::Pointer temp
= buffer
;
1550 /* Ensure variable state is clean */
1552 while (temp
.getRaw()) {
1553 varState
->feedData(temp
->buf
,temp
->len
);
1557 /* free the pre-processed content */
1558 ESISegmentFreeList (buffer
);
1560 buffer
= varState
->extractList ();
1564 return ESI_PROCESS_COMPLETE
;
1567 esiLiteral::esiLiteral(esiLiteral
const &old
) : buffer (old
.buffer
->cloneList()),
1574 esiLiteral::makeCacheable() const
1576 return new esiLiteral (*this);
1580 esiLiteral::makeUsable(esiTreeParentPtr
, ESIVarState
&newVarState
) const
1582 debugs(86, 5, "esiLiteral::makeUsable: Creating usable literal");
1583 esiLiteral
* result
= new esiLiteral (*this);
1584 result
->varState
= cbdataReference (&newVarState
);
1590 esiRemoveFree (void *data
)
1592 esiRemove
*thisNode
= (esiRemove
*)data
;
1593 debugs(86, 5, "esiRemoveFree " << thisNode
);
1597 esiRemove::operator new(size_t byteCount
)
1599 assert (byteCount
== sizeof (esiRemove
));
1601 CBDATA_INIT_TYPE_FREECB(esiRemove
, esiRemoveFree
);
1602 rv
= (void *)cbdataAlloc (esiRemove
);
1607 esiRemove::operator delete (void *address
)
1609 cbdataFree (address
);
1615 return new esiRemove
;
1618 esiRemove::esiRemove()
1626 esiRemove::render(ESISegment::Pointer output
)
1628 /* Removes do nothing dude */
1629 debugs(86, 5, "esiRemoveRender: Rendering remove " << this);
1632 /* Accept non-ESI children */
1634 esiRemove::addElement (ESIElement::Pointer element
)
1636 if (!dynamic_cast<esiLiteral
*>(element
.getRaw())) {
1637 debugs(86, 5, "esiRemoveAdd: Failed for " << this);
1645 esiRemove::makeCacheable() const
1647 debugs(86, 5, "esiRemove::makeCacheable: Returning NULL");
1652 esiRemove::makeUsable(esiTreeParentPtr
, ESIVarState
&) const
1654 fatal ("esiRemove::Usable: unreachable code!\n");
1661 debugs(86, 5, "esiTry::~esiTry " << this);
1664 esiTry::esiTry(esiTreeParentPtr aParent
) :
1668 memset(&flags
, 0, sizeof(flags
));
1672 esiTry::render(ESISegment::Pointer output
)
1674 /* Try renders from it's children */
1676 assert (attempt
.getRaw());
1677 assert (except
.getRaw());
1678 debugs(86, 5, "esiTryRender: Rendering Try " << this);
1680 if (flags
.attemptok
) {
1681 attempt
->render(output
);
1682 } else if (flags
.exceptok
) {
1685 if (exceptbuffer
.getRaw())
1686 ESISegment::ListTransfer(exceptbuffer
, output
);
1688 except
->render(output
);
1690 debugs(86, 5, "esiTryRender: Neither except nor attempt succeeded?!?");
1693 /* Accept attempt and except only */
1695 esiTry::addElement(ESIElement::Pointer element
)
1697 debugs(86, 5, "esiTryAdd: Try " << this << " adding element " <<
1700 if (dynamic_cast<esiLiteral
*>(element
.getRaw())) {
1701 /* Swallow whitespace */
1702 debugs(86, 5, "esiTryAdd: Try " << this << " skipping whitespace " << element
.getRaw());
1706 if (dynamic_cast<esiAttempt
*>(element
.getRaw())) {
1707 if (attempt
.getRaw()) {
1708 debugs(86, DBG_IMPORTANT
, "esiTryAdd: Failed for " << this << " - try allready has an attempt node (section 3.4)");
1716 if (dynamic_cast<esiExcept
*>(element
.getRaw())) {
1717 if (except
.getRaw()) {
1718 debugs(86, DBG_IMPORTANT
, "esiTryAdd: Failed for " << this << " - try already has an except node (section 3.4)");
1726 debugs(86, DBG_IMPORTANT
, "esiTryAdd: Failed to add element " << element
.getRaw() << " to try " << this << ", incorrect element type (see section 3.4)");
1731 esiTry::bestAttemptRV() const
1733 if (flags
.attemptfailed
)
1734 return ESI_PROCESS_COMPLETE
;
1736 return ESI_PROCESS_PENDING_MAYFAIL
;
1740 esiTry::process (int dovars
)
1742 esiProcessResult_t rv
= ESI_PROCESS_PENDING_MAYFAIL
;
1745 if (!attempt
.getRaw()) {
1746 debugs(86, DBG_CRITICAL
, "esiTryProcess: Try has no attempt element - ESI template is invalid (section 3.4)");
1747 return ESI_PROCESS_FAILED
;
1750 if (!except
.getRaw()) {
1751 debugs(86, DBG_CRITICAL
, "esiTryProcess: Try has no except element - ESI template is invalid (section 3.4)");
1752 return ESI_PROCESS_FAILED
;
1755 if (!flags
.attemptfailed
)
1756 /* Try the attempt branch */
1757 switch ((rv
= attempt
->process(dovars
))) {
1759 case ESI_PROCESS_COMPLETE
:
1760 debugs(86, 5, "esiTryProcess: attempt Processed OK");
1761 flags
.attemptok
= 1;
1762 return ESI_PROCESS_COMPLETE
;
1764 case ESI_PROCESS_PENDING_WONTFAIL
:
1765 debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
1766 /* We're not done yet, but don't need to test except */
1767 return ESI_PROCESS_PENDING_WONTFAIL
;
1769 case ESI_PROCESS_PENDING_MAYFAIL
:
1770 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
1773 case ESI_PROCESS_FAILED
:
1774 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
1775 flags
.attemptfailed
= 1;
1779 /* attempt is either MAYFAIL or FAILED */
1781 return bestAttemptRV();
1783 /* query except to see if it has a definite result */
1784 if (!flags
.exceptfailed
)
1785 /* Try the except branch */
1786 switch (except
->process(dovars
)) {
1788 case ESI_PROCESS_COMPLETE
:
1789 debugs(86, 5, "esiTryProcess: except Processed OK");
1791 return bestAttemptRV();
1793 case ESI_PROCESS_PENDING_WONTFAIL
:
1794 debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
1795 /* We're not done yet, but can't fail */
1796 return ESI_PROCESS_PENDING_WONTFAIL
;
1798 case ESI_PROCESS_PENDING_MAYFAIL
:
1799 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
1800 /* The except branch fail fail */
1801 return ESI_PROCESS_PENDING_MAYFAIL
;
1803 case ESI_PROCESS_FAILED
:
1804 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
1805 flags
.exceptfailed
= 1;
1809 if (flags
.exceptfailed
&& flags
.attemptfailed
)
1810 return ESI_PROCESS_FAILED
;
1812 /* one of attempt or except returned PENDING MAYFAIL */
1813 return ESI_PROCESS_PENDING_MAYFAIL
;
1817 esiTry::notifyParent()
1819 if (flags
.attemptfailed
) {
1820 if (flags
.exceptok
) {
1821 parent
->provideData (exceptbuffer
, this);
1822 exceptbuffer
= NULL
;
1823 } else if (flags
.exceptfailed
|| except
.getRaw() == NULL
) {
1824 parent
->fail (this, "esi:try - except claused failed, or no except clause found");
1828 /* nothing to do when except fails and attempt hasn't */
1832 esiTry::fail(ESIElement
*source
, char const *anError
)
1835 assert (source
== attempt
|| source
== except
);
1836 debugs(86, 5, "esiTry::fail: this=" << this << ", source=" << source
<< ", message=" << anError
);
1838 if (source
== except
) {
1839 flags
.exceptfailed
= 1;
1841 flags
.attemptfailed
= 1;
1848 esiTry::provideData (ESISegment::Pointer data
, ESIElement
* source
)
1850 if (source
== attempt
) {
1851 flags
.attemptok
= 1;
1852 parent
->provideData (data
, this);
1853 } else if (source
== except
) {
1855 assert (exceptbuffer
== NULL
);
1856 ESISegment::ListTransfer (data
, exceptbuffer
);
1861 esiTry::esiTry(esiTry
const &old
)
1865 flags
.attemptok
= 0;
1867 flags
.attemptfailed
= 0;
1868 flags
.exceptfailed
= 0;
1870 exceptbuffer
= NULL
;
1874 esiTry::makeCacheable() const
1876 debugs(86, 5, "esiTry::makeCacheable: making cachable Try from " << this);
1877 esiTry
*resultT
= new esiTry (*this);
1878 ESIElement::Pointer result
= resultT
;
1880 if (attempt
.getRaw())
1881 resultT
->attempt
= attempt
->makeCacheable();
1883 if (except
.getRaw())
1884 resultT
->except
= except
->makeCacheable();
1890 esiTry::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
1892 debugs(86, 5, "esiTry::makeUsable: making usable Try from " << this);
1893 esiTry
*resultT
= new esiTry (*this);
1894 ESIElement::Pointer result
= resultT
;
1896 resultT
->parent
= newParent
;
1898 if (attempt
.getRaw())
1899 resultT
->attempt
= attempt
->makeUsable(resultT
, newVarState
);
1901 if (except
.getRaw())
1902 resultT
->except
= except
->makeUsable(resultT
, newVarState
);
1912 if (attempt
.getRaw())
1917 if (except
.getRaw())
1926 esiAttempt::operator new(size_t byteCount
)
1928 assert (byteCount
== sizeof (esiAttempt
));
1933 esiAttempt::operator delete (void *address
)
1935 cbdataFree (address
);
1943 esiExcept::operator new(size_t byteCount
)
1945 assert (byteCount
== sizeof (esiExcept
));
1947 CBDATA_INIT_TYPE_FREECB(esiExcept
, esiSequence::Free
);
1948 rv
= (void *)cbdataAlloc (esiExcept
);
1953 esiExcept::operator delete (void *address
)
1955 cbdataFree (address
);
1963 esiVar::operator new(size_t byteCount
)
1965 assert (byteCount
== sizeof (esiVar
));
1967 CBDATA_INIT_TYPE_FREECB(esiVar
, esiSequence::Free
);
1968 rv
= (void *)cbdataAlloc (esiVar
);
1973 esiVar::operator delete (void *address
)
1975 cbdataFree (address
);
1981 esiChoose::~esiChoose()
1983 debugs(86, 5, "esiChoose::~esiChoose " << this);
1986 esiChoose::esiChoose(esiTreeParentPtr aParent
) : elements (), chosenelement (-1),parent (aParent
)
1990 esiChoose::render(ESISegment::Pointer output
)
1992 /* append all processed elements, and trim processed and rendered elements */
1993 assert (output
->next
== NULL
);
1994 assert (elements
.size() || otherwise
.getRaw());
1995 debugs(86, 5, "esiChooseRender: rendering");
1997 if (chosenelement
>= 0)
1998 elements
[chosenelement
]->render(output
);
1999 else if (otherwise
.getRaw())
2000 otherwise
->render(output
);
2004 esiChoose::addElement(ESIElement::Pointer element
)
2006 /* add an element to the output list */
2008 if (dynamic_cast<esiLiteral
*>(element
.getRaw())) {
2009 /* Swallow whitespace */
2010 debugs(86, 5, "esiChooseAdd: Choose " << this << " skipping whitespace " << element
.getRaw());
2014 /* Some elements require specific parents */
2015 if (!(dynamic_cast<esiWhen
*>(element
.getRaw()) || dynamic_cast<esiOtherwise
*>(element
.getRaw()))) {
2016 debugs(86, DBG_CRITICAL
, "esiChooseAdd: invalid child node for esi:choose (section 3.3)");
2020 if (dynamic_cast<esiOtherwise
*>(element
.getRaw())) {
2021 if (otherwise
.getRaw()) {
2022 debugs(86, DBG_CRITICAL
, "esiChooseAdd: only one otherwise node allowed for esi:choose (section 3.3)");
2026 otherwise
= element
;
2028 elements
.push_back (element
);
2030 debugs (86,3, "esiChooseAdd: Added a new element, elements = " << elements
.size());
2032 if (chosenelement
== -1) {
2033 const esiWhen
* topElement
=dynamic_cast<esiWhen
*>(element
.getRaw());
2034 if (topElement
&& topElement
->testsTrue()) {
2035 chosenelement
= elements
.size() - 1;
2036 debugs (86,3, "esiChooseAdd: Chose element " << elements
.size());
2045 esiChoose::selectElement()
2047 if (chosenelement
> -1)
2050 for (size_t counter
= 0; counter
< elements
.size(); ++counter
) {
2051 const esiWhen
*el
= dynamic_cast<esiWhen
*>(elements
[counter
].getRaw());
2052 if (el
&& el
->testsTrue()) {
2053 chosenelement
= counter
;
2054 debugs (86,3, "esiChooseAdd: Chose element " << counter
+ 1);
2063 elements
.setNULL(0, elements
.size());
2065 if (otherwise
.getRaw())
2066 otherwise
->finish();
2074 ElementList::setNULL (int start
, int end
)
2076 assert (start
>= 0 && start
<= elementcount
);
2077 assert (end
>= 0 && end
<= elementcount
);
2079 for (int loopPosition
= start
; loopPosition
< end
; ++loopPosition
) {
2080 if (elements
[loopPosition
].getRaw())
2081 elements
[loopPosition
]->finish();
2083 debugs(86, 5, "esiSequence::NULLElements: Setting index " <<
2084 loopPosition
<< ", pointer " <<
2085 elements
[loopPosition
].getRaw() << " to NULL");
2087 elements
[loopPosition
] = NULL
;
2092 esiChoose::NULLUnChosen()
2094 if (chosenelement
>= 0) {
2095 if (otherwise
.getRaw())
2096 otherwise
->finish();
2100 elements
.setNULL (0, chosenelement
);
2102 elements
.setNULL (chosenelement
+ 1, elements
.size());
2103 } else if (otherwise
.getRaw()) {
2104 elements
.setNULL (0, elements
.size());
2109 esiChoose::process (int dovars
)
2111 /* process as much of the list as we can, stopping only on
2114 /* We MUST have a when clause */
2117 if (!elements
.size()) {
2120 if (otherwise
.getRaw())
2121 otherwise
->finish();
2127 return ESI_PROCESS_FAILED
;
2130 if (chosenelement
>= 0) {
2131 return elements
[chosenelement
]->process(dovars
);
2132 } else if (otherwise
.getRaw())
2133 return otherwise
->process(dovars
);
2135 return ESI_PROCESS_COMPLETE
;
2139 esiChoose::checkValidSource (ESIElement::Pointer source
) const
2141 if (!elements
.size())
2142 fatal ("invalid callback = no when clause\n");
2144 if (chosenelement
>= 0)
2145 assert (source
== elements
[chosenelement
]);
2146 else if (otherwise
.getRaw())
2147 assert (source
== otherwise
);
2149 fatal ("esiChoose::checkValidSource: invalid callback - no elements chosen\n");
2153 esiChoose::fail(ESIElement
* source
, char const *anError
)
2155 checkValidSource (source
);
2156 elements
.setNULL (0, elements
.size());
2158 if (otherwise
.getRaw())
2159 otherwise
->finish();
2163 parent
->fail(this, anError
);
2169 esiChoose::provideData (ESISegment::Pointer data
, ESIElement
*source
)
2171 checkValidSource (source
);
2172 parent
->provideData (data
, this);
2175 esiChoose::esiChoose(esiChoose
const &old
) : chosenelement(-1), otherwise (NULL
), parent (NULL
)
2177 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2178 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeCacheable();
2180 if (newElement
.getRaw())
2181 assert (addElement(newElement
));
2186 esiChoose::makeCachableElements(esiChoose
const &old
)
2188 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2189 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeCacheable();
2191 if (newElement
.getRaw())
2192 assert (addElement(newElement
));
2197 esiChoose::makeUsableElements(esiChoose
const &old
, ESIVarState
&newVarState
)
2199 for (size_t counter
= 0; counter
< old
.elements
.size(); ++counter
) {
2200 ESIElement::Pointer newElement
= old
.elements
[counter
]->makeUsable (this, newVarState
);
2202 if (newElement
.getRaw())
2203 assert (addElement(newElement
));
2208 esiChoose::makeCacheable() const
2210 esiChoose
*resultC
= new esiChoose (*this);
2211 ESIElement::Pointer result
= resultC
;
2212 resultC
->makeCachableElements(*this);
2214 if (otherwise
.getRaw())
2215 resultC
->otherwise
= otherwise
->makeCacheable();
2221 esiChoose::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
2223 esiChoose
*resultC
= new esiChoose (*this);
2224 ESIElement::Pointer result
= resultC
;
2225 resultC
->parent
= newParent
;
2226 resultC
->makeUsableElements(*this, newVarState
);
2227 resultC
->selectElement();
2229 if (otherwise
.getRaw())
2230 resultC
->otherwise
= otherwise
->makeUsable(resultC
, newVarState
);
2236 ElementList::ElementList () : elements(NULL
), allocedcount(0), allocedsize(0), elementcount (0)
2239 ElementList::~ElementList()
2241 debugs(86, 5, "ElementList::~ElementList " << this);
2242 setNULL(0, elementcount
);
2245 memFreeBuf (allocedsize
, elements
);
2248 ESIElement::Pointer
&
2249 ElementList::operator [] (int index
)
2251 return elements
[index
];
2254 ESIElement::Pointer
const &
2255 ElementList::operator [] (int index
) const
2257 return elements
[index
];
2261 ElementList::pop_front (size_t const count
)
2266 memmove(elements
, &elements
[count
], (elementcount
- count
) * sizeof (ESIElement::Pointer
));
2268 elementcount
-= count
;
2272 ElementList::push_back(ESIElement::Pointer
&newElement
)
2274 elements
= (ESIElement::Pointer
*)memReallocBuf (elements
, ++elementcount
* sizeof (ESIElement::Pointer
),
2277 allocedcount
= elementcount
;
2278 memset(&elements
[elementcount
- 1], '\0', sizeof (ESIElement::Pointer
));
2279 elements
[elementcount
- 1] = newElement
;
2283 ElementList::size() const
2285 return elementcount
;
2289 esiWhen::esiWhen(esiTreeParentPtr aParent
, int attrcount
, const char **attr
,ESIVarState
*aVar
) :
2290 esiSequence(aParent
),
2292 unevaluatedExpression(NULL
),
2295 char const *expression
= NULL
;
2297 for (int loopCounter
= 0; loopCounter
< attrcount
&& attr
[loopCounter
]; loopCounter
+= 2) {
2298 if (!strcmp(attr
[loopCounter
],"test")) {
2300 debugs(86, 5, "esiWhen::esiWhen: Evaluating '" << attr
[loopCounter
+1] << "'");
2301 /* TODO: warn the user instead of asserting */
2302 assert (expression
== NULL
);
2303 expression
= attr
[loopCounter
+1];
2305 /* ignore mistyped attributes.
2306 * TODO:? error on these for user feedback - config parameter needed
2308 debugs(86, DBG_IMPORTANT
, "Found misttyped attribute on ESI When clause");
2312 /* No expression ? default is not matching */
2316 unevaluatedExpression
= xstrdup(expression
);
2318 varState
= cbdataReference (aVar
);
2325 safe_free (unevaluatedExpression
);
2328 cbdataReferenceDone (varState
);
2334 if (!unevaluatedExpression
)
2339 varState
->feedData(unevaluatedExpression
, strlen (unevaluatedExpression
));
2341 char const *expression
= varState
->extractChar ();
2343 setTestResult(ESIExpression::Evaluate (expression
));
2345 safe_free (expression
);
2348 esiWhen::esiWhen(esiWhen
const &old
) :
2351 unevaluatedExpression(NULL
),
2354 if (old
.unevaluatedExpression
)
2355 unevaluatedExpression
= xstrdup(old
.unevaluatedExpression
);
2359 esiWhen::makeCacheable() const
2361 return new esiWhen(*this);
2365 esiWhen::makeUsable(esiTreeParentPtr newParent
, ESIVarState
&newVarState
) const
2367 esiWhen
*resultW
= new esiWhen (*this);
2368 ESIElement::Pointer result
= resultW
;
2369 resultW
->parent
= newParent
;
2370 resultW
->makeUsableElements(*this, newVarState
);
2371 resultW
->varState
= cbdataReference (&newVarState
);
2372 resultW
->evaluate();
2379 esiOtherwise::operator new(size_t byteCount
)
2381 assert (byteCount
== sizeof (esiOtherwise
));
2383 CBDATA_INIT_TYPE_FREECB(esiOtherwise
, esiSequence::Free
);
2384 rv
= (void *)cbdataAlloc (esiOtherwise
);
2389 esiOtherwise::operator delete (void *address
)
2391 cbdataFree (address
);
2396 /* TODO: implement surrogate targeting and control processing */
2398 esiEnableProcessing (HttpReply
*rep
)
2402 if (rep
->surrogate_control
) {
2403 HttpHdrScTarget
*sctusable
=
2404 rep
->surrogate_control
->getMergedTarget(Config
.Accel
.surrogate_id
);
2406 if (!sctusable
|| !sctusable
->hasContent())
2407 /* Nothing generic or targeted at us, or no
2408 * content processing requested
2412 if (sctusable
->content().pos("ESI/1.0") != NULL
)
2421 #endif /* USE_SQUID_ESI == 1 */