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