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