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