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