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