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