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