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