source: squid-ssl/trunk/fuentes/src/esi/Esi.cc @ 5496

Last change on this file since 5496 was 5496, checked in by Juanma, 22 months ago

Initial release

File size: 65.0 KB
Line 
1/*
2 * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
3 *
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.
7 */
8
9/* DEBUG: section 86    ESI processing */
10
11#include "squid.h"
12
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.
15 */
16#if (USE_SQUID_ESI == 1)
17
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"
27#include "esi/Esi.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"
37#include "Mem.h"
38#include "MemBuf.h"
39#include "profiler/Profiler.h"
40#include "SquidConfig.h"
41
42/* quick reference on behaviour here.
43 * The ESI specification 1.0 requires the ESI processor to be able to
44 * return an error code at any point in the processing. To that end
45 * we buffer the incoming esi body until we know we will be able to
46 * satisfy the request. At that point we start streaming the queued
47 * data downstream.
48 *
49 */
50
51class ESIStreamContext;
52
53/* TODO: split this out into separate files ? */
54/* Parsing: quick and dirty. ESI files are not valid XML, so a generic
55 * XML parser is not much use. Also we need a push parser not a pull
56 * parser, so LibXML is out.
57 *
58 * Interpreter methods:
59 * Render: May only ever be called after Process returns PROCESS_COMPLETE.
60 * Renders the resulting content into a ESISegment chain.
61 * Process: returns the status of the node.
62 * COMPLETE - processing is complete, rendering may staret
63 * PENDING_WONTFAIL - process is incomplete, but the element *will*
64 *   be able to be rendered given time.
65 * PENDING_MAYFAIL - processing is incomplete, and the element *may*
66 *   fail to be able to rendered.
67 * FAILED - processing failed, return an error to the client.
68 */
69
70/*
71 * NOT TODO: esi:inline - out of scope.
72 */
73
74/* make comparisons with refcount pointers easy */
75bool operator == (ESIElement const *lhs, ESIElement::Pointer const &rhs)
76{
77    return lhs == rhs.getRaw();
78}
79
80typedef ESIContext::esiKick_t esiKick_t;
81
82/* some core operators */
83
84/* esiComment */
85
86struct esiComment : public ESIElement {
87    MEMPROXY_CLASS(esiComment);
88    ~esiComment();
89    esiComment();
90    Pointer makeCacheable() const;
91    Pointer makeUsable(esiTreeParentPtr, ESIVarState &) const;
92
93    void render(ESISegment::Pointer);
94    void finish();
95};
96
97MEMPROXY_CLASS_INLINE(esiComment);
98
99#include "esi/Literal.h"
100
101#include "esi/Sequence.h"
102
103#include "esi/Include.h"
104
105/* esiRemove */
106
107class esiRemove : public ESIElement
108{
109
110public:
111    void *operator new (size_t byteCount);
112    void operator delete (void *address);
113
114    esiRemove();
115    void render(ESISegment::Pointer);
116    bool addElement (ESIElement::Pointer);
117    Pointer makeCacheable() const;
118    Pointer makeUsable(esiTreeParentPtr, ESIVarState &) const;
119    void finish();
120};
121
122CBDATA_TYPE (esiRemove);
123static FREE esiRemoveFree;
124static ESIElement * esiRemoveNew(void);
125
126/* esiTry */
127
128struct esiTry : public ESIElement {
129    MEMPROXY_CLASS(esiTry);
130
131    esiTry(esiTreeParentPtr aParent);
132    ~esiTry();
133
134    void render(ESISegment::Pointer);
135    bool addElement (ESIElement::Pointer);
136    void fail(ESIElement *, char const * = NULL);
137    esiProcessResult_t process (int dovars);
138    void provideData (ESISegment::Pointer data, ESIElement * source);
139    Pointer makeCacheable() const;
140    Pointer makeUsable(esiTreeParentPtr, ESIVarState &) const;
141
142    ESIElement::Pointer attempt;
143    ESIElement::Pointer except;
144
145    struct {
146        int attemptok:1; /* the attempt branch process correctly */
147        int exceptok:1; /* likewise */
148        int attemptfailed:1; /* The attempt branch failed */
149        int exceptfailed:1; /* the except branch failed */
150    } flags;
151    void finish();
152
153private:
154    void notifyParent();
155    esiTreeParentPtr parent;
156    ESISegment::Pointer exceptbuffer;
157    esiTry (esiTry const &);
158    esiProcessResult_t bestAttemptRV() const;
159};
160
161MEMPROXY_CLASS_INLINE(esiTry);
162
163#include "esi/Var.h"
164
165/* esiChoose */
166
167struct esiChoose : public ESIElement {
168    MEMPROXY_CLASS(esiChoose);
169
170    esiChoose(esiTreeParentPtr);
171    ~esiChoose();
172
173    void render(ESISegment::Pointer);
174    bool addElement (ESIElement::Pointer);
175    void fail(ESIElement *, char const * = NULL);
176    esiProcessResult_t process (int dovars);
177
178    void provideData (ESISegment::Pointer data, ESIElement *source);
179    void makeCachableElements(esiChoose const &old);
180    void makeUsableElements(esiChoose const &old, ESIVarState &);
181    Pointer makeCacheable() const;
182    Pointer makeUsable(esiTreeParentPtr, ESIVarState &) const;
183    void NULLUnChosen();
184
185    ElementList elements;
186    int chosenelement;
187    ESIElement::Pointer otherwise;
188    void finish();
189
190private:
191    esiChoose(esiChoose const &);
192    esiTreeParentPtr parent;
193    void checkValidSource (ESIElement::Pointer source) const;
194    void selectElement();
195};
196
197MEMPROXY_CLASS_INLINE(esiChoose);
198
199/* esiWhen */
200
201struct esiWhen : public esiSequence {
202    MEMPROXY_CLASS(esiWhen);
203    esiWhen(esiTreeParentPtr aParent, int attributes, const char **attr, ESIVarState *);
204    ~esiWhen();
205    Pointer makeCacheable() const;
206    Pointer makeUsable(esiTreeParentPtr, ESIVarState &) const;
207
208    bool testsTrue() const { return testValue;}
209
210    void setTestResult(bool aBool) {testValue = aBool;}
211
212private:
213    esiWhen (esiWhen const &);
214    bool testValue;
215    char const *unevaluatedExpression;
216    ESIVarState *varState;
217    void evaluate();
218};
219
220MEMPROXY_CLASS_INLINE(esiWhen);
221
222/* esiOtherwise */
223
224struct esiOtherwise : public esiSequence {
225    //    void *operator new (size_t byteCount);
226    //    void operator delete (void *address);
227    esiOtherwise(esiTreeParentPtr aParent) : esiSequence (aParent) {}
228};
229
230CBDATA_CLASS_INIT(ESIContext);
231
232void ESIContext::startRead()
233{
234    assert (!reading_);
235    reading_ = true;
236}
237
238void ESIContext::finishRead()
239{
240    assert (reading_);
241    reading_ = false;
242}
243
244bool ESIContext::reading() const
245{
246    return reading_;
247}
248
249ESIStreamContext::ESIStreamContext() : finished(false), include (NULL), localbuffer (new ESISegment), buffer (NULL)
250{}
251
252/* Local functions */
253/* ESIContext */
254static ESIContext *ESIContextNew(HttpReply *, clientStreamNode *, ClientHttpRequest *);
255
256void
257ESIContext::setError()
258{
259    errorpage = ERR_ESI;
260    errorstatus = Http::scInternalServerError;
261    flags.error = 1;
262}
263
264void
265ESIContext::appendOutboundData(ESISegment::Pointer theData)
266{
267    if (!outbound.getRaw()) {
268        outbound = theData;
269        outboundtail = outbound;
270    } else {
271        assert (outboundtail->next.getRaw() == NULL);
272        outboundtail->next = theData;
273    }
274
275    fixupOutboundTail();
276    debugs(86, 9, "ESIContext::appendOutboundData: outbound " << outbound.getRaw());
277}
278
279void
280ESIContext::provideData (ESISegment::Pointer theData, ESIElement * source)
281{
282    debugs(86, 5, "ESIContext::provideData: " << this << " " << theData.getRaw() << " " << source);
283    /* No callbacks permitted after finish() called on the tree */
284    assert (tree.getRaw());
285    assert (source == tree);
286    appendOutboundData(theData);
287    trimBlanks();
288
289    if (!processing)
290        send();
291}
292
293void
294ESIContext::fail (ESIElement * source, char const *anError)
295{
296    setError();
297    setErrorMessage (anError);
298    fail ();
299    send ();
300}
301
302void
303ESIContext::fixupOutboundTail()
304{
305    /* TODO: fixup thisNode outboundtail dross a little */
306
307    if (outboundtail.getRaw())
308        outboundtail = outboundtail->tail();
309}
310
311esiKick_t
312ESIContext::kick ()
313{
314    if (flags.kicked) {
315        debugs(86, 5, "esiKick: Re-entered whilst in progress");
316        // return ESI_KICK_INPROGRESS;
317    } else
318        ++flags.kicked;
319
320    if (flags.detached)
321        /* we've been detached from - we can't do anything more */
322        return ESI_KICK_FAILED;
323
324    /* Something has occured. Process any remaining nodes */
325    if (!flags.finished)
326        /* Process some of our data */
327        switch (process ()) {
328
329        case ESI_PROCESS_COMPLETE:
330            debugs(86, 5, "esiKick: esiProcess OK");
331            break;
332
333        case ESI_PROCESS_PENDING_WONTFAIL:
334            debugs(86, 5, "esiKick: esiProcess PENDING OK");
335            break;
336
337        case ESI_PROCESS_PENDING_MAYFAIL:
338            debugs(86, 5, "esiKick: esiProcess PENDING UNKNOWN");
339            break;
340
341        case ESI_PROCESS_FAILED:
342            debugs(86, 2, "esiKick: esiProcess " << this << " FAILED");
343            /* this can not happen - processing can't fail until we have data,
344             * and when we come here we have sent data to the client
345             */
346
347            if (pos == 0)
348                fail ();
349
350            --flags.kicked;
351
352            return ESI_KICK_FAILED;
353        }
354
355    /* Render if we can to get maximal sent data */
356    assert (tree.getRaw() || flags.error);
357
358    if (!flags.finished && !outbound.getRaw()) {
359        outboundtail = new ESISegment;
360        outbound = outboundtail;
361    }
362
363    if (!flags.error && !flags.finished)
364        tree->render(outboundtail);
365
366    if (!flags.finished)
367        fixupOutboundTail();
368
369    /* Is there data to send? */
370    if (send ()) {
371        /* some data was sent. we're finished until the next read */
372        --flags.kicked;
373        return ESI_KICK_SENT;
374    }
375
376    --flags.kicked;
377    /* nothing to send */
378    return flags.error ? ESI_KICK_FAILED : ESI_KICK_PENDING;
379}
380
381/* request from downstream for more data
382 */
383void
384esiStreamRead (clientStreamNode *thisNode, ClientHttpRequest *http)
385{
386    clientStreamNode *next;
387    /* Test preconditions */
388    assert (thisNode != NULL);
389    assert (cbdataReferenceValid (thisNode));
390    /* we are not in the chain until ESI is detected on a data callback */
391    assert (thisNode->node.prev != NULL);
392    assert (thisNode->node.next != NULL);
393
394    ESIContext::Pointer context = dynamic_cast<ESIContext *>(thisNode->data.getRaw());
395    assert (context.getRaw() != NULL);
396
397    if (context->flags.passthrough) {
398        /* passthru mode - read into supplied buffers */
399        next = thisNode->next();
400        clientStreamRead (thisNode, http, next->readBuffer);
401        return;
402    }
403
404    context->flags.clientwantsdata = 1;
405    debugs(86, 5, "esiStreamRead: Client now wants data");
406
407    /* Ok, not passing through */
408
409    switch (context->kick ()) {
410
411    case ESIContext::ESI_KICK_FAILED:
412    /* this can not happen - processing can't fail until we have data,
413     * and when we come here we have sent data to the client
414     */
415
416    case ESIContext::ESI_KICK_SENT:
417
418    case ESIContext::ESI_KICK_INPROGRESS:
419        return;
420
421    case ESIContext::ESI_KICK_PENDING:
422        break;
423    }
424
425    /* Nothing to send */
426
427    if (context->flags.oktosend && (context->flags.finishedtemplate
428                                    || context->cachedASTInUse) &&
429            ! context->flags.finished) {
430        /* we've started sending, finished reading, but not finished
431         * processing. stop here, a callback will resume the stream
432         * flow
433         */
434        debugs(86, 5, "esiStreamRead: Waiting for async resume of esi processing");
435        return;
436    }
437
438    if (context->flags.oktosend && context->flags.finished && context->outbound.getRaw()) {
439        debugs(86, 5, "all processing complete, but outbound data still buffered");
440        assert (!context->flags.clientwantsdata);
441        /* client MUST be processing the last reply */
442        return;
443    }
444
445    if (context->flags.oktosend && context->flags.finished) {
446        StoreIOBuffer tempBuffer;
447        assert (!context->outbound.getRaw());
448        /* We've finished processing, and there is no more data buffered */
449        debugs(86, 5, "Telling recipient EOF on READ");
450        clientStreamCallback (thisNode, http, NULL, tempBuffer);
451        return;
452    }
453
454    if (context->reading())
455        return;
456
457    /* no data that is ready to send, and still reading? well, lets get some */
458    /* secure a buffer */
459    if (!context->incoming.getRaw()) {
460        /* create a new buffer segment */
461        context->buffered = new ESISegment;
462        context->incoming = context->buffered;
463    }
464
465    assert (context->incoming.getRaw() && context->incoming->len != HTTP_REQBUF_SZ);
466    {
467        StoreIOBuffer tempBuffer;
468        tempBuffer.offset =  context->readpos;
469        tempBuffer.length = context->incoming->len - HTTP_REQBUF_SZ;
470        tempBuffer.data = &context->incoming->buf[context->incoming->len];
471        context->startRead();
472        clientStreamRead (thisNode, http, tempBuffer);
473    }
474}
475
476clientStream_status_t
477esiStreamStatus (clientStreamNode *thisNode, ClientHttpRequest *http)
478{
479    /* Test preconditions */
480    assert (thisNode != NULL);
481    assert (cbdataReferenceValid (thisNode));
482    /* we are not in the chain until ESI is detected on a data callback */
483    assert (thisNode->node.prev != NULL);
484    assert (thisNode->node.next != NULL);
485
486    ESIContext::Pointer context = dynamic_cast<ESIContext *>(thisNode->data.getRaw());
487    assert (context.getRaw() != NULL);
488
489    if (context->flags.passthrough)
490        return clientStreamStatus (thisNode, http);
491
492    if (context->flags.oktosend && context->flags.finished &&
493            !(context->outbound.getRaw() && context->outbound_offset < context->outbound->len)) {
494        debugs(86, 5, "Telling recipient EOF on STATUS");
495        return STREAM_UNPLANNED_COMPLETE; /* we don't know lengths in advance */
496    }
497
498    /* ?? RC: we can't be aborted / fail ? */
499    return STREAM_NONE;
500}
501
502static int
503esiAlwaysPassthrough(Http::StatusCode sline)
504{
505    int result;
506
507    switch (sline) {
508
509    case Http::scContinue: /* Should never reach us... but squid needs to alter to accomodate this */
510
511    case Http::scSwitchingProtocols: /* Ditto */
512
513    case Http::scProcessing: /* Unknown - some extension */
514
515    case Http::scNoContent: /* no body, no esi */
516
517    case Http::scNotModified: /* ESI does not affect assembled page headers, so 304s are valid */
518        result = 1;
519        /* unreached */
520        break;
521
522    default:
523        result = 0;
524    }
525
526    return result;
527}
528
529void
530ESIContext::trimBlanks()
531{
532    /* trim leading empty buffers ? */
533
534    while (outbound.getRaw() && outbound->next.getRaw() && !outbound->len) {
535        debugs(86, 5, "ESIContext::trimBlanks: " << this <<
536               " skipping segment " << outbound.getRaw());
537        outbound = outbound->next;
538    }
539
540    if (outboundtail.getRaw())
541        assert (outbound.getRaw());
542}
543
544/* Send data downstream
545 * Returns 0 if nothing was sent. Non-zero if data was sent.
546 */
547size_t
548ESIContext::send ()
549{
550    debugs(86, 5, "ESIContext::send: this=" << this);
551    /* send any processed data */
552
553    trimBlanks();
554
555    if (!flags.clientwantsdata) {
556        debugs(86, 5, "ESIContext::send: Client does not want data - not sending anything");
557        return 0;
558    }
559
560    if (tree.getRaw() && tree->mayFail()) {
561        debugs(86, 5, "ESIContext::send: Tree may fail. Not sending.");
562        return 0;
563    } else
564        flags.oktosend = 1;
565
566#if 0
567
568    if (!flags.oktosend) {
569
570        fatal("ESIContext::send: Not OK to send.\n");
571        return 0;
572    }
573
574#endif
575
576    if (!(rep || (outbound.getRaw() &&
577                  outbound->len && (outbound_offset <= outbound->len)))) {
578        debugs(86, 5, "ESIContext::send: Nothing to send.");
579        return 0;
580    }
581
582    debugs(86, 5, "ESIContext::send: Sending something...");
583    /* Yes! Send it without asking for more upstream */
584    /* memcopying because the client provided the buffer */
585    /* TODO: skip data until pos == next->readoff; */
586    assert (thisNode->data == this);
587    clientStreamNode *next = thisNode->next();
588    ESIContext *templock = cbdataReference (this);
589    size_t len = 0;
590
591    if (outbound.getRaw())
592        len = min (next->readBuffer.length, outbound->len - outbound_offset);
593
594    /* prevent corruption on range requests, even though we don't support them yet */
595    assert (pos == next->readBuffer.offset);
596
597    /* We must send data or a reply */
598    assert (len != 0 || rep != NULL);
599
600    if (len) {
601        memcpy(next->readBuffer.data, &outbound->buf[outbound_offset], len);
602
603        if (len + outbound_offset == outbound->len) {
604            ESISegment::Pointer temp = outbound->next;
605            /* remove the used buffer */
606            outbound_offset = 0;
607            outbound = temp;
608        }
609
610        pos += len;
611
612        if (!outbound.getRaw())
613            outboundtail = NULL;
614
615        trimBlanks();
616    }
617
618    flags.clientwantsdata = 0;
619    debugs(86, 5, "ESIContext::send: this=" << this << " Client no longer wants data ");
620    /* Deal with re-entrancy */
621    HttpReply *temprep = rep;
622    rep = NULL; /* freed downstream */
623
624    if (temprep && varState)
625        varState->buildVary (temprep);
626
627    {
628        StoreIOBuffer tempBuffer;
629        tempBuffer.length = len;
630        tempBuffer.offset = pos - len;
631        tempBuffer.data = next->readBuffer.data;
632        clientStreamCallback (thisNode, http, temprep, tempBuffer);
633    }
634
635    if (len == 0)
636        len = 1; /* tell the caller we sent something (because we sent headers */
637
638    cbdataReferenceDone (templock);
639
640    debugs (86,5,"ESIContext::send: this=" << this << " sent " << len);
641
642    return len;
643}
644
645void
646ESIContext::finishChildren()
647{
648    if (tree.getRaw())
649        tree->finish();
650
651    tree = NULL;
652}
653
654/* Detach event from a client Stream */
655void
656esiStreamDetach (clientStreamNode *thisNode, ClientHttpRequest *http)
657{
658    /* if we have pending callbacks, tell them we're done. */
659    /* test preconditions */
660    assert (thisNode != NULL);
661    assert (cbdataReferenceValid (thisNode));
662    ESIContext::Pointer context = dynamic_cast<ESIContext *>(thisNode->data.getRaw());
663    assert (context.getRaw() != NULL);
664    /* detach from the stream */
665    clientStreamDetach (thisNode,http);
666    /* if we have pending callbacks (from subincludes), tell them we're done. */
667    context->thisNode = NULL;
668    context->flags.detached = 1;
669    context->finishChildren();
670    /* HACK for parser stack not being emptied */
671    context->parserState.stack[0] = NULL;
672    /* allow refcount logic to trigger */
673    context->cbdataLocker = NULL;
674}
675
676/* Process incoming data for ESI tags */
677/* ESI TODO: Long term: we should have a framework to parse html/xml and
678 * callback to a set of processors like thisNode, to prevent multiple parsing
679 * overhead. More thoughts on thisNode: We have to parse multiple times, because
680 * the output of one processor may create a very different tree. What we could
681 * do is something like DOM and pass that down to a final renderer. This is
682 * getting into web server territory though...
683 *
684 * Preconditions:
685 *   This is not the last node in the stream.
686 *   ESI processing has been enabled.
687 *   There is context data or a reply structure
688 */
689void
690esiProcessStream (clientStreamNode *thisNode, ClientHttpRequest *http, HttpReply *rep, StoreIOBuffer receivedData)
691{
692    /* test preconditions */
693    assert (thisNode != NULL);
694    /* ESI TODO: handle thisNode rather than asserting - it should only ever
695     * happen if we cause an abort and the callback chain
696     * loops back to here, so we can simply return. However, that itself
697     * shouldn't happen, so it stays as an assert for now. */
698    assert (cbdataReferenceValid (thisNode));
699    /*
700     * if data is NULL thisNode is the first entrance. If rep is also NULL,
701     * something is wrong.
702     * */
703    assert (thisNode->data.getRaw() != NULL || rep);
704    assert (thisNode->node.next != NULL);
705
706    if (!thisNode->data.getRaw())
707        /* setup ESI context from reply headers */
708        thisNode->data = ESIContextNew(rep, thisNode, http);
709
710    ESIContext::Pointer context = dynamic_cast<ESIContext *>(thisNode->data.getRaw());
711
712    assert (context.getRaw() != NULL);
713
714    context->finishRead();
715
716    /* Skipping all ESI processing. All remaining data gets untouched.
717     * Mainly used when an error or other non-ESI processable entity
718     * has been detected to prevent ESI processing the error body
719     */
720    if (context->flags.passthrough) {
721        clientStreamCallback (thisNode, http, rep, receivedData);
722        return;
723    }
724
725    debugs(86, 3, "esiProcessStream: Processing thisNode " << thisNode <<
726           " context " << context.getRaw() << " offset " <<
727           (int) receivedData.offset << " length " <<
728           (unsigned int)receivedData.length);
729
730    /* once we finish the template, we *cannot* return here */
731    assert (!context->flags.finishedtemplate);
732    assert (!context->cachedASTInUse);
733
734    /* Can we generate any data ?*/
735
736    if (receivedData.data) {
737        /* Increase our buffer area with incoming data */
738        assert (receivedData.length <= HTTP_REQBUF_SZ);
739        assert (thisNode->readBuffer.offset == receivedData.offset);
740        debugs (86,5, "esiProcessStream found " << receivedData.length << " bytes of body data at offset " << receivedData.offset);
741        /* secure the data for later use */
742
743        if (!context->incoming.getRaw()) {
744            /* create a new buffer segment */
745            debugs(86, 5, "esiProcessStream: Setting up incoming buffer");
746            context->buffered = new ESISegment;
747            context->incoming = context->buffered;
748        }
749
750        if (receivedData.data != &context->incoming->buf[context->incoming->len]) {
751            /* We have to copy the data out because we didn't supply thisNode buffer */
752            size_t space = HTTP_REQBUF_SZ - context->incoming->len;
753            size_t len = min (space, receivedData.length);
754            debugs(86, 5, "Copying data from " << receivedData.data << " to " <<
755                   &context->incoming->buf[context->incoming->len] <<
756                   " because our buffer was not used");
757
758            memcpy(&context->incoming->buf[context->incoming->len], receivedData.data, len);
759            context->incoming->len += len;
760
761            if (context->incoming->len == HTTP_REQBUF_SZ) {
762                /* append another buffer */
763                context->incoming->next = new ESISegment;
764                context->incoming = context->incoming->next;
765            }
766
767            if (len != receivedData.length) {
768                /* capture the remnants */
769                memcpy(context->incoming->buf, &receivedData.data[len], receivedData.length - len);
770                context->incoming->len = receivedData.length - len;
771            }
772
773            /* and note where we are up to */
774            context->readpos += receivedData.length;
775        } else {
776            /* update our position counters, and if needed assign a new buffer */
777            context->incoming->len += receivedData.length;
778            assert (context->incoming->len <= HTTP_REQBUF_SZ);
779
780            if (context->incoming->len > HTTP_REQBUF_SZ * 3 / 4) {
781                /* allocate a new buffer - to stop us asking for ridiculously small amounts */
782                context->incoming->next = new ESISegment;
783                context->incoming = context->incoming->next;
784            }
785
786            context->readpos += receivedData.length;
787        }
788    }
789
790    /* EOF / Read error /  aborted entry */
791    if (rep == NULL && receivedData.data == NULL && receivedData.length == 0 && !context->flags.finishedtemplate) {
792        /* TODO: get stream status to test the entry for aborts */
793        /* else flush the esi processor */
794        debugs(86, 5, "esiProcess: " << context.getRaw() << " Finished reading upstream data");
795        /* This is correct */
796        context->flags.finishedtemplate = 1;
797    }
798
799    switch (context->kick()) {
800
801    case ESIContext::ESI_KICK_FAILED:
802        /* thisNode can not happen - processing can't fail until we have data,
803         * and when we come here we have sent data to the client
804         */
805        return;
806
807    case ESIContext::ESI_KICK_SENT:
808
809    case ESIContext::ESI_KICK_INPROGRESS:
810        return;
811
812    case ESIContext::ESI_KICK_PENDING:
813        break;
814    }
815
816    /* ok.. no data sent, try to pull more data in from upstream.
817     * FIXME: Don't try thisNode if we have finished reading the template
818     */
819    if (!context->flags.finishedtemplate && !context->reading()
820            && !context->cachedASTInUse) {
821        StoreIOBuffer tempBuffer;
822        assert (context->incoming.getRaw() && context->incoming->len < HTTP_REQBUF_SZ);
823        tempBuffer.offset = context->readpos;
824        tempBuffer.length =  HTTP_REQBUF_SZ - context->incoming->len;
825        tempBuffer.data = &context->incoming->buf[context->incoming->len];
826        context->startRead();
827        clientStreamRead (thisNode, http, tempBuffer);
828        return;
829    }
830
831    debugs(86, 3, "esiProcessStream: no data to send, no data to read, awaiting a callback");
832}
833
834ESIContext::~ESIContext()
835{
836    freeResources ();
837    /* Not freed by freeresources because esi::fail needs it */
838    safe_free (errormessage);
839    debugs(86, 3, "ESIContext::~ESIContext: Freed " << this);
840}
841
842ESIContext *
843ESIContextNew (HttpReply *rep, clientStreamNode *thisNode, ClientHttpRequest *http)
844{
845    assert (rep);
846    ESIContext *rv = new ESIContext;
847    rv->rep = rep;
848    rv->cbdataLocker = rv;
849
850    if (esiAlwaysPassthrough(rep->sline.status())) {
851        rv->flags.passthrough = 1;
852    } else {
853        /* remove specific headers for ESI to prevent
854         * downstream cache confusion */
855        HttpHeader *hdr = &rep->header;
856        hdr->delById(HDR_ACCEPT_RANGES);
857        hdr->delById(HDR_ETAG);
858        hdr->delById(HDR_CONTENT_LENGTH);
859        hdr->delById(HDR_CONTENT_MD5);
860        rv->tree = new esiSequence (rv, true);
861        rv->thisNode = thisNode;
862        rv->http = http;
863        rv->flags.clientwantsdata = 1;
864        rv->varState = new ESIVarState (&http->request->header, http->uri);
865        debugs(86, 5, "ESIContextNew: Client wants data (always created during reply cycle");
866    }
867
868    debugs(86, 5, "ESIContextNew: Create context " << rv);
869    return rv;
870}
871
872ESIElement::ESIElementType_t
873ESIElement::IdentifyElement (const char *el)
874{
875    int offset = 0;
876    assert (el);
877
878    if (strlen (el) < 5)
879        return ESI_ELEMENT_NONE;
880
881    if (!strncmp (el, "esi:", 4))
882        offset = 4;
883    else if (!strncmp (el, "http://www.edge-delivery.org/esi/1.0|", 37))
884        offset = 37;
885    else
886        return ESI_ELEMENT_NONE;
887
888    if (!strncmp (el + offset, "otherwise", 9))
889        return ESI_ELEMENT_OTHERWISE;
890
891    if (!strncmp (el + offset, "comment", 7))
892        return ESI_ELEMENT_COMMENT;
893
894    if (!strncmp (el + offset, "include", 7))
895        return ESI_ELEMENT_INCLUDE;
896
897    if (!strncmp (el + offset, "attempt", 7))
898        return ESI_ELEMENT_ATTEMPT;
899
900    if (!strncmp (el + offset, "assign", 6))
901        return ESI_ELEMENT_ASSIGN;
902
903    if (!strncmp (el + offset, "remove", 6))
904        return ESI_ELEMENT_REMOVE;
905
906    if (!strncmp (el + offset, "except", 6))
907        return ESI_ELEMENT_EXCEPT;
908
909    if (!strncmp (el + offset, "choose", 6))
910        return ESI_ELEMENT_CHOOSE;
911
912    if (!strncmp (el + offset, "vars", 4))
913        return ESI_ELEMENT_VARS;
914
915    if (!strncmp (el + offset, "when", 4))
916        return ESI_ELEMENT_WHEN;
917
918    if (!strncmp (el + offset, "try", 3))
919        return ESI_ELEMENT_TRY;
920
921    return ESI_ELEMENT_NONE;
922}
923
924ESIElement::Pointer
925ESIContext::ParserState::top()
926{
927    return stack[stackdepth-1];
928}
929
930ESIContext::ParserState::ParserState() :
931    stackdepth(0),
932    parsing(0),
933    inited_(false)
934{}
935
936bool
937ESIContext::ParserState::inited() const
938{
939    return inited_;
940}
941
942void
943ESIContext::addStackElement (ESIElement::Pointer element)
944{
945    /* Put on the stack to allow skipping of 'invalid' markup */
946    assert (parserState.stackdepth <11);
947    assert (!failed());
948    debugs(86, 5, "ESIContext::addStackElement: About to add ESI Node " << element.getRaw());
949
950    if (!parserState.top()->addElement(element)) {
951        debugs(86, DBG_IMPORTANT, "ESIContext::addStackElement: failed to add esi node, probable error in ESI template");
952        flags.error = 1;
953    } else {
954        /* added ok, push onto the stack */
955        parserState.stack[parserState.stackdepth] = element;
956        ++parserState.stackdepth;
957    }
958}
959
960void
961ESIContext::start(const char *el, const char **attr, size_t attrCount)
962{
963    int i;
964    unsigned int ellen = strlen (el);
965    char localbuf [HTTP_REQBUF_SZ];
966    ESIElement::Pointer element;
967    int specifiedattcount = attrCount * 2;
968    char *position;
969    assert (ellen < sizeof (localbuf)); /* prevent unexpected overruns. */
970
971    debugs(86, 5, "ESIContext::Start: element '" << el << "' with " << specifiedattcount << " tags");
972
973    if (failed())
974        /* waiting for expat to finish the buffer we gave it */
975        return;
976
977    switch (ESIElement::IdentifyElement (el)) {
978
979    case ESIElement::ESI_ELEMENT_NONE:
980        /* Spit out elements we aren't interested in */
981        localbuf[0] = '<';
982        localbuf[1] = '\0';
983        assert (xstrncpy (&localbuf[1], el, sizeof(localbuf) - 2));
984        position = localbuf + strlen (localbuf);
985
986        for (i = 0; i < specifiedattcount && attr[i]; i += 2) {
987            *position = ' ';
988            ++position;
989            /* TODO: handle thisNode gracefully */
990            assert (xstrncpy (position, attr[i], sizeof(localbuf) + (position - localbuf)));
991            position += strlen (position);
992            *position = '=';
993            ++position;
994            *position = '\"';
995            ++position;
996            const char *chPtr = attr[i + 1];
997            char ch;
998            while ((ch = *chPtr++) != '\0') {
999                if (ch == '\"') {
1000                    assert( xstrncpy(position, "&quot;", sizeof(localbuf) + (position-localbuf)) );
1001                    position += 6;
1002                } else {
1003                    *position = ch;
1004                    ++position;
1005                }
1006            }
1007            position += strlen (position);
1008            *position = '\"';
1009            ++position;
1010        }
1011
1012        *position = '>';
1013        ++position;
1014        *position = '\0';
1015
1016        addLiteral (localbuf, position - localbuf);
1017        debugs(86, 5, "esi stack depth " << parserState.stackdepth);
1018        return;
1019        break;
1020
1021    case ESIElement::ESI_ELEMENT_COMMENT:
1022        /* Put on the stack to allow skipping of 'invalid' markup */
1023        element = new esiComment ();
1024        break;
1025
1026    case ESIElement::ESI_ELEMENT_INCLUDE:
1027        /* Put on the stack to allow skipping of 'invalid' markup */
1028        element = new ESIInclude (parserState.top().getRaw(), specifiedattcount, attr, this);
1029        break;
1030
1031    case ESIElement::ESI_ELEMENT_REMOVE:
1032        /* Put on the stack to allow skipping of 'invalid' markup */
1033        element = esiRemoveNew ();
1034        break;
1035
1036    case ESIElement::ESI_ELEMENT_TRY:
1037        /* Put on the stack to allow skipping of 'invalid' markup */
1038        element = new esiTry (parserState.top().getRaw());
1039        break;
1040
1041    case ESIElement::ESI_ELEMENT_ATTEMPT:
1042        /* Put on the stack to allow skipping of 'invalid' markup */
1043        element = new esiAttempt (parserState.top().getRaw());
1044        break;
1045
1046    case ESIElement::ESI_ELEMENT_EXCEPT:
1047        /* Put on the stack to allow skipping of 'invalid' markup */
1048        element = new esiExcept (parserState.top().getRaw());
1049        break;
1050
1051    case ESIElement::ESI_ELEMENT_VARS:
1052        /* Put on the stack to allow skipping of 'invalid' markup */
1053        element = new ESIVar (parserState.top().getRaw());
1054        break;
1055
1056    case ESIElement::ESI_ELEMENT_CHOOSE:
1057        /* Put on the stack to allow skipping of 'invalid' markup */
1058        element = new esiChoose (parserState.top().getRaw());
1059        break;
1060
1061    case ESIElement::ESI_ELEMENT_WHEN:
1062        /* Put on the stack to allow skipping of 'invalid' markup */
1063        element = new esiWhen (parserState.top().getRaw(), specifiedattcount, attr, varState);
1064        break;
1065
1066    case ESIElement::ESI_ELEMENT_OTHERWISE:
1067        /* Put on the stack to allow skipping of 'invalid' markup */
1068        element = new esiOtherwise (parserState.top().getRaw());
1069        break;
1070
1071    case ESIElement::ESI_ELEMENT_ASSIGN:
1072        /* Put on the stack to allow skipping of 'invalid' markup */
1073        element = new ESIAssign (parserState.top().getRaw(), specifiedattcount, attr, this);
1074        break;
1075    }
1076
1077    addStackElement(element);
1078
1079    debugs(86, 5, "esi stack depth " << parserState.stackdepth);
1080
1081}  /* End of start handler */
1082
1083void
1084ESIContext::end(const char *el)
1085{
1086    unsigned int ellen = strlen (el);
1087    char localbuf [HTTP_REQBUF_SZ];
1088    char *position;
1089
1090    if (flags.error)
1091        /* waiting for expat to finish the buffer we gave it */
1092        return;
1093
1094    switch (ESIElement::IdentifyElement (el)) {
1095
1096    case ESIElement::ESI_ELEMENT_NONE:
1097        assert (ellen < sizeof (localbuf)); /* prevent unexpected overruns. */
1098        /* Add elements we aren't interested in */
1099        localbuf[0] = '<';
1100        localbuf[1] = '/';
1101        assert (xstrncpy (&localbuf[2], el, sizeof(localbuf) - 3));
1102        position = localbuf + strlen (localbuf);
1103        *position = '>';
1104        ++position;
1105        *position = '\0';
1106        addLiteral (localbuf, position - localbuf);
1107        break;
1108
1109    case ESIElement::ESI_ELEMENT_COMMENT:
1110
1111    case ESIElement::ESI_ELEMENT_INCLUDE:
1112
1113    case ESIElement::ESI_ELEMENT_REMOVE:
1114
1115    case ESIElement::ESI_ELEMENT_TRY:
1116
1117    case ESIElement::ESI_ELEMENT_ATTEMPT:
1118
1119    case ESIElement::ESI_ELEMENT_EXCEPT:
1120
1121    case ESIElement::ESI_ELEMENT_VARS:
1122
1123    case ESIElement::ESI_ELEMENT_CHOOSE:
1124
1125    case ESIElement::ESI_ELEMENT_WHEN:
1126
1127    case ESIElement::ESI_ELEMENT_OTHERWISE:
1128
1129    case ESIElement::ESI_ELEMENT_ASSIGN:
1130        /* pop of the stack */
1131        parserState.stack[--parserState.stackdepth] = NULL;
1132        break;
1133    }
1134}  /* End of end handler */
1135
1136void
1137ESIContext::parserDefault (const char *s, int len)
1138{
1139    if (failed())
1140        return;
1141
1142    /* handle any skipped data */
1143    addLiteral (s, len);
1144}
1145
1146void
1147ESIContext::parserComment (const char *s)
1148{
1149    if (failed())
1150        return;
1151
1152    if (!strncmp(s, "esi",3)) {
1153        debugs(86, 5, "ESIContext::parserComment: ESI <!-- block encountered");
1154        ESIParser::Pointer tempParser = ESIParser::NewParser (this);
1155
1156        /* wrap the comment in some tags */
1157
1158        if (!tempParser->parse("<div>", 5,0) ||
1159                !tempParser->parse(s + 3, strlen(s) - 3, 0) ||
1160                !tempParser->parse("</div>",6,1)) {
1161            debugs(86, DBG_CRITICAL, "ESIContext::parserComment: Parsing fragment '" << s + 3 << "' failed.");
1162            setError();
1163            char tempstr[1024];
1164            snprintf(tempstr, 1023, "ESIContext::parserComment: Parse error at line %ld:\n%s\n",
1165                     tempParser->lineNumber(),
1166                     tempParser->errorString());
1167            debugs(86, DBG_CRITICAL, "" << tempstr << "");
1168
1169            setErrorMessage(tempstr);
1170        }
1171
1172        debugs(86, 5, "ESIContext::parserComment: ESI <!-- block parsed");
1173        return;
1174    } else {
1175        char localbuf [HTTP_REQBUF_SZ];
1176        unsigned int len;
1177        debugs(86, 5, "ESIContext::parserComment: Regenerating comment block");
1178        len = strlen (s);
1179
1180        if (len > sizeof (localbuf) - 9) {
1181            debugs(86, DBG_CRITICAL, "ESIContext::parserComment: Truncating long comment");
1182            len = sizeof (localbuf) - 9;
1183        }
1184
1185        xstrncpy(localbuf, "<!--", 5);
1186        xstrncpy(localbuf + 4, s, len + 1);
1187        xstrncpy(localbuf + 4 + len, "-->", 4);
1188        addLiteral (localbuf,len + 7);
1189    }
1190}
1191
1192void
1193ESIContext::addLiteral (const char *s, int len)
1194{
1195    /* handle any skipped data */
1196    assert (len);
1197    debugs(86, 5, "literal length is " << len);
1198    /* give a literal to the current element */
1199    assert (parserState.stackdepth <11);
1200    ESIElement::Pointer element (new esiLiteral (this, s, len));
1201
1202    if (!parserState.top()->addElement(element)) {
1203        debugs(86, DBG_IMPORTANT, "ESIContext::addLiteral: failed to add esi node, probable error in ESI template");
1204        flags.error = 1;
1205    }
1206}
1207
1208void
1209ESIContext::ParserState::init(ESIParserClient *userData)
1210{
1211    theParser = ESIParser::NewParser (userData);
1212    inited_ = true;
1213}
1214
1215void
1216ESIContext::parseOneBuffer()
1217{
1218    assert (buffered.getRaw());
1219
1220    debugs (86,9,"ESIContext::parseOneBuffer: " << buffered->len << " bytes");
1221    bool lastBlock = buffered->next.getRaw() == NULL && flags.finishedtemplate ? true : false;
1222
1223    if (! parserState.theParser->parse(buffered->buf, buffered->len, lastBlock)) {
1224        setError();
1225        char tempstr[1024];
1226        snprintf (tempstr, 1023, "esiProcess: Parse error at line %ld:\n%s\n",
1227                  parserState.theParser->lineNumber(),
1228                  parserState.theParser->errorString());
1229        debugs(86, DBG_CRITICAL, "" << tempstr << "");
1230
1231        setErrorMessage(tempstr);
1232
1233        assert (flags.error);
1234
1235        return;
1236    }
1237
1238    if (flags.error) {
1239        setError();
1240        return;
1241    }
1242
1243    ESISegment::Pointer temp = buffered;
1244    buffered = temp->next;
1245}
1246
1247void
1248ESIContext::parse()
1249{
1250    if (!parserState.stackdepth) {
1251        debugs(86, 5, "empty parser stack, inserting the top level node");
1252        assert (tree.getRaw());
1253        parserState.stack[parserState.stackdepth] = tree;
1254        ++parserState.stackdepth;
1255    }
1256
1257    if (rep && !parserState.inited())
1258        parserState.init(this);
1259
1260    /* we have data */
1261    if (buffered.getRaw()) {
1262        parserState.parsing = 1;
1263        /* we don't keep any data around */
1264
1265        PROF_start(esiParsing);
1266
1267        while (buffered.getRaw() && !flags.error)
1268            parseOneBuffer();
1269
1270        PROF_stop(esiParsing);
1271
1272        /* Tel the read code to allocate a new buffer */
1273        incoming = NULL;
1274
1275        parserState.parsing = 0;
1276    }
1277}
1278
1279esiProcessResult_t
1280ESIContext::process ()
1281{
1282    /* parsing:
1283     * read through buffered, skipping plain text, and skipping any
1284     * <...> entry that is not an <esi: entry.
1285     * when it's found, hand an esiLiteral of the preceding data to our current
1286     * context
1287     */
1288
1289    if (parserState.parsing) {
1290        /* in middle of parsing - finish here */
1291        return ESI_PROCESS_PENDING_MAYFAIL;
1292    }
1293
1294    assert (flags.finished == 0);
1295
1296    assert (!flags.error);
1297
1298    if (!hasCachedAST())
1299        parse();
1300    else if (!flags.finishedtemplate)
1301        getCachedAST();
1302
1303    if (flags.error) {
1304        debugs(86, 5, "ESIContext::process: Parsing failed");
1305        finishChildren ();
1306        parserState.popAll();
1307        return ESI_PROCESS_FAILED;
1308    }
1309
1310    if (!flags.finishedtemplate && !incoming.getRaw() && !cachedASTInUse) {
1311        buffered = new ESISegment;
1312        incoming = buffered;
1313    }
1314
1315    if (!flags.finishedtemplate && !cachedASTInUse) {
1316        return ESI_PROCESS_PENDING_MAYFAIL;
1317    }
1318
1319    assert (flags.finishedtemplate || cachedASTInUse);
1320    updateCachedAST();
1321    /* ok, we've done all we can with the data. What can we process now?
1322     */
1323    {
1324        esiProcessResult_t status;
1325        PROF_start(esiProcessing);
1326        processing = true;
1327        status = tree->process(0);
1328        processing = false;
1329
1330        switch (status) {
1331
1332        case ESI_PROCESS_COMPLETE:
1333            debugs(86, 5, "esiProcess: tree Processed OK");
1334            break;
1335
1336        case ESI_PROCESS_PENDING_WONTFAIL:
1337            debugs(86, 5, "esiProcess: tree Processed PENDING OK");
1338            break;
1339
1340        case ESI_PROCESS_PENDING_MAYFAIL:
1341            debugs(86, 5, "esiProcess: tree Processed PENDING UNKNOWN");
1342            break;
1343
1344        case ESI_PROCESS_FAILED:
1345            debugs(86, DBG_CRITICAL, "esiProcess: tree Processed FAILED");
1346            setError();
1347
1348            setErrorMessage("esiProcess: ESI template Processing failed.");
1349
1350            PROF_stop(esiProcessing);
1351
1352            return ESI_PROCESS_FAILED;
1353
1354            break;
1355        }
1356
1357        if (status != ESI_PROCESS_PENDING_MAYFAIL && (flags.finishedtemplate || cachedASTInUse)) {
1358            /* We've read the entire template, and no nodes will
1359             * return failure
1360             */
1361            debugs(86, 5, "esiProcess, request will succeed");
1362            flags.oktosend = 1;
1363        }
1364
1365        if (status == ESI_PROCESS_COMPLETE
1366                && (flags.finishedtemplate || cachedASTInUse)) {
1367            /* we've finished all processing. Render and send. */
1368            debugs(86, 5, "esiProcess, processing complete");
1369            flags.finished = 1;
1370        }
1371
1372        PROF_stop(esiProcessing);
1373        return status; /* because we have no callbacks */
1374    }
1375}
1376
1377void
1378ESIContext::ParserState::freeResources()
1379{
1380    theParser = NULL;
1381    inited_ = false;
1382}
1383
1384void
1385ESIContext::ParserState::popAll()
1386{
1387    while (stackdepth)
1388        stack[--stackdepth] = NULL;
1389}
1390
1391void
1392ESIContext::freeResources ()
1393{
1394    debugs(86, 5, HERE << "Freeing for this=" << this);
1395
1396    HTTPMSGUNLOCK(rep);
1397
1398    finishChildren ();
1399
1400    if (parserState.inited()) {
1401        parserState.freeResources();
1402    }
1403
1404    parserState.popAll();
1405    ESISegmentFreeList (buffered);
1406    ESISegmentFreeList (outbound);
1407    ESISegmentFreeList (outboundtail);
1408    delete varState;
1409    varState=NULL;
1410    /* don't touch incoming, it's a pointer into buffered anyway */
1411}
1412
1413ErrorState *clientBuildError (err_type, Http::StatusCode, char const *, Ip::Address &, HttpRequest *);
1414
1415/* This can ONLY be used before we have sent *any* data to the client */
1416void
1417ESIContext::fail ()
1418{
1419    debugs(86, 5, "ESIContext::fail: this=" << this);
1420    /* check preconditions */
1421    assert (pos == 0);
1422    /* cleanup current state */
1423    freeResources ();
1424    /* Stop altering thisNode request */
1425    flags.oktosend = 1;
1426    flags.finished = 1;
1427    /* don't honour range requests - for errors we send it all */
1428    flags.error = 1;
1429    /* create an error object */
1430    // XXX: with the in-direction on remote IP. does the http->getConn()->clientConnection exist?
1431    ErrorState * err = clientBuildError(errorpage, errorstatus, NULL, http->getConn()->clientConnection->remote, http->request);
1432    err->err_msg = errormessage;
1433    errormessage = NULL;
1434    rep = err->BuildHttpReply();
1435    assert (rep->body.hasContent());
1436    size_t errorprogress = rep->body.contentSize();
1437    /* Tell esiSend where to start sending from */
1438    outbound_offset = 0;
1439    /* copy the membuf from the reply to outbound */
1440
1441    while (errorprogress < (size_t)rep->body.contentSize()) {
1442        appendOutboundData(new ESISegment);
1443        errorprogress += outboundtail->append(rep->body.content() + errorprogress, rep->body.contentSize() - errorprogress);
1444    }
1445
1446    /* the esiCode now thinks that the error is the outbound,
1447     * and all processing has finished. */
1448    /* Send as much as we can */
1449    send ();
1450
1451    /* don't cancel anything. The stream nodes will clean up after
1452     * themselves when the reply is freed - and we don't know what to
1453     * clean anyway.
1454     */
1455}
1456
1457/* Implementation of ESIElements */
1458
1459/* esiComment */
1460esiComment::~esiComment()
1461{
1462    debugs(86, 5, "esiComment::~esiComment " << this);
1463}
1464
1465esiComment::esiComment()
1466{}
1467
1468void
1469esiComment::finish()
1470{}
1471
1472void
1473esiComment::render(ESISegment::Pointer output)
1474{
1475    /* Comments do nothing dude */
1476    debugs(86, 5, "esiCommentRender: Rendering comment " << this << " into " << output.getRaw());
1477}
1478
1479ESIElement::Pointer
1480esiComment::makeCacheable() const
1481{
1482    debugs(86, 5, "esiComment::makeCacheable: returning NULL");
1483    return NULL;
1484}
1485
1486ESIElement::Pointer
1487esiComment::makeUsable(esiTreeParentPtr, ESIVarState &) const
1488{
1489    fatal ("esiComment::Usable: unreachable code!\n");
1490    return NULL;
1491}
1492
1493/* esiLiteral */
1494esiLiteral::~esiLiteral()
1495{
1496    debugs(86, 5, "esiLiteral::~esiLiteral: " << this);
1497    ESISegmentFreeList (buffer);
1498    cbdataReferenceDone (varState);
1499}
1500
1501esiLiteral::esiLiteral(ESISegment::Pointer aSegment)
1502{
1503    buffer = aSegment;
1504    /* we've been handed a complete, processed string */
1505    varState = NULL;
1506    /* Nothing to do */
1507    flags.donevars = 1;
1508}
1509
1510void
1511esiLiteral::finish()
1512{}
1513
1514/* precondition: the buffer chain has at least start + length bytes of data
1515 */
1516esiLiteral::esiLiteral(ESIContext *context, const char *s, int numberOfCharacters)
1517{
1518    assert (s);
1519    flags.donevars = 0;
1520    buffer = new ESISegment;
1521    ESISegment::Pointer local = buffer;
1522    size_t start = 0;
1523    int remainingCharacters = numberOfCharacters;
1524
1525    while (remainingCharacters > 0) {
1526        if (local->len == sizeof (local->buf)) {
1527            local->next = new ESISegment;
1528            local=local->next;
1529        }
1530
1531        size_t len = local->append (&s[start], remainingCharacters);
1532        start += len;
1533        remainingCharacters -= len;
1534    }
1535
1536    varState = cbdataReference(context->varState);
1537}
1538
1539void
1540esiLiteral::render (ESISegment::Pointer output)
1541{
1542    debugs(86, 9, "esiLiteral::render: Rendering " << this);
1543    /* append the entire chain */
1544    assert (output->next.getRaw() == NULL);
1545    output->next = buffer;
1546    buffer = NULL;
1547}
1548
1549esiProcessResult_t
1550esiLiteral::process (int dovars)
1551{
1552    if (flags.donevars)
1553        return ESI_PROCESS_COMPLETE;
1554
1555    if (dovars) {
1556        ESISegment::Pointer temp = buffer;
1557        /* Ensure variable state is clean */
1558
1559        while (temp.getRaw()) {
1560            varState->feedData(temp->buf,temp->len);
1561            temp = temp->next;
1562        }
1563
1564        /* free the pre-processed content */
1565        ESISegmentFreeList (buffer);
1566
1567        buffer = varState->extractList ();
1568    }
1569
1570    flags.donevars = 1;
1571    return ESI_PROCESS_COMPLETE;
1572}
1573
1574esiLiteral::esiLiteral(esiLiteral const &old) : buffer (old.buffer->cloneList()),
1575    varState (NULL)
1576{
1577    flags.donevars = 0;
1578}
1579
1580ESIElement::Pointer
1581esiLiteral::makeCacheable() const
1582{
1583    return new esiLiteral (*this);
1584}
1585
1586ESIElement::Pointer
1587esiLiteral::makeUsable(esiTreeParentPtr , ESIVarState &newVarState) const
1588{
1589    debugs(86, 5, "esiLiteral::makeUsable: Creating usable literal");
1590    esiLiteral * result = new esiLiteral (*this);
1591    result->varState = cbdataReference (&newVarState);
1592    return result;
1593}
1594
1595/* esiRemove */
1596void
1597esiRemoveFree (void *data)
1598{
1599    esiRemove *thisNode = (esiRemove *)data;
1600    debugs(86, 5, "esiRemoveFree " << thisNode);
1601}
1602
1603void *
1604esiRemove::operator new(size_t byteCount)
1605{
1606    assert (byteCount == sizeof (esiRemove));
1607    void *rv;
1608    CBDATA_INIT_TYPE_FREECB(esiRemove, esiRemoveFree);
1609    rv = (void *)cbdataAlloc (esiRemove);
1610    return rv;
1611}
1612
1613void
1614esiRemove::operator delete (void *address)
1615{
1616    cbdataFree (address);
1617}
1618
1619ESIElement *
1620esiRemoveNew ()
1621{
1622    return new esiRemove;
1623}
1624
1625esiRemove::esiRemove()
1626{}
1627
1628void
1629esiRemove::finish()
1630{}
1631
1632void
1633esiRemove::render(ESISegment::Pointer output)
1634{
1635    /* Removes do nothing dude */
1636    debugs(86, 5, "esiRemoveRender: Rendering remove " << this);
1637}
1638
1639/* Accept non-ESI children */
1640bool
1641esiRemove::addElement (ESIElement::Pointer element)
1642{
1643    if (!dynamic_cast<esiLiteral*>(element.getRaw())) {
1644        debugs(86, 5, "esiRemoveAdd: Failed for " << this);
1645        return false;
1646    }
1647
1648    return true;
1649}
1650
1651ESIElement::Pointer
1652esiRemove::makeCacheable() const
1653{
1654    debugs(86, 5, "esiRemove::makeCacheable: Returning NULL");
1655    return NULL;
1656}
1657
1658ESIElement::Pointer
1659esiRemove::makeUsable(esiTreeParentPtr, ESIVarState &) const
1660{
1661    fatal ("esiRemove::Usable: unreachable code!\n");
1662    return NULL;
1663}
1664
1665/* esiTry */
1666esiTry::~esiTry()
1667{
1668    debugs(86, 5, "esiTry::~esiTry " << this);
1669}
1670
1671esiTry::esiTry(esiTreeParentPtr aParent) :
1672    parent(aParent),
1673    exceptbuffer(NULL)
1674{
1675    memset(&flags, 0, sizeof(flags));
1676}
1677
1678void
1679esiTry::render(ESISegment::Pointer output)
1680{
1681    /* Try renders from it's children */
1682    assert (attempt.getRaw());
1683    assert (except.getRaw());
1684    debugs(86, 5, "esiTryRender: Rendering Try " << this);
1685
1686    if (flags.attemptok) {
1687        attempt->render(output);
1688    } else if (flags.exceptok) {
1689        /* prerendered */
1690
1691        if (exceptbuffer.getRaw())
1692            ESISegment::ListTransfer(exceptbuffer, output);
1693        else
1694            except->render(output);
1695    } else
1696        debugs(86, 5, "esiTryRender: Neither except nor attempt succeeded?!?");
1697}
1698
1699/* Accept attempt and except only */
1700bool
1701esiTry::addElement(ESIElement::Pointer element)
1702{
1703    debugs(86, 5, "esiTryAdd: Try " << this << " adding element " <<
1704           element.getRaw());
1705
1706    if (dynamic_cast<esiLiteral*>(element.getRaw())) {
1707        /* Swallow whitespace */
1708        debugs(86, 5, "esiTryAdd: Try " << this << " skipping whitespace " << element.getRaw());
1709        return true;
1710    }
1711
1712    if (dynamic_cast<esiAttempt*>(element.getRaw())) {
1713        if (attempt.getRaw()) {
1714            debugs(86, DBG_IMPORTANT, "esiTryAdd: Failed for " << this << " - try allready has an attempt node (section 3.4)");
1715            return false;
1716        }
1717
1718        attempt = element;
1719        return true;
1720    }
1721
1722    if (dynamic_cast<esiExcept*>(element.getRaw())) {
1723        if (except.getRaw()) {
1724            debugs(86, DBG_IMPORTANT, "esiTryAdd: Failed for " << this << " - try already has an except node (section 3.4)");
1725            return false;
1726        }
1727
1728        except = element;
1729        return true;
1730    }
1731
1732    debugs(86, DBG_IMPORTANT, "esiTryAdd: Failed to add element " << element.getRaw() << " to try " << this << ", incorrect element type (see section 3.4)");
1733    return false;
1734}
1735
1736esiProcessResult_t
1737esiTry::bestAttemptRV() const
1738{
1739    if (flags.attemptfailed)
1740        return ESI_PROCESS_COMPLETE;
1741    else
1742        return ESI_PROCESS_PENDING_MAYFAIL;
1743}
1744
1745esiProcessResult_t
1746esiTry::process (int dovars)
1747{
1748    esiProcessResult_t rv = ESI_PROCESS_PENDING_MAYFAIL;
1749
1750    if (!attempt.getRaw()) {
1751        debugs(86, DBG_CRITICAL, "esiTryProcess: Try has no attempt element - ESI template is invalid (section 3.4)");
1752        return ESI_PROCESS_FAILED;
1753    }
1754
1755    if (!except.getRaw()) {
1756        debugs(86, DBG_CRITICAL, "esiTryProcess: Try has no except element - ESI template is invalid (section 3.4)");
1757        return ESI_PROCESS_FAILED;
1758    }
1759
1760    if (!flags.attemptfailed)
1761        /* Try the attempt branch */
1762        switch ((rv = attempt->process(dovars))) {
1763
1764        case ESI_PROCESS_COMPLETE:
1765            debugs(86, 5, "esiTryProcess: attempt Processed OK");
1766            flags.attemptok = 1;
1767            return ESI_PROCESS_COMPLETE;
1768
1769        case ESI_PROCESS_PENDING_WONTFAIL:
1770            debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
1771            /* We're not done yet, but don't need to test except */
1772            return ESI_PROCESS_PENDING_WONTFAIL;
1773
1774        case ESI_PROCESS_PENDING_MAYFAIL:
1775            debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
1776            break;
1777
1778        case ESI_PROCESS_FAILED:
1779            debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
1780            flags.attemptfailed = 1;
1781            break;
1782        }
1783
1784    /* attempt is either MAYFAIL or FAILED */
1785    if (flags.exceptok)
1786        return bestAttemptRV();
1787
1788    /* query except to see if it has a definite result */
1789    if (!flags.exceptfailed)
1790        /* Try the except branch */
1791        switch (except->process(dovars)) {
1792
1793        case ESI_PROCESS_COMPLETE:
1794            debugs(86, 5, "esiTryProcess: except Processed OK");
1795            flags.exceptok = 1;
1796            return bestAttemptRV();
1797
1798        case ESI_PROCESS_PENDING_WONTFAIL:
1799            debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
1800            /* We're not done yet, but can't fail */
1801            return ESI_PROCESS_PENDING_WONTFAIL;
1802
1803        case ESI_PROCESS_PENDING_MAYFAIL:
1804            debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
1805            /* The except branch fail fail */
1806            return ESI_PROCESS_PENDING_MAYFAIL;
1807
1808        case ESI_PROCESS_FAILED:
1809            debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
1810            flags.exceptfailed = 1;
1811            break;
1812        }
1813
1814    if (flags.exceptfailed && flags.attemptfailed)
1815        return ESI_PROCESS_FAILED;
1816
1817    /* one of attempt or except returned PENDING MAYFAIL */
1818    return ESI_PROCESS_PENDING_MAYFAIL;
1819}
1820
1821void
1822esiTry::notifyParent()
1823{
1824    if (flags.attemptfailed) {
1825        if (flags.exceptok) {
1826            parent->provideData (exceptbuffer, this);
1827            exceptbuffer = NULL;
1828        } else if (flags.exceptfailed || except.getRaw() == NULL) {
1829            parent->fail (this, "esi:try - except claused failed, or no except clause found");
1830        }
1831    }
1832
1833    /* nothing to do when except fails and attempt hasn't */
1834}
1835
1836void
1837esiTry::fail(ESIElement *source, char const *anError)
1838{
1839    assert (source);
1840    assert (source == attempt || source == except);
1841    debugs(86, 5, "esiTry::fail: this=" << this << ", source=" << source << ", message=" << anError);
1842
1843    if (source == except) {
1844        flags.exceptfailed = 1;
1845    } else {
1846        flags.attemptfailed = 1;
1847    }
1848
1849    notifyParent();
1850}
1851
1852void
1853esiTry::provideData (ESISegment::Pointer data, ESIElement* source)
1854{
1855    if (source == attempt) {
1856        flags.attemptok = 1;
1857        parent->provideData (data, this);
1858    } else if (source == except) {
1859        flags.exceptok = 1;
1860        assert (exceptbuffer == NULL);
1861        ESISegment::ListTransfer (data, exceptbuffer);
1862        notifyParent();
1863    }
1864}
1865
1866esiTry::esiTry(esiTry const &old)
1867{
1868    attempt = NULL;
1869    except  = NULL;
1870    flags.attemptok = 0;
1871    flags.exceptok = 0;
1872    flags.attemptfailed = 0;
1873    flags.exceptfailed = 0;
1874    parent = NULL;
1875    exceptbuffer = NULL;
1876}
1877
1878ESIElement::Pointer
1879esiTry::makeCacheable() const
1880{
1881    debugs(86, 5, "esiTry::makeCacheable: making cachable Try from " << this);
1882    esiTry *resultT = new esiTry (*this);
1883    ESIElement::Pointer result = resultT;
1884
1885    if (attempt.getRaw())
1886        resultT->attempt = attempt->makeCacheable();
1887
1888    if (except.getRaw())
1889        resultT->except  = except->makeCacheable();
1890
1891    return result;
1892}
1893
1894ESIElement::Pointer
1895esiTry::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
1896{
1897    debugs(86, 5, "esiTry::makeUsable: making usable Try from " << this);
1898    esiTry *resultT = new esiTry (*this);
1899    ESIElement::Pointer result = resultT;
1900
1901    resultT->parent = newParent;
1902
1903    if (attempt.getRaw())
1904        resultT->attempt = attempt->makeUsable(resultT, newVarState);
1905
1906    if (except.getRaw())
1907        resultT->except  = except->makeUsable(resultT, newVarState);
1908
1909    return result;
1910}
1911
1912void
1913esiTry::finish()
1914{
1915    parent = NULL;
1916
1917    if (attempt.getRaw())
1918        attempt->finish();
1919
1920    attempt = NULL;
1921
1922    if (except.getRaw())
1923        except->finish();
1924
1925    except = NULL;
1926}
1927
1928/* esiAttempt */
1929#if 0
1930void *
1931esiAttempt::operator new(size_t byteCount)
1932{
1933    assert (byteCount == sizeof (esiAttempt));
1934
1935}
1936
1937void
1938esiAttempt::operator delete (void *address)
1939{
1940    cbdataFree (address);
1941}
1942
1943#endif
1944
1945/* esiExcept */
1946#if 0
1947void *
1948esiExcept::operator new(size_t byteCount)
1949{
1950    assert (byteCount == sizeof (esiExcept));
1951    void *rv;
1952    CBDATA_INIT_TYPE_FREECB(esiExcept, esiSequence::Free);
1953    rv = (void *)cbdataAlloc (esiExcept);
1954    return rv;
1955}
1956
1957void
1958esiExcept::operator delete (void *address)
1959{
1960    cbdataFree (address);
1961}
1962
1963#endif
1964
1965/* ESIVar */
1966#if 0
1967void *
1968esiVar::operator new(size_t byteCount)
1969{
1970    assert (byteCount == sizeof (esiVar));
1971    void *rv;
1972    CBDATA_INIT_TYPE_FREECB(esiVar, esiSequence::Free);
1973    rv = (void *)cbdataAlloc (esiVar);
1974    return rv;
1975}
1976
1977void
1978esiVar::operator delete (void *address)
1979{
1980    cbdataFree (address);
1981}
1982
1983#endif
1984
1985/* esiChoose */
1986esiChoose::~esiChoose()
1987{
1988    debugs(86, 5, "esiChoose::~esiChoose " << this);
1989}
1990
1991esiChoose::esiChoose(esiTreeParentPtr aParent) : elements (), chosenelement (-1),parent (aParent)
1992{}
1993
1994void
1995esiChoose::render(ESISegment::Pointer output)
1996{
1997    /* append all processed elements, and trim processed and rendered elements */
1998    assert (output->next == NULL);
1999    assert (elements.size() || otherwise.getRaw());
2000    debugs(86, 5, "esiChooseRender: rendering");
2001
2002    if (chosenelement >= 0)
2003        elements[chosenelement]->render(output);
2004    else if (otherwise.getRaw())
2005        otherwise->render(output);
2006}
2007
2008bool
2009esiChoose::addElement(ESIElement::Pointer element)
2010{
2011    /* add an element to the output list */
2012
2013    if (dynamic_cast<esiLiteral*>(element.getRaw())) {
2014        /* Swallow whitespace */
2015        debugs(86, 5, "esiChooseAdd: Choose " << this << " skipping whitespace " << element.getRaw());
2016        return true;
2017    }
2018
2019    /* Some elements require specific parents */
2020    if (!(dynamic_cast<esiWhen*>(element.getRaw()) || dynamic_cast<esiOtherwise*>(element.getRaw()))) {
2021        debugs(86, DBG_CRITICAL, "esiChooseAdd: invalid child node for esi:choose (section 3.3)");
2022        return false;
2023    }
2024
2025    if (dynamic_cast<esiOtherwise*>(element.getRaw())) {
2026        if (otherwise.getRaw()) {
2027            debugs(86, DBG_CRITICAL, "esiChooseAdd: only one otherwise node allowed for esi:choose (section 3.3)");
2028            return false;
2029        }
2030
2031        otherwise = element;
2032    } else {
2033        elements.push_back (element);
2034
2035        debugs (86,3, "esiChooseAdd: Added a new element, elements = " << elements.size());
2036
2037        if (chosenelement == -1) {
2038            const esiWhen * topElement=dynamic_cast<esiWhen *>(element.getRaw());
2039            if (topElement && topElement->testsTrue()) {
2040                chosenelement = elements.size() - 1;
2041                debugs (86,3, "esiChooseAdd: Chose element " << elements.size());
2042            }
2043        }
2044    }
2045
2046    return true;
2047}
2048
2049void
2050esiChoose::selectElement()
2051{
2052    if (chosenelement > -1)
2053        return;
2054
2055    for (size_t counter = 0; counter < elements.size(); ++counter) {
2056        const esiWhen *el = dynamic_cast<esiWhen *>(elements[counter].getRaw());
2057        if (el && el->testsTrue()) {
2058            chosenelement = counter;
2059            debugs (86,3, "esiChooseAdd: Chose element " << counter + 1);
2060            return;
2061        }
2062    }
2063}
2064
2065void
2066esiChoose::finish()
2067{
2068    elements.setNULL(0, elements.size());
2069
2070    if (otherwise.getRaw())
2071        otherwise->finish();
2072
2073    otherwise = NULL;
2074
2075    parent = NULL;
2076}
2077
2078void
2079ElementList::setNULL (int start, int end)
2080{
2081    assert (start >= 0 && start <= elementcount);
2082    assert (end >= 0 && end <= elementcount);
2083
2084    for (int loopPosition = start; loopPosition < end; ++loopPosition) {
2085        if (elements[loopPosition].getRaw())
2086            elements[loopPosition]->finish();
2087
2088        debugs(86, 5, "esiSequence::NULLElements: Setting index " <<
2089               loopPosition << ", pointer " <<
2090               elements[loopPosition].getRaw() << " to NULL");
2091
2092        elements[loopPosition] = NULL;
2093    }
2094}
2095
2096void
2097esiChoose::NULLUnChosen()
2098{
2099    if (chosenelement >= 0) {
2100        if (otherwise.getRaw())
2101            otherwise->finish();
2102
2103        otherwise = NULL;
2104
2105        elements.setNULL (0, chosenelement);
2106
2107        elements.setNULL (chosenelement + 1, elements.size());
2108    } else if (otherwise.getRaw()) {
2109        elements.setNULL (0, elements.size());
2110    }
2111}
2112
2113esiProcessResult_t
2114esiChoose::process (int dovars)
2115{
2116    /* process as much of the list as we can, stopping only on
2117     * faliures
2118     */
2119    /* We MUST have a when clause */
2120    NULLUnChosen();
2121
2122    if (!elements.size()) {
2123        parent->fail(this);
2124
2125        if (otherwise.getRaw())
2126            otherwise->finish();
2127
2128        otherwise = NULL;
2129
2130        parent = NULL;
2131
2132        return ESI_PROCESS_FAILED;
2133    }
2134
2135    if (chosenelement >= 0) {
2136        return elements[chosenelement]->process(dovars);
2137    } else if (otherwise.getRaw())
2138        return otherwise->process(dovars);
2139    else
2140        return ESI_PROCESS_COMPLETE;
2141}
2142
2143void
2144esiChoose::checkValidSource (ESIElement::Pointer source) const
2145{
2146    if (!elements.size())
2147        fatal ("invalid callback = no when clause\n");
2148
2149    if (chosenelement >= 0)
2150        assert (source == elements[chosenelement]);
2151    else if (otherwise.getRaw())
2152        assert (source == otherwise);
2153    else
2154        fatal ("esiChoose::checkValidSource: invalid callback - no elements chosen\n");
2155}
2156
2157void
2158esiChoose::fail(ESIElement * source, char const *anError)
2159{
2160    checkValidSource (source);
2161    elements.setNULL (0, elements.size());
2162
2163    if (otherwise.getRaw())
2164        otherwise->finish();
2165
2166    otherwise = NULL;
2167
2168    parent->fail(this, anError);
2169
2170    parent = NULL;
2171}
2172
2173void
2174esiChoose::provideData (ESISegment::Pointer data, ESIElement*source)
2175{
2176    checkValidSource (source);
2177    parent->provideData (data, this);
2178}
2179
2180esiChoose::esiChoose(esiChoose const &old) : chosenelement(-1), otherwise (NULL), parent (NULL)
2181{
2182    for (size_t counter = 0; counter < old.elements.size(); ++counter) {
2183        ESIElement::Pointer newElement = old.elements[counter]->makeCacheable();
2184
2185        if (newElement.getRaw())
2186            assert (addElement(newElement));
2187    }
2188}
2189
2190void
2191esiChoose::makeCachableElements(esiChoose const &old)
2192{
2193    for (size_t counter = 0; counter < old.elements.size(); ++counter) {
2194        ESIElement::Pointer newElement = old.elements[counter]->makeCacheable();
2195
2196        if (newElement.getRaw())
2197            assert (addElement(newElement));
2198    }
2199}
2200
2201void
2202esiChoose::makeUsableElements(esiChoose const &old, ESIVarState &newVarState)
2203{
2204    for (size_t counter = 0; counter < old.elements.size(); ++counter) {
2205        ESIElement::Pointer newElement = old.elements[counter]->makeUsable (this, newVarState);
2206
2207        if (newElement.getRaw())
2208            assert (addElement(newElement));
2209    }
2210}
2211
2212ESIElement::Pointer
2213esiChoose::makeCacheable() const
2214{
2215    esiChoose *resultC = new esiChoose (*this);
2216    ESIElement::Pointer result = resultC;
2217    resultC->makeCachableElements(*this);
2218
2219    if (otherwise.getRaw())
2220        resultC->otherwise = otherwise->makeCacheable();
2221
2222    return result;
2223}
2224
2225ESIElement::Pointer
2226esiChoose::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
2227{
2228    esiChoose *resultC = new esiChoose (*this);
2229    ESIElement::Pointer result = resultC;
2230    resultC->parent = newParent;
2231    resultC->makeUsableElements(*this, newVarState);
2232    resultC->selectElement();
2233
2234    if (otherwise.getRaw())
2235        resultC->otherwise = otherwise->makeUsable(resultC, newVarState);
2236
2237    return result;
2238}
2239
2240/* ElementList */
2241ElementList::ElementList () : elements(NULL), allocedcount(0), allocedsize(0), elementcount (0)
2242{}
2243
2244ElementList::~ElementList()
2245{
2246    debugs(86, 5, "ElementList::~ElementList " << this);
2247    setNULL(0, elementcount);
2248
2249    if (elements)
2250        memFreeBuf (allocedsize, elements);
2251}
2252
2253ESIElement::Pointer &
2254ElementList::operator [] (int index)
2255{
2256    return elements[index];
2257}
2258
2259ESIElement::Pointer const &
2260ElementList::operator [] (int index) const
2261{
2262    return elements[index];
2263}
2264
2265void
2266ElementList::pop_front (size_t const count)
2267{
2268    if (!count)
2269        return;
2270
2271    memmove(elements, &elements[count], (elementcount - count)  * sizeof (ESIElement::Pointer));
2272
2273    elementcount -= count;
2274}
2275
2276void
2277ElementList::push_back(ESIElement::Pointer &newElement)
2278{
2279    elements = (ESIElement::Pointer *)memReallocBuf (elements, ++elementcount * sizeof (ESIElement::Pointer),
2280               &allocedsize);
2281    assert (elements);
2282    allocedcount = elementcount;
2283    memset(&elements[elementcount - 1], '\0', sizeof (ESIElement::Pointer));
2284    elements[elementcount - 1] = newElement;
2285}
2286
2287size_t
2288ElementList::size() const
2289{
2290    return elementcount;
2291}
2292
2293/* esiWhen */
2294esiWhen::esiWhen(esiTreeParentPtr aParent, int attrcount, const char **attr,ESIVarState *aVar) :
2295    esiSequence(aParent),
2296    testValue(false),
2297    unevaluatedExpression(NULL),
2298    varState(NULL)
2299{
2300    char const *expression = NULL;
2301
2302    for (int loopCounter = 0; loopCounter < attrcount && attr[loopCounter]; loopCounter += 2) {
2303        if (!strcmp(attr[loopCounter],"test")) {
2304            /* evaluate test */
2305            debugs(86, 5, "esiWhen::esiWhen: Evaluating '" << attr[loopCounter+1] << "'");
2306            /* TODO: warn the user instead of asserting */
2307            assert (expression == NULL);
2308            expression = attr[loopCounter+1];
2309        } else {
2310            /* ignore mistyped attributes.
2311             * TODO:? error on these for user feedback - config parameter needed
2312             */
2313            debugs(86, DBG_IMPORTANT, "Found misttyped attribute on ESI When clause");
2314        }
2315    }
2316
2317    /* No expression ? default is not matching */
2318    if (!expression)
2319        return;
2320
2321    unevaluatedExpression = xstrdup(expression);
2322
2323    varState = cbdataReference (aVar);
2324
2325    evaluate();
2326}
2327
2328esiWhen::~esiWhen()
2329{
2330    safe_free (unevaluatedExpression);
2331
2332    if (varState)
2333        cbdataReferenceDone (varState);
2334}
2335
2336void
2337esiWhen::evaluate()
2338{
2339    if (!unevaluatedExpression)
2340        return;
2341
2342    assert(varState);
2343
2344    varState->feedData(unevaluatedExpression, strlen (unevaluatedExpression));
2345
2346    char const *expression = varState->extractChar ();
2347
2348    setTestResult(ESIExpression::Evaluate (expression));
2349
2350    safe_free (expression);
2351}
2352
2353esiWhen::esiWhen(esiWhen const &old) :
2354    esiSequence(old),
2355    testValue(false),
2356    unevaluatedExpression(NULL),
2357    varState(NULL)
2358{
2359    if (old.unevaluatedExpression)
2360        unevaluatedExpression = xstrdup(old.unevaluatedExpression);
2361}
2362
2363ESIElement::Pointer
2364esiWhen::makeCacheable() const
2365{
2366    return new esiWhen(*this);
2367}
2368
2369ESIElement::Pointer
2370esiWhen::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
2371{
2372    esiWhen *resultW = new esiWhen (*this);
2373    ESIElement::Pointer result = resultW;
2374    resultW->parent = newParent;
2375    resultW->makeUsableElements(*this, newVarState);
2376    resultW->varState = cbdataReference (&newVarState);
2377    resultW->evaluate();
2378    return result;
2379}
2380
2381/* esiOtherwise */
2382#if 0
2383void *
2384esiOtherwise::operator new(size_t byteCount)
2385{
2386    assert (byteCount == sizeof (esiOtherwise));
2387    void *rv;
2388    CBDATA_INIT_TYPE_FREECB(esiOtherwise, esiSequence::Free);
2389    rv = (void *)cbdataAlloc (esiOtherwise);
2390    return rv;
2391}
2392
2393void
2394esiOtherwise::operator delete (void *address)
2395{
2396    cbdataFree (address);
2397}
2398
2399#endif
2400
2401/* TODO: implement surrogate targeting and control processing */
2402int
2403esiEnableProcessing (HttpReply *rep)
2404{
2405    int rv = 0;
2406
2407    if (rep->surrogate_control) {
2408        HttpHdrScTarget *sctusable =
2409            rep->surrogate_control->getMergedTarget(Config.Accel.surrogate_id);
2410
2411        // found something targeted at us
2412        if (sctusable &&
2413                sctusable->hasContent() &&
2414                sctusable->content().pos("ESI/1.0")) {
2415            rv = 1;
2416        }
2417
2418        delete sctusable;
2419    }
2420
2421    return rv;
2422}
2423
2424#endif /* USE_SQUID_ESI == 1 */
2425
Note: See TracBrowser for help on using the repository browser.