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

Last change on this file since 5495 was 5495, checked in by Juanma, 23 months ago

Initial release

File size: 65.5 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 != NULL || (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::Pointer temprep = rep;
622    rep = NULL; /* freed downstream */
623
624    if (temprep != NULL && varState)
625        varState->buildVary(temprep.getRaw());
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.getRaw(), 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    Must(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        xstrncpy(&localbuf[1], el, sizeof(localbuf) - 2);
984        position = localbuf + strlen (localbuf);
985
986        for (i = 0; i < specifiedattcount && attr[i]; i += 2) {
987            Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 1);
988            *position = ' ';
989            ++position;
990            /* TODO: handle thisNode gracefully */
991            xstrncpy(position, attr[i], sizeof(localbuf) - (position - localbuf));
992            position += strlen (position);
993            Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 2);
994            *position = '=';
995            ++position;
996            *position = '\"';
997            ++position;
998            const char *chPtr = attr[i + 1];
999            char ch;
1000            while ((ch = *chPtr++) != '\0') {
1001                if (ch == '\"') {
1002                    Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 6);
1003                    xstrncpy(position, "&quot;", sizeof(localbuf) - (position-localbuf));
1004                    position += 6;
1005                } else {
1006                    Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 1);
1007                    *position = ch;
1008                    ++position;
1009                }
1010            }
1011            Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 1);
1012            *position = '\"';
1013            ++position;
1014        }
1015
1016        Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 2);
1017        *position = '>';
1018        ++position;
1019        *position = '\0';
1020
1021        addLiteral (localbuf, position - localbuf);
1022        debugs(86, 5, "esi stack depth " << parserState.stackdepth);
1023        return;
1024        break;
1025
1026    case ESIElement::ESI_ELEMENT_COMMENT:
1027        /* Put on the stack to allow skipping of 'invalid' markup */
1028        element = new esiComment ();
1029        break;
1030
1031    case ESIElement::ESI_ELEMENT_INCLUDE:
1032        /* Put on the stack to allow skipping of 'invalid' markup */
1033        element = new ESIInclude (parserState.top().getRaw(), specifiedattcount, attr, this);
1034        break;
1035
1036    case ESIElement::ESI_ELEMENT_REMOVE:
1037        /* Put on the stack to allow skipping of 'invalid' markup */
1038        element = esiRemoveNew ();
1039        break;
1040
1041    case ESIElement::ESI_ELEMENT_TRY:
1042        /* Put on the stack to allow skipping of 'invalid' markup */
1043        element = new esiTry (parserState.top().getRaw());
1044        break;
1045
1046    case ESIElement::ESI_ELEMENT_ATTEMPT:
1047        /* Put on the stack to allow skipping of 'invalid' markup */
1048        element = new esiAttempt (parserState.top().getRaw());
1049        break;
1050
1051    case ESIElement::ESI_ELEMENT_EXCEPT:
1052        /* Put on the stack to allow skipping of 'invalid' markup */
1053        element = new esiExcept (parserState.top().getRaw());
1054        break;
1055
1056    case ESIElement::ESI_ELEMENT_VARS:
1057        /* Put on the stack to allow skipping of 'invalid' markup */
1058        element = new ESIVar (parserState.top().getRaw());
1059        break;
1060
1061    case ESIElement::ESI_ELEMENT_CHOOSE:
1062        /* Put on the stack to allow skipping of 'invalid' markup */
1063        element = new esiChoose (parserState.top().getRaw());
1064        break;
1065
1066    case ESIElement::ESI_ELEMENT_WHEN:
1067        /* Put on the stack to allow skipping of 'invalid' markup */
1068        element = new esiWhen (parserState.top().getRaw(), specifiedattcount, attr, varState);
1069        break;
1070
1071    case ESIElement::ESI_ELEMENT_OTHERWISE:
1072        /* Put on the stack to allow skipping of 'invalid' markup */
1073        element = new esiOtherwise (parserState.top().getRaw());
1074        break;
1075
1076    case ESIElement::ESI_ELEMENT_ASSIGN:
1077        /* Put on the stack to allow skipping of 'invalid' markup */
1078        element = new ESIAssign (parserState.top().getRaw(), specifiedattcount, attr, this);
1079        break;
1080    }
1081
1082    addStackElement(element);
1083
1084    debugs(86, 5, "esi stack depth " << parserState.stackdepth);
1085
1086}  /* End of start handler */
1087
1088void
1089ESIContext::end(const char *el)
1090{
1091    unsigned int ellen = strlen (el);
1092    char localbuf [HTTP_REQBUF_SZ];
1093    char *position;
1094
1095    if (flags.error)
1096        /* waiting for expat to finish the buffer we gave it */
1097        return;
1098
1099    switch (ESIElement::IdentifyElement (el)) {
1100
1101    case ESIElement::ESI_ELEMENT_NONE:
1102        Must(ellen < sizeof(localbuf) - 3); /* prevent unexpected overruns. */
1103        /* Add elements we aren't interested in */
1104        localbuf[0] = '<';
1105        localbuf[1] = '/';
1106        xstrncpy(&localbuf[2], el, sizeof(localbuf) - 3);
1107        position = localbuf + strlen (localbuf);
1108        *position = '>';
1109        ++position;
1110        *position = '\0';
1111        addLiteral (localbuf, position - localbuf);
1112        break;
1113
1114    case ESIElement::ESI_ELEMENT_COMMENT:
1115
1116    case ESIElement::ESI_ELEMENT_INCLUDE:
1117
1118    case ESIElement::ESI_ELEMENT_REMOVE:
1119
1120    case ESIElement::ESI_ELEMENT_TRY:
1121
1122    case ESIElement::ESI_ELEMENT_ATTEMPT:
1123
1124    case ESIElement::ESI_ELEMENT_EXCEPT:
1125
1126    case ESIElement::ESI_ELEMENT_VARS:
1127
1128    case ESIElement::ESI_ELEMENT_CHOOSE:
1129
1130    case ESIElement::ESI_ELEMENT_WHEN:
1131
1132    case ESIElement::ESI_ELEMENT_OTHERWISE:
1133
1134    case ESIElement::ESI_ELEMENT_ASSIGN:
1135        /* pop of the stack */
1136        parserState.stack[--parserState.stackdepth] = NULL;
1137        break;
1138    }
1139}  /* End of end handler */
1140
1141void
1142ESIContext::parserDefault (const char *s, int len)
1143{
1144    if (failed())
1145        return;
1146
1147    /* handle any skipped data */
1148    addLiteral (s, len);
1149}
1150
1151void
1152ESIContext::parserComment (const char *s)
1153{
1154    if (failed())
1155        return;
1156
1157    if (!strncmp(s, "esi",3)) {
1158        debugs(86, 5, "ESIContext::parserComment: ESI <!-- block encountered");
1159        ESIParser::Pointer tempParser = ESIParser::NewParser (this);
1160
1161        /* wrap the comment in some tags */
1162
1163        if (!tempParser->parse("<div>", 5,0) ||
1164                !tempParser->parse(s + 3, strlen(s) - 3, 0) ||
1165                !tempParser->parse("</div>",6,1)) {
1166            debugs(86, DBG_CRITICAL, "ESIContext::parserComment: Parsing fragment '" << s + 3 << "' failed.");
1167            setError();
1168            char tempstr[1024];
1169            snprintf(tempstr, 1023, "ESIContext::parserComment: Parse error at line %ld:\n%s\n",
1170                     tempParser->lineNumber(),
1171                     tempParser->errorString());
1172            debugs(86, DBG_CRITICAL, "" << tempstr << "");
1173
1174            setErrorMessage(tempstr);
1175        }
1176
1177        debugs(86, 5, "ESIContext::parserComment: ESI <!-- block parsed");
1178        return;
1179    } else {
1180        char localbuf [HTTP_REQBUF_SZ];
1181        unsigned int len;
1182        debugs(86, 5, "ESIContext::parserComment: Regenerating comment block");
1183        len = strlen (s);
1184
1185        if (len > sizeof (localbuf) - 9) {
1186            debugs(86, DBG_CRITICAL, "ESIContext::parserComment: Truncating long comment");
1187            len = sizeof (localbuf) - 9;
1188        }
1189
1190        xstrncpy(localbuf, "<!--", 5);
1191        xstrncpy(localbuf + 4, s, len + 1);
1192        xstrncpy(localbuf + 4 + len, "-->", 4);
1193        addLiteral (localbuf,len + 7);
1194    }
1195}
1196
1197void
1198ESIContext::addLiteral (const char *s, int len)
1199{
1200    /* handle any skipped data */
1201    assert (len);
1202    debugs(86, 5, "literal length is " << len);
1203    /* give a literal to the current element */
1204    assert (parserState.stackdepth <11);
1205    ESIElement::Pointer element (new esiLiteral (this, s, len));
1206
1207    if (!parserState.top()->addElement(element)) {
1208        debugs(86, DBG_IMPORTANT, "ESIContext::addLiteral: failed to add esi node, probable error in ESI template");
1209        flags.error = 1;
1210    }
1211}
1212
1213void
1214ESIContext::ParserState::init(ESIParserClient *userData)
1215{
1216    theParser = ESIParser::NewParser (userData);
1217    inited_ = true;
1218}
1219
1220void
1221ESIContext::parseOneBuffer()
1222{
1223    assert (buffered.getRaw());
1224
1225    debugs (86,9,"ESIContext::parseOneBuffer: " << buffered->len << " bytes");
1226    bool lastBlock = buffered->next.getRaw() == NULL && flags.finishedtemplate ? true : false;
1227
1228    if (! parserState.theParser->parse(buffered->buf, buffered->len, lastBlock)) {
1229        setError();
1230        char tempstr[1024];
1231        snprintf (tempstr, 1023, "esiProcess: Parse error at line %ld:\n%s\n",
1232                  parserState.theParser->lineNumber(),
1233                  parserState.theParser->errorString());
1234        debugs(86, DBG_CRITICAL, "" << tempstr << "");
1235
1236        setErrorMessage(tempstr);
1237
1238        assert (flags.error);
1239
1240        return;
1241    }
1242
1243    if (flags.error) {
1244        setError();
1245        return;
1246    }
1247
1248    ESISegment::Pointer temp = buffered;
1249    buffered = temp->next;
1250}
1251
1252void
1253ESIContext::parse()
1254{
1255    if (!parserState.stackdepth) {
1256        debugs(86, 5, "empty parser stack, inserting the top level node");
1257        assert (tree.getRaw());
1258        parserState.stack[parserState.stackdepth] = tree;
1259        ++parserState.stackdepth;
1260    }
1261
1262    if (rep != NULL && !parserState.inited())
1263        parserState.init(this);
1264
1265    /* we have data */
1266    if (buffered.getRaw()) {
1267        parserState.parsing = 1;
1268        /* we don't keep any data around */
1269
1270        PROF_start(esiParsing);
1271
1272        while (buffered.getRaw() && !flags.error)
1273            parseOneBuffer();
1274
1275        PROF_stop(esiParsing);
1276
1277        /* Tel the read code to allocate a new buffer */
1278        incoming = NULL;
1279
1280        parserState.parsing = 0;
1281    }
1282}
1283
1284esiProcessResult_t
1285ESIContext::process ()
1286{
1287    /* parsing:
1288     * read through buffered, skipping plain text, and skipping any
1289     * <...> entry that is not an <esi: entry.
1290     * when it's found, hand an esiLiteral of the preceding data to our current
1291     * context
1292     */
1293
1294    if (parserState.parsing) {
1295        /* in middle of parsing - finish here */
1296        return ESI_PROCESS_PENDING_MAYFAIL;
1297    }
1298
1299    assert (flags.finished == 0);
1300
1301    assert (!flags.error);
1302
1303    if (!hasCachedAST())
1304        parse();
1305    else if (!flags.finishedtemplate)
1306        getCachedAST();
1307
1308    if (flags.error) {
1309        debugs(86, 5, "ESIContext::process: Parsing failed");
1310        finishChildren ();
1311        parserState.popAll();
1312        return ESI_PROCESS_FAILED;
1313    }
1314
1315    if (!flags.finishedtemplate && !incoming.getRaw() && !cachedASTInUse) {
1316        buffered = new ESISegment;
1317        incoming = buffered;
1318    }
1319
1320    if (!flags.finishedtemplate && !cachedASTInUse) {
1321        return ESI_PROCESS_PENDING_MAYFAIL;
1322    }
1323
1324    assert (flags.finishedtemplate || cachedASTInUse);
1325    updateCachedAST();
1326    /* ok, we've done all we can with the data. What can we process now?
1327     */
1328    {
1329        esiProcessResult_t status;
1330        PROF_start(esiProcessing);
1331        processing = true;
1332        status = tree->process(0);
1333        processing = false;
1334
1335        switch (status) {
1336
1337        case ESI_PROCESS_COMPLETE:
1338            debugs(86, 5, "esiProcess: tree Processed OK");
1339            break;
1340
1341        case ESI_PROCESS_PENDING_WONTFAIL:
1342            debugs(86, 5, "esiProcess: tree Processed PENDING OK");
1343            break;
1344
1345        case ESI_PROCESS_PENDING_MAYFAIL:
1346            debugs(86, 5, "esiProcess: tree Processed PENDING UNKNOWN");
1347            break;
1348
1349        case ESI_PROCESS_FAILED:
1350            debugs(86, DBG_CRITICAL, "esiProcess: tree Processed FAILED");
1351            setError();
1352
1353            setErrorMessage("esiProcess: ESI template Processing failed.");
1354
1355            PROF_stop(esiProcessing);
1356
1357            return ESI_PROCESS_FAILED;
1358
1359            break;
1360        }
1361
1362        if (status != ESI_PROCESS_PENDING_MAYFAIL && (flags.finishedtemplate || cachedASTInUse)) {
1363            /* We've read the entire template, and no nodes will
1364             * return failure
1365             */
1366            debugs(86, 5, "esiProcess, request will succeed");
1367            flags.oktosend = 1;
1368        }
1369
1370        if (status == ESI_PROCESS_COMPLETE
1371                && (flags.finishedtemplate || cachedASTInUse)) {
1372            /* we've finished all processing. Render and send. */
1373            debugs(86, 5, "esiProcess, processing complete");
1374            flags.finished = 1;
1375        }
1376
1377        PROF_stop(esiProcessing);
1378        return status; /* because we have no callbacks */
1379    }
1380}
1381
1382void
1383ESIContext::ParserState::freeResources()
1384{
1385    theParser = NULL;
1386    inited_ = false;
1387}
1388
1389void
1390ESIContext::ParserState::popAll()
1391{
1392    while (stackdepth)
1393        stack[--stackdepth] = NULL;
1394}
1395
1396void
1397ESIContext::freeResources ()
1398{
1399    debugs(86, 5, HERE << "Freeing for this=" << this);
1400
1401    rep = NULL; // refcounted
1402
1403    finishChildren ();
1404
1405    if (parserState.inited()) {
1406        parserState.freeResources();
1407    }
1408
1409    parserState.popAll();
1410    ESISegmentFreeList (buffered);
1411    ESISegmentFreeList (outbound);
1412    ESISegmentFreeList (outboundtail);
1413    delete varState;
1414    varState=NULL;
1415    /* don't touch incoming, it's a pointer into buffered anyway */
1416}
1417
1418ErrorState *clientBuildError (err_type, Http::StatusCode, char const *, Ip::Address &, HttpRequest *);
1419
1420/* This can ONLY be used before we have sent *any* data to the client */
1421void
1422ESIContext::fail ()
1423{
1424    debugs(86, 5, "ESIContext::fail: this=" << this);
1425    /* check preconditions */
1426    assert (pos == 0);
1427    /* cleanup current state */
1428    freeResources ();
1429    /* Stop altering thisNode request */
1430    flags.oktosend = 1;
1431    flags.finished = 1;
1432    /* don't honour range requests - for errors we send it all */
1433    flags.error = 1;
1434    /* create an error object */
1435    // XXX: with the in-direction on remote IP. does the http->getConn()->clientConnection exist?
1436    ErrorState * err = clientBuildError(errorpage, errorstatus, NULL, http->getConn()->clientConnection->remote, http->request);
1437    err->err_msg = errormessage;
1438    errormessage = NULL;
1439    rep = err->BuildHttpReply();
1440    assert (rep->body.hasContent());
1441    size_t errorprogress = rep->body.contentSize();
1442    /* Tell esiSend where to start sending from */
1443    outbound_offset = 0;
1444    /* copy the membuf from the reply to outbound */
1445
1446    while (errorprogress < (size_t)rep->body.contentSize()) {
1447        appendOutboundData(new ESISegment);
1448        errorprogress += outboundtail->append(rep->body.content() + errorprogress, rep->body.contentSize() - errorprogress);
1449    }
1450
1451    /* the esiCode now thinks that the error is the outbound,
1452     * and all processing has finished. */
1453    /* Send as much as we can */
1454    send ();
1455
1456    /* don't cancel anything. The stream nodes will clean up after
1457     * themselves when the reply is freed - and we don't know what to
1458     * clean anyway.
1459     */
1460}
1461
1462/* Implementation of ESIElements */
1463
1464/* esiComment */
1465esiComment::~esiComment()
1466{
1467    debugs(86, 5, "esiComment::~esiComment " << this);
1468}
1469
1470esiComment::esiComment()
1471{}
1472
1473void
1474esiComment::finish()
1475{}
1476
1477void
1478esiComment::render(ESISegment::Pointer output)
1479{
1480    /* Comments do nothing dude */
1481    debugs(86, 5, "esiCommentRender: Rendering comment " << this << " into " << output.getRaw());
1482}
1483
1484ESIElement::Pointer
1485esiComment::makeCacheable() const
1486{
1487    debugs(86, 5, "esiComment::makeCacheable: returning NULL");
1488    return NULL;
1489}
1490
1491ESIElement::Pointer
1492esiComment::makeUsable(esiTreeParentPtr, ESIVarState &) const
1493{
1494    fatal ("esiComment::Usable: unreachable code!\n");
1495    return NULL;
1496}
1497
1498/* esiLiteral */
1499esiLiteral::~esiLiteral()
1500{
1501    debugs(86, 5, "esiLiteral::~esiLiteral: " << this);
1502    ESISegmentFreeList (buffer);
1503    cbdataReferenceDone (varState);
1504}
1505
1506esiLiteral::esiLiteral(ESISegment::Pointer aSegment)
1507{
1508    buffer = aSegment;
1509    /* we've been handed a complete, processed string */
1510    varState = NULL;
1511    /* Nothing to do */
1512    flags.donevars = 1;
1513}
1514
1515void
1516esiLiteral::finish()
1517{}
1518
1519/* precondition: the buffer chain has at least start + length bytes of data
1520 */
1521esiLiteral::esiLiteral(ESIContext *context, const char *s, int numberOfCharacters)
1522{
1523    assert (s);
1524    flags.donevars = 0;
1525    buffer = new ESISegment;
1526    ESISegment::Pointer local = buffer;
1527    size_t start = 0;
1528    int remainingCharacters = numberOfCharacters;
1529
1530    while (remainingCharacters > 0) {
1531        if (local->len == sizeof (local->buf)) {
1532            local->next = new ESISegment;
1533            local=local->next;
1534        }
1535
1536        size_t len = local->append (&s[start], remainingCharacters);
1537        start += len;
1538        remainingCharacters -= len;
1539    }
1540
1541    varState = cbdataReference(context->varState);
1542}
1543
1544void
1545esiLiteral::render (ESISegment::Pointer output)
1546{
1547    debugs(86, 9, "esiLiteral::render: Rendering " << this);
1548    /* append the entire chain */
1549    assert (output->next.getRaw() == NULL);
1550    output->next = buffer;
1551    buffer = NULL;
1552}
1553
1554esiProcessResult_t
1555esiLiteral::process (int dovars)
1556{
1557    if (flags.donevars)
1558        return ESI_PROCESS_COMPLETE;
1559
1560    if (dovars) {
1561        ESISegment::Pointer temp = buffer;
1562        /* Ensure variable state is clean */
1563
1564        while (temp.getRaw()) {
1565            varState->feedData(temp->buf,temp->len);
1566            temp = temp->next;
1567        }
1568
1569        /* free the pre-processed content */
1570        ESISegmentFreeList (buffer);
1571
1572        buffer = varState->extractList ();
1573    }
1574
1575    flags.donevars = 1;
1576    return ESI_PROCESS_COMPLETE;
1577}
1578
1579esiLiteral::esiLiteral(esiLiteral const &old) : buffer (old.buffer->cloneList()),
1580    varState (NULL)
1581{
1582    flags.donevars = 0;
1583}
1584
1585ESIElement::Pointer
1586esiLiteral::makeCacheable() const
1587{
1588    return new esiLiteral (*this);
1589}
1590
1591ESIElement::Pointer
1592esiLiteral::makeUsable(esiTreeParentPtr , ESIVarState &newVarState) const
1593{
1594    debugs(86, 5, "esiLiteral::makeUsable: Creating usable literal");
1595    esiLiteral * result = new esiLiteral (*this);
1596    result->varState = cbdataReference (&newVarState);
1597    return result;
1598}
1599
1600/* esiRemove */
1601void
1602esiRemoveFree (void *data)
1603{
1604    esiRemove *thisNode = (esiRemove *)data;
1605    debugs(86, 5, "esiRemoveFree " << thisNode);
1606}
1607
1608void *
1609esiRemove::operator new(size_t byteCount)
1610{
1611    assert (byteCount == sizeof (esiRemove));
1612    void *rv;
1613    CBDATA_INIT_TYPE_FREECB(esiRemove, esiRemoveFree);
1614    rv = (void *)cbdataAlloc (esiRemove);
1615    return rv;
1616}
1617
1618void
1619esiRemove::operator delete (void *address)
1620{
1621    cbdataFree (address);
1622}
1623
1624ESIElement *
1625esiRemoveNew ()
1626{
1627    return new esiRemove;
1628}
1629
1630esiRemove::esiRemove()
1631{}
1632
1633void
1634esiRemove::finish()
1635{}
1636
1637void
1638esiRemove::render(ESISegment::Pointer output)
1639{
1640    /* Removes do nothing dude */
1641    debugs(86, 5, "esiRemoveRender: Rendering remove " << this);
1642}
1643
1644/* Accept non-ESI children */
1645bool
1646esiRemove::addElement (ESIElement::Pointer element)
1647{
1648    if (!dynamic_cast<esiLiteral*>(element.getRaw())) {
1649        debugs(86, 5, "esiRemoveAdd: Failed for " << this);
1650        return false;
1651    }
1652
1653    return true;
1654}
1655
1656ESIElement::Pointer
1657esiRemove::makeCacheable() const
1658{
1659    debugs(86, 5, "esiRemove::makeCacheable: Returning NULL");
1660    return NULL;
1661}
1662
1663ESIElement::Pointer
1664esiRemove::makeUsable(esiTreeParentPtr, ESIVarState &) const
1665{
1666    fatal ("esiRemove::Usable: unreachable code!\n");
1667    return NULL;
1668}
1669
1670/* esiTry */
1671esiTry::~esiTry()
1672{
1673    debugs(86, 5, "esiTry::~esiTry " << this);
1674}
1675
1676esiTry::esiTry(esiTreeParentPtr aParent) :
1677    parent(aParent),
1678    exceptbuffer(NULL)
1679{
1680    memset(&flags, 0, sizeof(flags));
1681}
1682
1683void
1684esiTry::render(ESISegment::Pointer output)
1685{
1686    /* Try renders from it's children */
1687    assert (attempt.getRaw());
1688    assert (except.getRaw());
1689    debugs(86, 5, "esiTryRender: Rendering Try " << this);
1690
1691    if (flags.attemptok) {
1692        attempt->render(output);
1693    } else if (flags.exceptok) {
1694        /* prerendered */
1695
1696        if (exceptbuffer.getRaw())
1697            ESISegment::ListTransfer(exceptbuffer, output);
1698        else
1699            except->render(output);
1700    } else
1701        debugs(86, 5, "esiTryRender: Neither except nor attempt succeeded?!?");
1702}
1703
1704/* Accept attempt and except only */
1705bool
1706esiTry::addElement(ESIElement::Pointer element)
1707{
1708    debugs(86, 5, "esiTryAdd: Try " << this << " adding element " <<
1709           element.getRaw());
1710
1711    if (dynamic_cast<esiLiteral*>(element.getRaw())) {
1712        /* Swallow whitespace */
1713        debugs(86, 5, "esiTryAdd: Try " << this << " skipping whitespace " << element.getRaw());
1714        return true;
1715    }
1716
1717    if (dynamic_cast<esiAttempt*>(element.getRaw())) {
1718        if (attempt.getRaw()) {
1719            debugs(86, DBG_IMPORTANT, "esiTryAdd: Failed for " << this << " - try allready has an attempt node (section 3.4)");
1720            return false;
1721        }
1722
1723        attempt = element;
1724        return true;
1725    }
1726
1727    if (dynamic_cast<esiExcept*>(element.getRaw())) {
1728        if (except.getRaw()) {
1729            debugs(86, DBG_IMPORTANT, "esiTryAdd: Failed for " << this << " - try already has an except node (section 3.4)");
1730            return false;
1731        }
1732
1733        except = element;
1734        return true;
1735    }
1736
1737    debugs(86, DBG_IMPORTANT, "esiTryAdd: Failed to add element " << element.getRaw() << " to try " << this << ", incorrect element type (see section 3.4)");
1738    return false;
1739}
1740
1741esiProcessResult_t
1742esiTry::bestAttemptRV() const
1743{
1744    if (flags.attemptfailed)
1745        return ESI_PROCESS_COMPLETE;
1746    else
1747        return ESI_PROCESS_PENDING_MAYFAIL;
1748}
1749
1750esiProcessResult_t
1751esiTry::process (int dovars)
1752{
1753    esiProcessResult_t rv = ESI_PROCESS_PENDING_MAYFAIL;
1754
1755    if (!attempt.getRaw()) {
1756        debugs(86, DBG_CRITICAL, "esiTryProcess: Try has no attempt element - ESI template is invalid (section 3.4)");
1757        return ESI_PROCESS_FAILED;
1758    }
1759
1760    if (!except.getRaw()) {
1761        debugs(86, DBG_CRITICAL, "esiTryProcess: Try has no except element - ESI template is invalid (section 3.4)");
1762        return ESI_PROCESS_FAILED;
1763    }
1764
1765    if (!flags.attemptfailed)
1766        /* Try the attempt branch */
1767        switch ((rv = attempt->process(dovars))) {
1768
1769        case ESI_PROCESS_COMPLETE:
1770            debugs(86, 5, "esiTryProcess: attempt Processed OK");
1771            flags.attemptok = 1;
1772            return ESI_PROCESS_COMPLETE;
1773
1774        case ESI_PROCESS_PENDING_WONTFAIL:
1775            debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
1776            /* We're not done yet, but don't need to test except */
1777            return ESI_PROCESS_PENDING_WONTFAIL;
1778
1779        case ESI_PROCESS_PENDING_MAYFAIL:
1780            debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
1781            break;
1782
1783        case ESI_PROCESS_FAILED:
1784            debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
1785            flags.attemptfailed = 1;
1786            break;
1787        }
1788
1789    /* attempt is either MAYFAIL or FAILED */
1790    if (flags.exceptok)
1791        return bestAttemptRV();
1792
1793    /* query except to see if it has a definite result */
1794    if (!flags.exceptfailed)
1795        /* Try the except branch */
1796        switch (except->process(dovars)) {
1797
1798        case ESI_PROCESS_COMPLETE:
1799            debugs(86, 5, "esiTryProcess: except Processed OK");
1800            flags.exceptok = 1;
1801            return bestAttemptRV();
1802
1803        case ESI_PROCESS_PENDING_WONTFAIL:
1804            debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
1805            /* We're not done yet, but can't fail */
1806            return ESI_PROCESS_PENDING_WONTFAIL;
1807
1808        case ESI_PROCESS_PENDING_MAYFAIL:
1809            debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
1810            /* The except branch fail fail */
1811            return ESI_PROCESS_PENDING_MAYFAIL;
1812
1813        case ESI_PROCESS_FAILED:
1814            debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
1815            flags.exceptfailed = 1;
1816            break;
1817        }
1818
1819    if (flags.exceptfailed && flags.attemptfailed)
1820        return ESI_PROCESS_FAILED;
1821
1822    /* one of attempt or except returned PENDING MAYFAIL */
1823    return ESI_PROCESS_PENDING_MAYFAIL;
1824}
1825
1826void
1827esiTry::notifyParent()
1828{
1829    if (flags.attemptfailed) {
1830        if (flags.exceptok) {
1831            parent->provideData (exceptbuffer, this);
1832            exceptbuffer = NULL;
1833        } else if (flags.exceptfailed || except.getRaw() == NULL) {
1834            parent->fail (this, "esi:try - except claused failed, or no except clause found");
1835        }
1836    }
1837
1838    /* nothing to do when except fails and attempt hasn't */
1839}
1840
1841void
1842esiTry::fail(ESIElement *source, char const *anError)
1843{
1844    assert (source);
1845    assert (source == attempt || source == except);
1846    debugs(86, 5, "esiTry::fail: this=" << this << ", source=" << source << ", message=" << anError);
1847
1848    if (source == except) {
1849        flags.exceptfailed = 1;
1850    } else {
1851        flags.attemptfailed = 1;
1852    }
1853
1854    notifyParent();
1855}
1856
1857void
1858esiTry::provideData (ESISegment::Pointer data, ESIElement* source)
1859{
1860    if (source == attempt) {
1861        flags.attemptok = 1;
1862        parent->provideData (data, this);
1863    } else if (source == except) {
1864        flags.exceptok = 1;
1865        assert (exceptbuffer == NULL);
1866        ESISegment::ListTransfer (data, exceptbuffer);
1867        notifyParent();
1868    }
1869}
1870
1871esiTry::esiTry(esiTry const &old)
1872{
1873    attempt = NULL;
1874    except  = NULL;
1875    flags.attemptok = 0;
1876    flags.exceptok = 0;
1877    flags.attemptfailed = 0;
1878    flags.exceptfailed = 0;
1879    parent = NULL;
1880    exceptbuffer = NULL;
1881}
1882
1883ESIElement::Pointer
1884esiTry::makeCacheable() const
1885{
1886    debugs(86, 5, "esiTry::makeCacheable: making cachable Try from " << this);
1887    esiTry *resultT = new esiTry (*this);
1888    ESIElement::Pointer result = resultT;
1889
1890    if (attempt.getRaw())
1891        resultT->attempt = attempt->makeCacheable();
1892
1893    if (except.getRaw())
1894        resultT->except  = except->makeCacheable();
1895
1896    return result;
1897}
1898
1899ESIElement::Pointer
1900esiTry::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
1901{
1902    debugs(86, 5, "esiTry::makeUsable: making usable Try from " << this);
1903    esiTry *resultT = new esiTry (*this);
1904    ESIElement::Pointer result = resultT;
1905
1906    resultT->parent = newParent;
1907
1908    if (attempt.getRaw())
1909        resultT->attempt = attempt->makeUsable(resultT, newVarState);
1910
1911    if (except.getRaw())
1912        resultT->except  = except->makeUsable(resultT, newVarState);
1913
1914    return result;
1915}
1916
1917void
1918esiTry::finish()
1919{
1920    parent = NULL;
1921
1922    if (attempt.getRaw())
1923        attempt->finish();
1924
1925    attempt = NULL;
1926
1927    if (except.getRaw())
1928        except->finish();
1929
1930    except = NULL;
1931}
1932
1933/* esiAttempt */
1934#if 0
1935void *
1936esiAttempt::operator new(size_t byteCount)
1937{
1938    assert (byteCount == sizeof (esiAttempt));
1939
1940}
1941
1942void
1943esiAttempt::operator delete (void *address)
1944{
1945    cbdataFree (address);
1946}
1947
1948#endif
1949
1950/* esiExcept */
1951#if 0
1952void *
1953esiExcept::operator new(size_t byteCount)
1954{
1955    assert (byteCount == sizeof (esiExcept));
1956    void *rv;
1957    CBDATA_INIT_TYPE_FREECB(esiExcept, esiSequence::Free);
1958    rv = (void *)cbdataAlloc (esiExcept);
1959    return rv;
1960}
1961
1962void
1963esiExcept::operator delete (void *address)
1964{
1965    cbdataFree (address);
1966}
1967
1968#endif
1969
1970/* ESIVar */
1971#if 0
1972void *
1973esiVar::operator new(size_t byteCount)
1974{
1975    assert (byteCount == sizeof (esiVar));
1976    void *rv;
1977    CBDATA_INIT_TYPE_FREECB(esiVar, esiSequence::Free);
1978    rv = (void *)cbdataAlloc (esiVar);
1979    return rv;
1980}
1981
1982void
1983esiVar::operator delete (void *address)
1984{
1985    cbdataFree (address);
1986}
1987
1988#endif
1989
1990/* esiChoose */
1991esiChoose::~esiChoose()
1992{
1993    debugs(86, 5, "esiChoose::~esiChoose " << this);
1994}
1995
1996esiChoose::esiChoose(esiTreeParentPtr aParent) : elements (), chosenelement (-1),parent (aParent)
1997{}
1998
1999void
2000esiChoose::render(ESISegment::Pointer output)
2001{
2002    /* append all processed elements, and trim processed and rendered elements */
2003    assert (output->next == NULL);
2004    assert (elements.size() || otherwise.getRaw());
2005    debugs(86, 5, "esiChooseRender: rendering");
2006
2007    if (chosenelement >= 0)
2008        elements[chosenelement]->render(output);
2009    else if (otherwise.getRaw())
2010        otherwise->render(output);
2011}
2012
2013bool
2014esiChoose::addElement(ESIElement::Pointer element)
2015{
2016    /* add an element to the output list */
2017
2018    if (dynamic_cast<esiLiteral*>(element.getRaw())) {
2019        /* Swallow whitespace */
2020        debugs(86, 5, "esiChooseAdd: Choose " << this << " skipping whitespace " << element.getRaw());
2021        return true;
2022    }
2023
2024    /* Some elements require specific parents */
2025    if (!(dynamic_cast<esiWhen*>(element.getRaw()) || dynamic_cast<esiOtherwise*>(element.getRaw()))) {
2026        debugs(86, DBG_CRITICAL, "esiChooseAdd: invalid child node for esi:choose (section 3.3)");
2027        return false;
2028    }
2029
2030    if (dynamic_cast<esiOtherwise*>(element.getRaw())) {
2031        if (otherwise.getRaw()) {
2032            debugs(86, DBG_CRITICAL, "esiChooseAdd: only one otherwise node allowed for esi:choose (section 3.3)");
2033            return false;
2034        }
2035
2036        otherwise = element;
2037    } else {
2038        elements.push_back (element);
2039
2040        debugs (86,3, "esiChooseAdd: Added a new element, elements = " << elements.size());
2041
2042        if (chosenelement == -1) {
2043            const esiWhen * topElement=dynamic_cast<esiWhen *>(element.getRaw());
2044            if (topElement && topElement->testsTrue()) {
2045                chosenelement = elements.size() - 1;
2046                debugs (86,3, "esiChooseAdd: Chose element " << elements.size());
2047            }
2048        }
2049    }
2050
2051    return true;
2052}
2053
2054void
2055esiChoose::selectElement()
2056{
2057    if (chosenelement > -1)
2058        return;
2059
2060    for (size_t counter = 0; counter < elements.size(); ++counter) {
2061        const esiWhen *el = dynamic_cast<esiWhen *>(elements[counter].getRaw());
2062        if (el && el->testsTrue()) {
2063            chosenelement = counter;
2064            debugs (86,3, "esiChooseAdd: Chose element " << counter + 1);
2065            return;
2066        }
2067    }
2068}
2069
2070void
2071esiChoose::finish()
2072{
2073    elements.setNULL(0, elements.size());
2074
2075    if (otherwise.getRaw())
2076        otherwise->finish();
2077
2078    otherwise = NULL;
2079
2080    parent = NULL;
2081}
2082
2083void
2084ElementList::setNULL (int start, int end)
2085{
2086    assert (start >= 0 && start <= elementcount);
2087    assert (end >= 0 && end <= elementcount);
2088
2089    for (int loopPosition = start; loopPosition < end; ++loopPosition) {
2090        if (elements[loopPosition].getRaw())
2091            elements[loopPosition]->finish();
2092
2093        debugs(86, 5, "esiSequence::NULLElements: Setting index " <<
2094               loopPosition << ", pointer " <<
2095               elements[loopPosition].getRaw() << " to NULL");
2096
2097        elements[loopPosition] = NULL;
2098    }
2099}
2100
2101void
2102esiChoose::NULLUnChosen()
2103{
2104    if (chosenelement >= 0) {
2105        if (otherwise.getRaw())
2106            otherwise->finish();
2107
2108        otherwise = NULL;
2109
2110        elements.setNULL (0, chosenelement);
2111
2112        elements.setNULL (chosenelement + 1, elements.size());
2113    } else if (otherwise.getRaw()) {
2114        elements.setNULL (0, elements.size());
2115    }
2116}
2117
2118esiProcessResult_t
2119esiChoose::process (int dovars)
2120{
2121    /* process as much of the list as we can, stopping only on
2122     * faliures
2123     */
2124    /* We MUST have a when clause */
2125    NULLUnChosen();
2126
2127    if (!elements.size()) {
2128        parent->fail(this);
2129
2130        if (otherwise.getRaw())
2131            otherwise->finish();
2132
2133        otherwise = NULL;
2134
2135        parent = NULL;
2136
2137        return ESI_PROCESS_FAILED;
2138    }
2139
2140    if (chosenelement >= 0) {
2141        return elements[chosenelement]->process(dovars);
2142    } else if (otherwise.getRaw())
2143        return otherwise->process(dovars);
2144    else
2145        return ESI_PROCESS_COMPLETE;
2146}
2147
2148void
2149esiChoose::checkValidSource (ESIElement::Pointer source) const
2150{
2151    if (!elements.size())
2152        fatal ("invalid callback = no when clause\n");
2153
2154    if (chosenelement >= 0)
2155        assert (source == elements[chosenelement]);
2156    else if (otherwise.getRaw())
2157        assert (source == otherwise);
2158    else
2159        fatal ("esiChoose::checkValidSource: invalid callback - no elements chosen\n");
2160}
2161
2162void
2163esiChoose::fail(ESIElement * source, char const *anError)
2164{
2165    checkValidSource (source);
2166    elements.setNULL (0, elements.size());
2167
2168    if (otherwise.getRaw())
2169        otherwise->finish();
2170
2171    otherwise = NULL;
2172
2173    parent->fail(this, anError);
2174
2175    parent = NULL;
2176}
2177
2178void
2179esiChoose::provideData (ESISegment::Pointer data, ESIElement*source)
2180{
2181    checkValidSource (source);
2182    parent->provideData (data, this);
2183}
2184
2185esiChoose::esiChoose(esiChoose const &old) : chosenelement(-1), otherwise (NULL), parent (NULL)
2186{
2187    for (size_t counter = 0; counter < old.elements.size(); ++counter) {
2188        ESIElement::Pointer newElement = old.elements[counter]->makeCacheable();
2189
2190        if (newElement.getRaw())
2191            assert (addElement(newElement));
2192    }
2193}
2194
2195void
2196esiChoose::makeCachableElements(esiChoose const &old)
2197{
2198    for (size_t counter = 0; counter < old.elements.size(); ++counter) {
2199        ESIElement::Pointer newElement = old.elements[counter]->makeCacheable();
2200
2201        if (newElement.getRaw())
2202            assert (addElement(newElement));
2203    }
2204}
2205
2206void
2207esiChoose::makeUsableElements(esiChoose const &old, ESIVarState &newVarState)
2208{
2209    for (size_t counter = 0; counter < old.elements.size(); ++counter) {
2210        ESIElement::Pointer newElement = old.elements[counter]->makeUsable (this, newVarState);
2211
2212        if (newElement.getRaw())
2213            assert (addElement(newElement));
2214    }
2215}
2216
2217ESIElement::Pointer
2218esiChoose::makeCacheable() const
2219{
2220    esiChoose *resultC = new esiChoose (*this);
2221    ESIElement::Pointer result = resultC;
2222    resultC->makeCachableElements(*this);
2223
2224    if (otherwise.getRaw())
2225        resultC->otherwise = otherwise->makeCacheable();
2226
2227    return result;
2228}
2229
2230ESIElement::Pointer
2231esiChoose::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
2232{
2233    esiChoose *resultC = new esiChoose (*this);
2234    ESIElement::Pointer result = resultC;
2235    resultC->parent = newParent;
2236    resultC->makeUsableElements(*this, newVarState);
2237    resultC->selectElement();
2238
2239    if (otherwise.getRaw())
2240        resultC->otherwise = otherwise->makeUsable(resultC, newVarState);
2241
2242    return result;
2243}
2244
2245/* ElementList */
2246ElementList::ElementList () : elements(NULL), allocedcount(0), allocedsize(0), elementcount (0)
2247{}
2248
2249ElementList::~ElementList()
2250{
2251    debugs(86, 5, "ElementList::~ElementList " << this);
2252    setNULL(0, elementcount);
2253
2254    if (elements)
2255        memFreeBuf (allocedsize, elements);
2256}
2257
2258ESIElement::Pointer &
2259ElementList::operator [] (int index)
2260{
2261    return elements[index];
2262}
2263
2264ESIElement::Pointer const &
2265ElementList::operator [] (int index) const
2266{
2267    return elements[index];
2268}
2269
2270void
2271ElementList::pop_front (size_t const count)
2272{
2273    if (!count)
2274        return;
2275
2276    memmove(elements, &elements[count], (elementcount - count)  * sizeof (ESIElement::Pointer));
2277
2278    elementcount -= count;
2279}
2280
2281void
2282ElementList::push_back(ESIElement::Pointer &newElement)
2283{
2284    elements = (ESIElement::Pointer *)memReallocBuf (elements, ++elementcount * sizeof (ESIElement::Pointer),
2285               &allocedsize);
2286    assert (elements);
2287    allocedcount = elementcount;
2288    memset(&elements[elementcount - 1], '\0', sizeof (ESIElement::Pointer));
2289    elements[elementcount - 1] = newElement;
2290}
2291
2292size_t
2293ElementList::size() const
2294{
2295    return elementcount;
2296}
2297
2298/* esiWhen */
2299esiWhen::esiWhen(esiTreeParentPtr aParent, int attrcount, const char **attr,ESIVarState *aVar) :
2300    esiSequence(aParent),
2301    testValue(false),
2302    unevaluatedExpression(NULL),
2303    varState(NULL)
2304{
2305    char const *expression = NULL;
2306
2307    for (int loopCounter = 0; loopCounter < attrcount && attr[loopCounter]; loopCounter += 2) {
2308        if (!strcmp(attr[loopCounter],"test")) {
2309            /* evaluate test */
2310            debugs(86, 5, "esiWhen::esiWhen: Evaluating '" << attr[loopCounter+1] << "'");
2311            /* TODO: warn the user instead of asserting */
2312            assert (expression == NULL);
2313            expression = attr[loopCounter+1];
2314        } else {
2315            /* ignore mistyped attributes.
2316             * TODO:? error on these for user feedback - config parameter needed
2317             */
2318            debugs(86, DBG_IMPORTANT, "Found misttyped attribute on ESI When clause");
2319        }
2320    }
2321
2322    /* No expression ? default is not matching */
2323    if (!expression)
2324        return;
2325
2326    unevaluatedExpression = xstrdup(expression);
2327
2328    varState = cbdataReference (aVar);
2329
2330    evaluate();
2331}
2332
2333esiWhen::~esiWhen()
2334{
2335    safe_free (unevaluatedExpression);
2336
2337    if (varState)
2338        cbdataReferenceDone (varState);
2339}
2340
2341void
2342esiWhen::evaluate()
2343{
2344    if (!unevaluatedExpression)
2345        return;
2346
2347    assert(varState);
2348
2349    varState->feedData(unevaluatedExpression, strlen (unevaluatedExpression));
2350
2351    char const *expression = varState->extractChar ();
2352
2353    setTestResult(ESIExpression::Evaluate (expression));
2354
2355    safe_free (expression);
2356}
2357
2358esiWhen::esiWhen(esiWhen const &old) :
2359    esiSequence(old),
2360    testValue(false),
2361    unevaluatedExpression(NULL),
2362    varState(NULL)
2363{
2364    if (old.unevaluatedExpression)
2365        unevaluatedExpression = xstrdup(old.unevaluatedExpression);
2366}
2367
2368ESIElement::Pointer
2369esiWhen::makeCacheable() const
2370{
2371    return new esiWhen(*this);
2372}
2373
2374ESIElement::Pointer
2375esiWhen::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
2376{
2377    esiWhen *resultW = new esiWhen (*this);
2378    ESIElement::Pointer result = resultW;
2379    resultW->parent = newParent;
2380    resultW->makeUsableElements(*this, newVarState);
2381    resultW->varState = cbdataReference (&newVarState);
2382    resultW->evaluate();
2383    return result;
2384}
2385
2386/* esiOtherwise */
2387#if 0
2388void *
2389esiOtherwise::operator new(size_t byteCount)
2390{
2391    assert (byteCount == sizeof (esiOtherwise));
2392    void *rv;
2393    CBDATA_INIT_TYPE_FREECB(esiOtherwise, esiSequence::Free);
2394    rv = (void *)cbdataAlloc (esiOtherwise);
2395    return rv;
2396}
2397
2398void
2399esiOtherwise::operator delete (void *address)
2400{
2401    cbdataFree (address);
2402}
2403
2404#endif
2405
2406/* TODO: implement surrogate targeting and control processing */
2407int
2408esiEnableProcessing (HttpReply *rep)
2409{
2410    int rv = 0;
2411
2412    if (rep->surrogate_control) {
2413        HttpHdrScTarget *sctusable =
2414            rep->surrogate_control->getMergedTarget(Config.Accel.surrogate_id);
2415
2416        // found something targeted at us
2417        if (sctusable &&
2418                sctusable->hasContent() &&
2419                sctusable->content().pos("ESI/1.0")) {
2420            rv = 1;
2421        }
2422
2423        delete sctusable;
2424    }
2425
2426    return rv;
2427}
2428
2429#endif /* USE_SQUID_ESI == 1 */
2430
Note: See TracBrowser for help on using the repository browser.