]> git.ipfire.org Git - thirdparty/squid.git/blame - src/esi/Esi.cc
Docs: Copyright updates for 2018 (#114)
[thirdparty/squid.git] / src / esi / Esi.cc
CommitLineData
43ae1d95 1/*
5b74111a 2 * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
43ae1d95 3 *
bbc27441
AJ
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.
43ae1d95 7 */
8
bbc27441
AJ
9/* DEBUG: section 86 ESI processing */
10
582c2af2 11#include "squid.h"
454e8283 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
582c2af2 18#include "client_side.h"
602d9612 19#include "client_side_request.h"
582c2af2 20#include "clientStream.h"
1b76e6c1 21#include "comm/Connection.h"
aa839030 22#include "errorpage.h"
582c2af2
FC
23#include "esi/Assign.h"
24#include "esi/Attempt.h"
f99c2cfe 25#include "esi/Context.h"
582c2af2
FC
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"
8c41d298 32#include "fatal.h"
d3dddfb5 33#include "http/Stream.h"
b19dd748 34#include "HttpHdrSc.h"
35#include "HttpHdrScTarget.h"
43ae1d95 36#include "HttpReply.h"
924f73bc 37#include "HttpRequest.h"
96d89ea0 38#include "ip/Address.h"
582c2af2
FC
39#include "MemBuf.h"
40#include "profiler/Profiler.h"
4d5904f7 41#include "SquidConfig.h"
43ae1d95 42
43/* quick reference on behaviour here.
26ac0430
AJ
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
43ae1d95 47 * satisfy the request. At that point we start streaming the queued
48 * data downstream.
49 *
50 */
51
0655fa4d 52class ESIStreamContext;
53
43ae1d95 54/* TODO: split this out into separate files ? */
55/* Parsing: quick and dirty. ESI files are not valid XML, so a generic
26ac0430 56 * XML parser is not much use. Also we need a push parser not a pull
43ae1d95 57 * parser, so LibXML is out.
58 *
26ac0430 59 * Interpreter methods:
43ae1d95 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 */
76bool operator == (ESIElement const *lhs, ESIElement::Pointer const &rhs)
77{
78 return lhs == rhs.getRaw();
79}
80
43ae1d95 81typedef ESIContext::esiKick_t esiKick_t;
82
43ae1d95 83/* some core operators */
84
741c2986
AJ
85class esiComment : public ESIElement
86{
b001e822 87 MEMPROXY_CLASS(esiComment);
741c2986
AJ
88
89public:
43ae1d95 90 ~esiComment();
91 esiComment();
92 Pointer makeCacheable() const;
924f73bc 93 Pointer makeUsable(esiTreeParentPtr, ESIVarState &) const;
43ae1d95 94
95 void render(ESISegment::Pointer);
96 void finish();
43ae1d95 97};
98
f99c2cfe 99#include "esi/Literal.h"
43ae1d95 100
f99c2cfe 101#include "esi/Sequence.h"
43ae1d95 102
f99c2cfe 103#include "esi/Include.h"
43ae1d95 104
105/* esiRemove */
106
107class esiRemove : public ESIElement
108{
8c41d298 109 MEMPROXY_CLASS(esiRemove);
43ae1d95 110
111public:
8c41d298
AJ
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() {}
43ae1d95 120};
121
741c2986
AJ
122class esiTry : public ESIElement
123{
b001e822 124 MEMPROXY_CLASS(esiTry);
43ae1d95 125
741c2986 126public:
43ae1d95 127 esiTry(esiTreeParentPtr aParent);
128 ~esiTry();
129
130 void render(ESISegment::Pointer);
131 bool addElement (ESIElement::Pointer);
924f73bc 132 void fail(ESIElement *, char const * = NULL);
43ae1d95 133 esiProcessResult_t process (int dovars);
134 void provideData (ESISegment::Pointer data, ESIElement * source);
135 Pointer makeCacheable() const;
924f73bc 136 Pointer makeUsable(esiTreeParentPtr, ESIVarState &) const;
43ae1d95 137
138 ESIElement::Pointer attempt;
139 ESIElement::Pointer except;
140
26ac0430 141 struct {
3d0ac046
HN
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 */
2fadd50d 146 } flags;
43ae1d95 147 void finish();
148
149private:
43ae1d95 150 void notifyParent();
151 esiTreeParentPtr parent;
152 ESISegment::Pointer exceptbuffer;
153 esiTry (esiTry const &);
154 esiProcessResult_t bestAttemptRV() const;
155};
156
f99c2cfe 157#include "esi/Var.h"
43ae1d95 158
741c2986
AJ
159class esiChoose : public ESIElement
160{
b001e822 161 MEMPROXY_CLASS(esiChoose);
43ae1d95 162
741c2986 163public:
43ae1d95 164 esiChoose(esiTreeParentPtr);
165 ~esiChoose();
166
167 void render(ESISegment::Pointer);
168 bool addElement (ESIElement::Pointer);
924f73bc 169 void fail(ESIElement *, char const * = NULL);
43ae1d95 170 esiProcessResult_t process (int dovars);
171
172 void provideData (ESISegment::Pointer data, ESIElement *source);
173 void makeCachableElements(esiChoose const &old);
924f73bc 174 void makeUsableElements(esiChoose const &old, ESIVarState &);
43ae1d95 175 Pointer makeCacheable() const;
924f73bc 176 Pointer makeUsable(esiTreeParentPtr, ESIVarState &) const;
43ae1d95 177 void NULLUnChosen();
178
179 ElementList elements;
180 int chosenelement;
181 ESIElement::Pointer otherwise;
182 void finish();
183
184private:
43ae1d95 185 esiChoose(esiChoose const &);
186 esiTreeParentPtr parent;
187 void checkValidSource (ESIElement::Pointer source) const;
188 void selectElement();
189};
190
741c2986
AJ
191class esiWhen : public esiSequence
192{
b001e822 193 MEMPROXY_CLASS(esiWhen);
741c2986
AJ
194
195public:
924f73bc 196 esiWhen(esiTreeParentPtr aParent, int attributes, const char **attr, ESIVarState *);
43ae1d95 197 ~esiWhen();
198 Pointer makeCacheable() const;
924f73bc 199 Pointer makeUsable(esiTreeParentPtr, ESIVarState &) const;
43ae1d95 200
201 bool testsTrue() const { return testValue;}
202
203 void setTestResult(bool aBool) {testValue = aBool;}
204
205private:
43ae1d95 206 esiWhen (esiWhen const &);
207 bool testValue;
208 char const *unevaluatedExpression;
924f73bc 209 ESIVarState *varState;
43ae1d95 210 void evaluate();
211};
212
26ac0430 213struct esiOtherwise : public esiSequence {
3d0ac046
HN
214 esiOtherwise(esiTreeParentPtr aParent) : esiSequence (aParent) {}
215};
43ae1d95 216
217CBDATA_CLASS_INIT(ESIContext);
218
219void ESIContext::startRead()
220{
221 assert (!reading_);
222 reading_ = true;
223}
224
225void ESIContext::finishRead()
226{
227 assert (reading_);
228 reading_ = false;
229}
230
231bool ESIContext::reading() const
232{
233 return reading_;
234}
235
0655fa4d 236ESIStreamContext::ESIStreamContext() : finished(false), include (NULL), localbuffer (new ESISegment), buffer (NULL)
43ae1d95 237{}
238
239/* Local functions */
240/* ESIContext */
59a1efb2 241static ESIContext *ESIContextNew(HttpReply *, clientStreamNode *, ClientHttpRequest *);
43ae1d95 242
43ae1d95 243void
244ESIContext::setError()
245{
246 errorpage = ERR_ESI;
955394ce 247 errorstatus = Http::scInternalServerError;
43ae1d95 248 flags.error = 1;
249}
250
251void
252ESIContext::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();
bf8fe701 263 debugs(86, 9, "ESIContext::appendOutboundData: outbound " << outbound.getRaw());
43ae1d95 264}
265
266void
267ESIContext::provideData (ESISegment::Pointer theData, ESIElement * source)
268{
bf8fe701 269 debugs(86, 5, "ESIContext::provideData: " << this << " " << theData.getRaw() << " " << source);
43ae1d95 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
280void
924f73bc 281ESIContext::fail (ESIElement * source, char const *anError)
43ae1d95 282{
283 setError();
924f73bc 284 setErrorMessage (anError);
43ae1d95 285 fail ();
286 send ();
287}
288
289void
290ESIContext::fixupOutboundTail()
291{
292 /* TODO: fixup thisNode outboundtail dross a little */
293
294 if (outboundtail.getRaw())
295 outboundtail = outboundtail->tail();
296}
297
298esiKick_t
299ESIContext::kick ()
300{
43ae1d95 301 if (flags.kicked) {
bf8fe701 302 debugs(86, 5, "esiKick: Re-entered whilst in progress");
43ae1d95 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:
bf8fe701 317 debugs(86, 5, "esiKick: esiProcess OK");
43ae1d95 318 break;
319
320 case ESI_PROCESS_PENDING_WONTFAIL:
bf8fe701 321 debugs(86, 5, "esiKick: esiProcess PENDING OK");
43ae1d95 322 break;
323
324 case ESI_PROCESS_PENDING_MAYFAIL:
bf8fe701 325 debugs(86, 5, "esiKick: esiProcess PENDING UNKNOWN");
43ae1d95 326 break;
327
328 case ESI_PROCESS_FAILED:
bf8fe701 329 debugs(86, 2, "esiKick: esiProcess " << this << " FAILED");
43ae1d95 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 */
370void
59a1efb2 371esiStreamRead (clientStreamNode *thisNode, ClientHttpRequest *http)
43ae1d95 372{
373 clientStreamNode *next;
43ae1d95 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 */
43ae1d95 378 assert (thisNode->node.prev != NULL);
379 assert (thisNode->node.next != NULL);
380
0655fa4d 381 ESIContext::Pointer context = dynamic_cast<ESIContext *>(thisNode->data.getRaw());
382 assert (context.getRaw() != NULL);
43ae1d95 383
384 if (context->flags.passthrough) {
385 /* passthru mode - read into supplied buffers */
386 next = thisNode->next();
387 clientStreamRead (thisNode, http, next->readBuffer);
43ae1d95 388 return;
389 }
390
391 context->flags.clientwantsdata = 1;
bf8fe701 392 debugs(86, 5, "esiStreamRead: Client now wants data");
43ae1d95 393
394 /* Ok, not passing through */
395
396 switch (context->kick ()) {
397
398 case ESIContext::ESI_KICK_FAILED:
f53969cc
SM
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 */
43ae1d95 402
403 case ESIContext::ESI_KICK_SENT:
404
405 case ESIContext::ESI_KICK_INPROGRESS:
43ae1d95 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 */
bf8fe701 421 debugs(86, 5, "esiStreamRead: Waiting for async resume of esi processing");
43ae1d95 422 return;
423 }
424
425 if (context->flags.oktosend && context->flags.finished && context->outbound.getRaw()) {
bf8fe701 426 debugs(86, 5, "all processing complete, but outbound data still buffered");
43ae1d95 427 assert (!context->flags.clientwantsdata);
428 /* client MUST be processing the last reply */
43ae1d95 429 return;
430 }
431
43ae1d95 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 */
bf8fe701 436 debugs(86, 5, "Telling recipient EOF on READ");
43ae1d95 437 clientStreamCallback (thisNode, http, NULL, tempBuffer);
43ae1d95 438 return;
439 }
440
0655fa4d 441 if (context->reading())
43ae1d95 442 return;
43ae1d95 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 }
43ae1d95 461}
462
463clientStream_status_t
59a1efb2 464esiStreamStatus (clientStreamNode *thisNode, ClientHttpRequest *http)
43ae1d95 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 */
43ae1d95 470 assert (thisNode->node.prev != NULL);
471 assert (thisNode->node.next != NULL);
472
0655fa4d 473 ESIContext::Pointer context = dynamic_cast<ESIContext *>(thisNode->data.getRaw());
474 assert (context.getRaw() != NULL);
43ae1d95 475
0655fa4d 476 if (context->flags.passthrough)
43ae1d95 477 return clientStreamStatus (thisNode, http);
43ae1d95 478
479 if (context->flags.oktosend && context->flags.finished &&
480 !(context->outbound.getRaw() && context->outbound_offset < context->outbound->len)) {
bf8fe701 481 debugs(86, 5, "Telling recipient EOF on STATUS");
43ae1d95 482 return STREAM_UNPLANNED_COMPLETE; /* we don't know lengths in advance */
483 }
484
485 /* ?? RC: we can't be aborted / fail ? */
43ae1d95 486 return STREAM_NONE;
487}
488
489static int
955394ce 490esiAlwaysPassthrough(Http::StatusCode sline)
43ae1d95 491{
1f140227 492 int result;
493
43ae1d95 494 switch (sline) {
495
955394ce 496 case Http::scContinue: /* Should never reach us... but squid needs to alter to accomodate this */
43ae1d95 497
955394ce 498 case Http::scSwitchingProtocols: /* Ditto */
43ae1d95 499
955394ce 500 case Http::scProcessing: /* Unknown - some extension */
43ae1d95 501
955394ce 502 case Http::scNoContent: /* no body, no esi */
43ae1d95 503
955394ce 504 case Http::scNotModified: /* ESI does not affect assembled page headers, so 304s are valid */
1f140227 505 result = 1;
43ae1d95 506 /* unreached */
507 break;
508
509 default:
1f140227 510 result = 0;
43ae1d95 511 }
1f140227 512
513 return result;
43ae1d95 514}
515
516void
517ESIContext::trimBlanks()
518{
519 /* trim leading empty buffers ? */
520
521 while (outbound.getRaw() && outbound->next.getRaw() && !outbound->len) {
bf8fe701 522 debugs(86, 5, "ESIContext::trimBlanks: " << this <<
523 " skipping segment " << outbound.getRaw());
43ae1d95 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 */
534size_t
535ESIContext::send ()
536{
bf8fe701 537 debugs(86, 5, "ESIContext::send: this=" << this);
43ae1d95 538 /* send any processed data */
539
540 trimBlanks();
541
542 if (!flags.clientwantsdata) {
bf8fe701 543 debugs(86, 5, "ESIContext::send: Client does not want data - not sending anything");
43ae1d95 544 return 0;
545 }
546
547 if (tree.getRaw() && tree->mayFail()) {
bf8fe701 548 debugs(86, 5, "ESIContext::send: Tree may fail. Not sending.");
43ae1d95 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)))) {
bf8fe701 565 debugs(86, 5, "ESIContext::send: Nothing to send.");
43ae1d95 566 return 0;
567 }
568
bf8fe701 569 debugs(86, 5, "ESIContext::send: Sending something...");
43ae1d95 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();
aa625860 575 ESIContext *templock = cbdataReference (this);
43ae1d95 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) {
41d00cd3 588 memcpy(next->readBuffer.data, &outbound->buf[outbound_offset], len);
43ae1d95 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;
bf8fe701 606 debugs(86, 5, "ESIContext::send: this=" << this << " Client no longer wants data ");
43ae1d95 607 /* Deal with re-entrancy */
f522f9b5 608 HttpReplyPointer temprep = rep;
43ae1d95 609 rep = NULL; /* freed downstream */
610
611 if (temprep && varState)
f522f9b5 612 varState->buildVary(temprep.getRaw());
43ae1d95 613
614 {
615 StoreIOBuffer tempBuffer;
616 tempBuffer.length = len;
617 tempBuffer.offset = pos - len;
618 tempBuffer.data = next->readBuffer.data;
f522f9b5 619 clientStreamCallback (thisNode, http, temprep.getRaw(), tempBuffer);
43ae1d95 620 }
621
622 if (len == 0)
623 len = 1; /* tell the caller we sent something (because we sent headers */
624
aa625860 625 cbdataReferenceDone (templock);
43ae1d95 626
137a13ea 627 debugs (86,5,"ESIContext::send: this=" << this << " sent " << len);
43ae1d95 628
629 return len;
630}
631
632void
633ESIContext::finishChildren()
634{
635 if (tree.getRaw())
636 tree->finish();
637
638 tree = NULL;
639}
640
641/* Detach event from a client Stream */
642void
59a1efb2 643esiStreamDetach (clientStreamNode *thisNode, ClientHttpRequest *http)
43ae1d95 644{
645 /* if we have pending callbacks, tell them we're done. */
43ae1d95 646 /* test preconditions */
647 assert (thisNode != NULL);
648 assert (cbdataReferenceValid (thisNode));
0655fa4d 649 ESIContext::Pointer context = dynamic_cast<ESIContext *>(thisNode->data.getRaw());
650 assert (context.getRaw() != NULL);
43ae1d95 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;
43ae1d95 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
26ac0430 668 * do is something like DOM and pass that down to a final renderer. This is
43ae1d95 669 * getting into web server territory though...
26ac0430 670 *
43ae1d95 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 */
676void
2324cda2 677esiProcessStream (clientStreamNode *thisNode, ClientHttpRequest *http, HttpReply *rep, StoreIOBuffer receivedData)
43ae1d95 678{
43ae1d95 679 /* test preconditions */
680 assert (thisNode != NULL);
681 /* ESI TODO: handle thisNode rather than asserting - it should only ever
26ac0430 682 * happen if we cause an abort and the callback chain
43ae1d95 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 * */
0655fa4d 690 assert (thisNode->data.getRaw() != NULL || rep);
43ae1d95 691 assert (thisNode->node.next != NULL);
692
0655fa4d 693 if (!thisNode->data.getRaw())
43ae1d95 694 /* setup ESI context from reply headers */
695 thisNode->data = ESIContextNew(rep, thisNode, http);
696
0655fa4d 697 ESIContext::Pointer context = dynamic_cast<ESIContext *>(thisNode->data.getRaw());
698
699 assert (context.getRaw() != NULL);
43ae1d95 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) {
2324cda2 708 clientStreamCallback (thisNode, http, rep, receivedData);
43ae1d95 709 return;
710 }
711
bf8fe701 712 debugs(86, 3, "esiProcessStream: Processing thisNode " << thisNode <<
713 " context " << context.getRaw() << " offset " <<
2324cda2 714 (int) receivedData.offset << " length " <<
715 (unsigned int)receivedData.length);
43ae1d95 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
2324cda2 723 if (receivedData.data) {
43ae1d95 724 /* Increase our buffer area with incoming data */
2324cda2 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);
43ae1d95 728 /* secure the data for later use */
729
730 if (!context->incoming.getRaw()) {
731 /* create a new buffer segment */
bf8fe701 732 debugs(86, 5, "esiProcessStream: Setting up incoming buffer");
43ae1d95 733 context->buffered = new ESISegment;
734 context->incoming = context->buffered;
735 }
736
2324cda2 737 if (receivedData.data != &context->incoming->buf[context->incoming->len]) {
43ae1d95 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;
2324cda2 740 size_t len = min (space, receivedData.length);
741 debugs(86, 5, "Copying data from " << receivedData.data << " to " <<
bf8fe701 742 &context->incoming->buf[context->incoming->len] <<
743 " because our buffer was not used");
744
41d00cd3 745 memcpy(&context->incoming->buf[context->incoming->len], receivedData.data, len);
43ae1d95 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
2324cda2 754 if (len != receivedData.length) {
43ae1d95 755 /* capture the remnants */
41d00cd3 756 memcpy(context->incoming->buf, &receivedData.data[len], receivedData.length - len);
2324cda2 757 context->incoming->len = receivedData.length - len;
43ae1d95 758 }
759
760 /* and note where we are up to */
2324cda2 761 context->readpos += receivedData.length;
43ae1d95 762 } else {
763 /* update our position counters, and if needed assign a new buffer */
2324cda2 764 context->incoming->len += receivedData.length;
43ae1d95 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
2324cda2 773 context->readpos += receivedData.length;
43ae1d95 774 }
775 }
776
777 /* EOF / Read error / aborted entry */
2324cda2 778 if (rep == NULL && receivedData.data == NULL && receivedData.length == 0 && !context->flags.finishedtemplate) {
43ae1d95 779 /* TODO: get stream status to test the entry for aborts */
780 /* else flush the esi processor */
bf8fe701 781 debugs(86, 5, "esiProcess: " << context.getRaw() << " Finished reading upstream data");
43ae1d95 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 */
43ae1d95 792 return;
793
794 case ESIContext::ESI_KICK_SENT:
795
796 case ESIContext::ESI_KICK_INPROGRESS:
43ae1d95 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);
43ae1d95 815 return;
816 }
817
bf8fe701 818 debugs(86, 3, "esiProcessStream: no data to send, no data to read, awaiting a callback");
43ae1d95 819}
820
821ESIContext::~ESIContext()
822{
823 freeResources ();
824 /* Not freed by freeresources because esi::fail needs it */
825 safe_free (errormessage);
bf8fe701 826 debugs(86, 3, "ESIContext::~ESIContext: Freed " << this);
43ae1d95 827}
828
829ESIContext *
59a1efb2 830ESIContextNew (HttpReply *rep, clientStreamNode *thisNode, ClientHttpRequest *http)
43ae1d95 831{
832 assert (rep);
833 ESIContext *rv = new ESIContext;
834 rv->rep = rep;
835 rv->cbdataLocker = rv;
836
9b769c67 837 if (esiAlwaysPassthrough(rep->sline.status())) {
43ae1d95 838 rv->flags.passthrough = 1;
839 } else {
840 /* remove specific headers for ESI to prevent
841 * downstream cache confusion */
842 HttpHeader *hdr = &rep->header;
789217a2
FC
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);
43ae1d95 847 rv->tree = new esiSequence (rv, true);
848 rv->thisNode = thisNode;
849 rv->http = http;
850 rv->flags.clientwantsdata = 1;
924f73bc 851 rv->varState = new ESIVarState (&http->request->header, http->uri);
bf8fe701 852 debugs(86, 5, "ESIContextNew: Client wants data (always created during reply cycle");
43ae1d95 853 }
854
bf8fe701 855 debugs(86, 5, "ESIContextNew: Create context " << rv);
43ae1d95 856 return rv;
857}
858
859ESIElement::ESIElementType_t
860ESIElement::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
924f73bc 887 if (!strncmp (el + offset, "assign", 6))
888 return ESI_ELEMENT_ASSIGN;
889
43ae1d95 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
911ESIElement::Pointer
912ESIContext::ParserState::top()
913{
914 return stack[stackdepth-1];
915}
916
a5488e5d 917ESIContext::ParserState::ParserState() :
f53969cc
SM
918 stackdepth(0),
919 parsing(0),
920 inited_(false)
43ae1d95 921{}
922
923bool
924ESIContext::ParserState::inited() const
925{
926 return inited_;
927}
928
929void
930ESIContext::addStackElement (ESIElement::Pointer element)
931{
932 /* Put on the stack to allow skipping of 'invalid' markup */
933 assert (parserState.stackdepth <11);
934 assert (!failed());
bf8fe701 935 debugs(86, 5, "ESIContext::addStackElement: About to add ESI Node " << element.getRaw());
43ae1d95 936
937 if (!parserState.top()->addElement(element)) {
e0236918 938 debugs(86, DBG_IMPORTANT, "ESIContext::addStackElement: failed to add esi node, probable error in ESI template");
43ae1d95 939 flags.error = 1;
940 } else {
941 /* added ok, push onto the stack */
f207fe64
FC
942 parserState.stack[parserState.stackdepth] = element;
943 ++parserState.stackdepth;
43ae1d95 944 }
945}
946
947void
948ESIContext::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;
b0365bd9 955 char *position;
9863ae03 956 Must(ellen < sizeof(localbuf)); /* prevent unexpected overruns. */
43ae1d95 957
bf8fe701 958 debugs(86, 5, "ESIContext::Start: element '" << el << "' with " << specifiedattcount << " tags");
43ae1d95 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';
9863ae03 970 xstrncpy(&localbuf[1], el, sizeof(localbuf) - 2);
b0365bd9 971 position = localbuf + strlen (localbuf);
43ae1d95 972
973 for (i = 0; i < specifiedattcount && attr[i]; i += 2) {
9863ae03 974 Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 1);
f207fe64
FC
975 *position = ' ';
976 ++position;
43ae1d95 977 /* TODO: handle thisNode gracefully */
9863ae03 978 xstrncpy(position, attr[i], sizeof(localbuf) - (position - localbuf));
b0365bd9 979 position += strlen (position);
9863ae03 980 Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 2);
f207fe64
FC
981 *position = '=';
982 ++position;
983 *position = '\"';
984 ++position;
dde08ec7
AJ
985 const char *chPtr = attr[i + 1];
986 char ch;
987 while ((ch = *chPtr++) != '\0') {
988 if (ch == '\"') {
9863ae03
AJ
989 Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 6);
990 xstrncpy(position, "&quot;", sizeof(localbuf) - (position-localbuf));
b0365bd9 991 position += 6;
dde08ec7 992 } else {
9863ae03 993 Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 1);
aec55359
FC
994 *position = ch;
995 ++position;
dde08ec7
AJ
996 }
997 }
9863ae03 998 Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 1);
f207fe64
FC
999 *position = '\"';
1000 ++position;
43ae1d95 1001 }
1002
9863ae03 1003 Must(static_cast<size_t>(position - localbuf) < sizeof(localbuf) - 2);
aec55359
FC
1004 *position = '>';
1005 ++position;
b0365bd9 1006 *position = '\0';
43ae1d95 1007
b0365bd9 1008 addLiteral (localbuf, position - localbuf);
bf8fe701 1009 debugs(86, 5, "esi stack depth " << parserState.stackdepth);
43ae1d95 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 */
924f73bc 1020 element = new ESIInclude (parserState.top().getRaw(), specifiedattcount, attr, this);
43ae1d95 1021 break;
1022
1023 case ESIElement::ESI_ELEMENT_REMOVE:
1024 /* Put on the stack to allow skipping of 'invalid' markup */
8c41d298 1025 element = new esiRemove();
43ae1d95 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 */
924f73bc 1045 element = new ESIVar (parserState.top().getRaw());
43ae1d95 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;
924f73bc 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;
43ae1d95 1067 }
1068
1069 addStackElement(element);
1070
bf8fe701 1071 debugs(86, 5, "esi stack depth " << parserState.stackdepth);
43ae1d95 1072
1073} /* End of start handler */
1074
1075void
1076ESIContext::end(const char *el)
1077{
1078 unsigned int ellen = strlen (el);
1079 char localbuf [HTTP_REQBUF_SZ];
b0365bd9 1080 char *position;
43ae1d95 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:
9863ae03 1089 Must(ellen < sizeof(localbuf) - 3); /* prevent unexpected overruns. */
43ae1d95 1090 /* Add elements we aren't interested in */
1091 localbuf[0] = '<';
1092 localbuf[1] = '/';
9863ae03 1093 xstrncpy(&localbuf[2], el, sizeof(localbuf) - 3);
b0365bd9 1094 position = localbuf + strlen (localbuf);
f207fe64
FC
1095 *position = '>';
1096 ++position;
b0365bd9
FC
1097 *position = '\0';
1098 addLiteral (localbuf, position - localbuf);
43ae1d95 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:
924f73bc 1120
1121 case ESIElement::ESI_ELEMENT_ASSIGN:
43ae1d95 1122 /* pop of the stack */
1123 parserState.stack[--parserState.stackdepth] = NULL;
1124 break;
1125 }
1126} /* End of end handler */
1127
1128void
1129ESIContext::parserDefault (const char *s, int len)
1130{
1131 if (failed())
1132 return;
1133
1134 /* handle any skipped data */
1135 addLiteral (s, len);
1136}
1137
1138void
1139ESIContext::parserComment (const char *s)
1140{
1141 if (failed())
1142 return;
1143
1144 if (!strncmp(s, "esi",3)) {
bf8fe701 1145 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block encountered");
43ae1d95 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)) {
fa84c01d 1153 debugs(86, DBG_CRITICAL, "ESIContext::parserComment: Parsing fragment '" << s + 3 << "' failed.");
43ae1d95 1154 setError();
1155 char tempstr[1024];
137a13ea 1156 snprintf(tempstr, 1023, "ESIContext::parserComment: Parse error at line %ld:\n%s\n",
43ae1d95 1157 tempParser->lineNumber(),
1158 tempParser->errorString());
fa84c01d 1159 debugs(86, DBG_CRITICAL, "" << tempstr << "");
43ae1d95 1160
924f73bc 1161 setErrorMessage(tempstr);
43ae1d95 1162 }
1163
bf8fe701 1164 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block parsed");
43ae1d95 1165 return;
1166 } else {
1167 char localbuf [HTTP_REQBUF_SZ];
1168 unsigned int len;
bf8fe701 1169 debugs(86, 5, "ESIContext::parserComment: Regenerating comment block");
43ae1d95 1170 len = strlen (s);
1171
1172 if (len > sizeof (localbuf) - 9) {
fa84c01d 1173 debugs(86, DBG_CRITICAL, "ESIContext::parserComment: Truncating long comment");
43ae1d95 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
1184void
1185ESIContext::addLiteral (const char *s, int len)
1186{
1187 /* handle any skipped data */
1188 assert (len);
bf8fe701 1189 debugs(86, 5, "literal length is " << len);
43ae1d95 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)) {
e0236918 1195 debugs(86, DBG_IMPORTANT, "ESIContext::addLiteral: failed to add esi node, probable error in ESI template");
43ae1d95 1196 flags.error = 1;
1197 }
1198}
1199
1200void
1201ESIContext::ParserState::init(ESIParserClient *userData)
1202{
1203 theParser = ESIParser::NewParser (userData);
1204 inited_ = true;
1205}
1206
1207void
1208ESIContext::parseOneBuffer()
1209{
1210 assert (buffered.getRaw());
1211
137a13ea 1212 debugs (86,9,"ESIContext::parseOneBuffer: " << buffered->len << " bytes");
43ae1d95 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];
137a13ea 1218 snprintf (tempstr, 1023, "esiProcess: Parse error at line %ld:\n%s\n",
43ae1d95 1219 parserState.theParser->lineNumber(),
1220 parserState.theParser->errorString());
fa84c01d 1221 debugs(86, DBG_CRITICAL, "" << tempstr << "");
43ae1d95 1222
924f73bc 1223 setErrorMessage(tempstr);
43ae1d95 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
1239void
1240ESIContext::parse()
1241{
1242 if (!parserState.stackdepth) {
bf8fe701 1243 debugs(86, 5, "empty parser stack, inserting the top level node");
43ae1d95 1244 assert (tree.getRaw());
f207fe64
FC
1245 parserState.stack[parserState.stackdepth] = tree;
1246 ++parserState.stackdepth;
43ae1d95 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
1271esiProcessResult_t
1272ESIContext::process ()
1273{
1274 /* parsing:
1275 * read through buffered, skipping plain text, and skipping any
1276 * <...> entry that is not an <esi: entry.
14e9491b 1277 * when it's found, hand an esiLiteral of the preceding data to our current
43ae1d95 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) {
bf8fe701 1296 debugs(86, 5, "ESIContext::process: Parsing failed");
43ae1d95 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
26ac0430 1322 switch (status) {
43ae1d95 1323
1324 case ESI_PROCESS_COMPLETE:
bf8fe701 1325 debugs(86, 5, "esiProcess: tree Processed OK");
43ae1d95 1326 break;
1327
1328 case ESI_PROCESS_PENDING_WONTFAIL:
bf8fe701 1329 debugs(86, 5, "esiProcess: tree Processed PENDING OK");
43ae1d95 1330 break;
1331
1332 case ESI_PROCESS_PENDING_MAYFAIL:
bf8fe701 1333 debugs(86, 5, "esiProcess: tree Processed PENDING UNKNOWN");
43ae1d95 1334 break;
1335
1336 case ESI_PROCESS_FAILED:
fa84c01d 1337 debugs(86, DBG_CRITICAL, "esiProcess: tree Processed FAILED");
43ae1d95 1338 setError();
1339
924f73bc 1340 setErrorMessage("esiProcess: ESI template Processing failed.");
43ae1d95 1341
1342 PROF_stop(esiProcessing);
1343
1344 return ESI_PROCESS_FAILED;
1345
1346 break;
1347 }
1348
26ac0430 1349 if (status != ESI_PROCESS_PENDING_MAYFAIL && (flags.finishedtemplate || cachedASTInUse)) {
43ae1d95 1350 /* We've read the entire template, and no nodes will
1351 * return failure
1352 */
bf8fe701 1353 debugs(86, 5, "esiProcess, request will succeed");
43ae1d95 1354 flags.oktosend = 1;
1355 }
1356
1357 if (status == ESI_PROCESS_COMPLETE
26ac0430 1358 && (flags.finishedtemplate || cachedASTInUse)) {
43ae1d95 1359 /* we've finished all processing. Render and send. */
bf8fe701 1360 debugs(86, 5, "esiProcess, processing complete");
43ae1d95 1361 flags.finished = 1;
1362 }
1363
1364 PROF_stop(esiProcessing);
1365 return status; /* because we have no callbacks */
1366 }
1367}
1368
1369void
1370ESIContext::ParserState::freeResources()
1371{
1372 theParser = NULL;
1373 inited_ = false;
1374}
1375
1376void
1377ESIContext::ParserState::popAll()
1378{
1379 while (stackdepth)
1380 stack[--stackdepth] = NULL;
1381}
1382
1383void
1384ESIContext::freeResources ()
1385{
b9fde3d8 1386 debugs(86, 5, HERE << "Freeing for this=" << this);
43ae1d95 1387
f522f9b5 1388 rep = nullptr; // refcounted
43ae1d95 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);
00d77d6b 1400 delete varState;
6269c7b1 1401 varState=NULL;
43ae1d95 1402 /* don't touch incoming, it's a pointer into buffered anyway */
1403}
1404
955394ce 1405ErrorState *clientBuildError (err_type, Http::StatusCode, char const *, Ip::Address &, HttpRequest *);
43ae1d95 1406
43ae1d95 1407/* This can ONLY be used before we have sent *any* data to the client */
1408void
1409ESIContext::fail ()
1410{
bf8fe701 1411 debugs(86, 5, "ESIContext::fail: this=" << this);
43ae1d95 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 */
1b76e6c1
AJ
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);
43ae1d95 1424 err->err_msg = errormessage;
1425 errormessage = NULL;
c70281f8 1426 rep = err->BuildHttpReply();
0521f8be
FC
1427 assert (rep->body.hasContent());
1428 size_t errorprogress = rep->body.contentSize();
43ae1d95 1429 /* Tell esiSend where to start sending from */
1430 outbound_offset = 0;
1431 /* copy the membuf from the reply to outbound */
1432
0521f8be 1433 while (errorprogress < (size_t)rep->body.contentSize()) {
43ae1d95 1434 appendOutboundData(new ESISegment);
0521f8be 1435 errorprogress += outboundtail->append(rep->body.content() + errorprogress, rep->body.contentSize() - errorprogress);
43ae1d95 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
26ac0430 1444 * themselves when the reply is freed - and we don't know what to
43ae1d95 1445 * clean anyway.
1446 */
1447}
1448
43ae1d95 1449/* Implementation of ESIElements */
1450
1451/* esiComment */
1452esiComment::~esiComment()
1453{
bf8fe701 1454 debugs(86, 5, "esiComment::~esiComment " << this);
43ae1d95 1455}
1456
43ae1d95 1457esiComment::esiComment()
1458{}
1459
1460void
1461esiComment::finish()
1462{}
1463
1464void
1465esiComment::render(ESISegment::Pointer output)
1466{
1467 /* Comments do nothing dude */
bf8fe701 1468 debugs(86, 5, "esiCommentRender: Rendering comment " << this << " into " << output.getRaw());
43ae1d95 1469}
1470
1471ESIElement::Pointer
1472esiComment::makeCacheable() const
1473{
bf8fe701 1474 debugs(86, 5, "esiComment::makeCacheable: returning NULL");
43ae1d95 1475 return NULL;
1476}
1477
1478ESIElement::Pointer
924f73bc 1479esiComment::makeUsable(esiTreeParentPtr, ESIVarState &) const
43ae1d95 1480{
1481 fatal ("esiComment::Usable: unreachable code!\n");
1482 return NULL;
1483}
1484
1485/* esiLiteral */
43ae1d95 1486esiLiteral::~esiLiteral()
1487{
bf8fe701 1488 debugs(86, 5, "esiLiteral::~esiLiteral: " << this);
43ae1d95 1489 ESISegmentFreeList (buffer);
1490 cbdataReferenceDone (varState);
1491}
1492
d59e4742
FC
1493esiLiteral::esiLiteral(ESISegment::Pointer aSegment) :
1494 buffer(aSegment),
1495 varState(nullptr)
43ae1d95 1496{
43ae1d95 1497 /* Nothing to do */
1498 flags.donevars = 1;
1499}
1500
1501void
1502esiLiteral::finish()
1503{}
1504
1505/* precondition: the buffer chain has at least start + length bytes of data
1506 */
1507esiLiteral::esiLiteral(ESIContext *context, const char *s, int numberOfCharacters)
1508{
1509 assert (s);
a5488e5d 1510 flags.donevars = 0;
43ae1d95 1511 buffer = new ESISegment;
1512 ESISegment::Pointer local = buffer;
57d55dfa 1513 size_t start = 0;
43ae1d95 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
a5488e5d 1527 varState = cbdataReference(context->varState);
43ae1d95 1528}
1529
1530void
1531esiLiteral::render (ESISegment::Pointer output)
1532{
bf8fe701 1533 debugs(86, 9, "esiLiteral::render: Rendering " << this);
43ae1d95 1534 /* append the entire chain */
1535 assert (output->next.getRaw() == NULL);
1536 output->next = buffer;
1537 buffer = NULL;
1538}
1539
1540esiProcessResult_t
1541esiLiteral::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()) {
6c3b212b 1551 varState->feedData(temp->buf,temp->len);
43ae1d95 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
1565esiLiteral::esiLiteral(esiLiteral const &old) : buffer (old.buffer->cloneList()),
f53969cc 1566 varState (NULL)
43ae1d95 1567{
1568 flags.donevars = 0;
1569}
1570
1571ESIElement::Pointer
1572esiLiteral::makeCacheable() const
1573{
1574 return new esiLiteral (*this);
1575}
1576
1577ESIElement::Pointer
9e167fa2 1578esiLiteral::makeUsable(esiTreeParentPtr, ESIVarState &newVarState) const
43ae1d95 1579{
bf8fe701 1580 debugs(86, 5, "esiLiteral::makeUsable: Creating usable literal");
43ae1d95 1581 esiLiteral * result = new esiLiteral (*this);
1582 result->varState = cbdataReference (&newVarState);
1583 return result;
1584}
1585
924f73bc 1586/* esiRemove */
924f73bc 1587void
1588esiRemove::render(ESISegment::Pointer output)
1589{
1590 /* Removes do nothing dude */
bf8fe701 1591 debugs(86, 5, "esiRemoveRender: Rendering remove " << this);
43ae1d95 1592}
1593
924f73bc 1594/* Accept non-ESI children */
1595bool
1596esiRemove::addElement (ESIElement::Pointer element)
43ae1d95 1597{
924f73bc 1598 if (!dynamic_cast<esiLiteral*>(element.getRaw())) {
bf8fe701 1599 debugs(86, 5, "esiRemoveAdd: Failed for " << this);
924f73bc 1600 return false;
1601 }
43ae1d95 1602
924f73bc 1603 return true;
43ae1d95 1604}
1605
924f73bc 1606ESIElement::Pointer
1607esiRemove::makeCacheable() const
43ae1d95 1608{
bf8fe701 1609 debugs(86, 5, "esiRemove::makeCacheable: Returning NULL");
924f73bc 1610 return NULL;
1611}
43ae1d95 1612
924f73bc 1613ESIElement::Pointer
1614esiRemove::makeUsable(esiTreeParentPtr, ESIVarState &) const
1615{
1616 fatal ("esiRemove::Usable: unreachable code!\n");
1617 return NULL;
1618}
43ae1d95 1619
924f73bc 1620/* esiTry */
1621esiTry::~esiTry()
1622{
bf8fe701 1623 debugs(86, 5, "esiTry::~esiTry " << this);
924f73bc 1624}
43ae1d95 1625
a5488e5d 1626esiTry::esiTry(esiTreeParentPtr aParent) :
f53969cc
SM
1627 parent(aParent),
1628 exceptbuffer(NULL)
a5488e5d
AJ
1629{
1630 memset(&flags, 0, sizeof(flags));
1631}
43ae1d95 1632
1633void
a5488e5d 1634esiTry::render(ESISegment::Pointer output)
43ae1d95 1635{
1636 /* Try renders from it's children */
43ae1d95 1637 assert (attempt.getRaw());
1638 assert (except.getRaw());
bf8fe701 1639 debugs(86, 5, "esiTryRender: Rendering Try " << this);
43ae1d95 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
bf8fe701 1651 debugs(86, 5, "esiTryRender: Neither except nor attempt succeeded?!?");
43ae1d95 1652}
1653
1654/* Accept attempt and except only */
1655bool
1656esiTry::addElement(ESIElement::Pointer element)
1657{
bf8fe701 1658 debugs(86, 5, "esiTryAdd: Try " << this << " adding element " <<
1659 element.getRaw());
43ae1d95 1660
1661 if (dynamic_cast<esiLiteral*>(element.getRaw())) {
1662 /* Swallow whitespace */
bf8fe701 1663 debugs(86, 5, "esiTryAdd: Try " << this << " skipping whitespace " << element.getRaw());
43ae1d95 1664 return true;
1665 }
1666
1667 if (dynamic_cast<esiAttempt*>(element.getRaw())) {
1668 if (attempt.getRaw()) {
e0236918 1669 debugs(86, DBG_IMPORTANT, "esiTryAdd: Failed for " << this << " - try allready has an attempt node (section 3.4)");
43ae1d95 1670 return false;
1671 }
1672
1673 attempt = element;
1674 return true;
1675 }
1676
1677 if (dynamic_cast<esiExcept*>(element.getRaw())) {
1678 if (except.getRaw()) {
e0236918 1679 debugs(86, DBG_IMPORTANT, "esiTryAdd: Failed for " << this << " - try already has an except node (section 3.4)");
43ae1d95 1680 return false;
1681 }
1682
1683 except = element;
1684 return true;
1685 }
1686
e0236918 1687 debugs(86, DBG_IMPORTANT, "esiTryAdd: Failed to add element " << element.getRaw() << " to try " << this << ", incorrect element type (see section 3.4)");
43ae1d95 1688 return false;
1689}
1690
1691esiProcessResult_t
1692esiTry::bestAttemptRV() const
1693{
1694 if (flags.attemptfailed)
1695 return ESI_PROCESS_COMPLETE;
1696 else
1697 return ESI_PROCESS_PENDING_MAYFAIL;
1698}
1699
1700esiProcessResult_t
1701esiTry::process (int dovars)
1702{
1703 esiProcessResult_t rv = ESI_PROCESS_PENDING_MAYFAIL;
43ae1d95 1704
1705 if (!attempt.getRaw()) {
fa84c01d 1706 debugs(86, DBG_CRITICAL, "esiTryProcess: Try has no attempt element - ESI template is invalid (section 3.4)");
43ae1d95 1707 return ESI_PROCESS_FAILED;
1708 }
1709
1710 if (!except.getRaw()) {
fa84c01d 1711 debugs(86, DBG_CRITICAL, "esiTryProcess: Try has no except element - ESI template is invalid (section 3.4)");
43ae1d95 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:
bf8fe701 1720 debugs(86, 5, "esiTryProcess: attempt Processed OK");
43ae1d95 1721 flags.attemptok = 1;
1722 return ESI_PROCESS_COMPLETE;
1723
1724 case ESI_PROCESS_PENDING_WONTFAIL:
bf8fe701 1725 debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
43ae1d95 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:
bf8fe701 1730 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
43ae1d95 1731 break;
1732
1733 case ESI_PROCESS_FAILED:
bf8fe701 1734 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
43ae1d95 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:
bf8fe701 1749 debugs(86, 5, "esiTryProcess: except Processed OK");
43ae1d95 1750 flags.exceptok = 1;
1751 return bestAttemptRV();
1752
1753 case ESI_PROCESS_PENDING_WONTFAIL:
bf8fe701 1754 debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
43ae1d95 1755 /* We're not done yet, but can't fail */
1756 return ESI_PROCESS_PENDING_WONTFAIL;
1757
1758 case ESI_PROCESS_PENDING_MAYFAIL:
bf8fe701 1759 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
43ae1d95 1760 /* The except branch fail fail */
1761 return ESI_PROCESS_PENDING_MAYFAIL;
1762
1763 case ESI_PROCESS_FAILED:
bf8fe701 1764 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
43ae1d95 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
1776void
1777esiTry::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) {
924f73bc 1784 parent->fail (this, "esi:try - except claused failed, or no except clause found");
43ae1d95 1785 }
1786 }
1787
1788 /* nothing to do when except fails and attempt hasn't */
1789}
1790
1791void
924f73bc 1792esiTry::fail(ESIElement *source, char const *anError)
43ae1d95 1793{
1794 assert (source);
1795 assert (source == attempt || source == except);
bf8fe701 1796 debugs(86, 5, "esiTry::fail: this=" << this << ", source=" << source << ", message=" << anError);
43ae1d95 1797
1798 if (source == except) {
1799 flags.exceptfailed = 1;
1800 } else {
1801 flags.attemptfailed = 1;
1802 }
1803
1804 notifyParent();
1805}
1806
1807void
1808esiTry::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
1821esiTry::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
1833ESIElement::Pointer
1834esiTry::makeCacheable() const
1835{
bf8fe701 1836 debugs(86, 5, "esiTry::makeCacheable: making cachable Try from " << this);
43ae1d95 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
1849ESIElement::Pointer
924f73bc 1850esiTry::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
43ae1d95 1851{
bf8fe701 1852 debugs(86, 5, "esiTry::makeUsable: making usable Try from " << this);
43ae1d95 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
1867void
1868esiTry::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
43ae1d95 1883/* esiChoose */
1884esiChoose::~esiChoose()
1885{
bf8fe701 1886 debugs(86, 5, "esiChoose::~esiChoose " << this);
43ae1d95 1887}
1888
d59e4742
FC
1889esiChoose::esiChoose(esiTreeParentPtr aParent) :
1890 elements(),
1891 chosenelement(-1),
1892 parent(aParent)
43ae1d95 1893{}
1894
1895void
1896esiChoose::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());
bf8fe701 1901 debugs(86, 5, "esiChooseRender: rendering");
43ae1d95 1902
1903 if (chosenelement >= 0)
1904 elements[chosenelement]->render(output);
1905 else if (otherwise.getRaw())
1906 otherwise->render(output);
1907}
1908
1909bool
1910esiChoose::addElement(ESIElement::Pointer element)
1911{
1912 /* add an element to the output list */
1913
1914 if (dynamic_cast<esiLiteral*>(element.getRaw())) {
1915 /* Swallow whitespace */
bf8fe701 1916 debugs(86, 5, "esiChooseAdd: Choose " << this << " skipping whitespace " << element.getRaw());
43ae1d95 1917 return true;
1918 }
1919
1920 /* Some elements require specific parents */
1921 if (!(dynamic_cast<esiWhen*>(element.getRaw()) || dynamic_cast<esiOtherwise*>(element.getRaw()))) {
fa84c01d 1922 debugs(86, DBG_CRITICAL, "esiChooseAdd: invalid child node for esi:choose (section 3.3)");
43ae1d95 1923 return false;
1924 }
1925
1926 if (dynamic_cast<esiOtherwise*>(element.getRaw())) {
1927 if (otherwise.getRaw()) {
fa84c01d 1928 debugs(86, DBG_CRITICAL, "esiChooseAdd: only one otherwise node allowed for esi:choose (section 3.3)");
43ae1d95 1929 return false;
1930 }
1931
1932 otherwise = element;
1933 } else {
1934 elements.push_back (element);
1935
137a13ea 1936 debugs (86,3, "esiChooseAdd: Added a new element, elements = " << elements.size());
43ae1d95 1937
ea25a575
TH
1938 if (chosenelement == -1) {
1939 const esiWhen * topElement=dynamic_cast<esiWhen *>(element.getRaw());
1940 if (topElement && topElement->testsTrue()) {
43ae1d95 1941 chosenelement = elements.size() - 1;
137a13ea 1942 debugs (86,3, "esiChooseAdd: Chose element " << elements.size());
43ae1d95 1943 }
ea25a575 1944 }
43ae1d95 1945 }
1946
1947 return true;
1948}
1949
1950void
1951esiChoose::selectElement()
1952{
1953 if (chosenelement > -1)
1954 return;
1955
1956 for (size_t counter = 0; counter < elements.size(); ++counter) {
a5488e5d
AJ
1957 const esiWhen *el = dynamic_cast<esiWhen *>(elements[counter].getRaw());
1958 if (el && el->testsTrue()) {
43ae1d95 1959 chosenelement = counter;
137a13ea 1960 debugs (86,3, "esiChooseAdd: Chose element " << counter + 1);
43ae1d95 1961 return;
1962 }
1963 }
1964}
1965
1966void
1967esiChoose::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
1979void
1980ElementList::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
26ac0430
AJ
1989 debugs(86, 5, "esiSequence::NULLElements: Setting index " <<
1990 loopPosition << ", pointer " <<
1991 elements[loopPosition].getRaw() << " to NULL");
43ae1d95 1992
1993 elements[loopPosition] = NULL;
1994 }
1995}
1996
1997void
1998esiChoose::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
2014esiProcessResult_t
2015esiChoose::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
2044void
2045esiChoose::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
2058void
924f73bc 2059esiChoose::fail(ESIElement * source, char const *anError)
43ae1d95 2060{
2061 checkValidSource (source);
2062 elements.setNULL (0, elements.size());
2063
2064 if (otherwise.getRaw())
2065 otherwise->finish();
2066
2067 otherwise = NULL;
2068
924f73bc 2069 parent->fail(this, anError);
43ae1d95 2070
2071 parent = NULL;
2072}
2073
2074void
2075esiChoose::provideData (ESISegment::Pointer data, ESIElement*source)
2076{
2077 checkValidSource (source);
2078 parent->provideData (data, this);
2079}
2080
43ae1d95 2081esiChoose::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
2091void
2092esiChoose::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
2102void
924f73bc 2103esiChoose::makeUsableElements(esiChoose const &old, ESIVarState &newVarState)
43ae1d95 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
2113ESIElement::Pointer
2114esiChoose::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
2126ESIElement::Pointer
924f73bc 2127esiChoose::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
43ae1d95 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 */
2142ElementList::ElementList () : elements(NULL), allocedcount(0), allocedsize(0), elementcount (0)
2143{}
2144
2145ElementList::~ElementList()
2146{
bf8fe701 2147 debugs(86, 5, "ElementList::~ElementList " << this);
43ae1d95 2148 setNULL(0, elementcount);
2149
2150 if (elements)
2151 memFreeBuf (allocedsize, elements);
2152}
2153
2154ESIElement::Pointer &
2155ElementList::operator [] (int index)
2156{
2157 return elements[index];
2158}
2159
2160ESIElement::Pointer const &
2161ElementList::operator [] (int index) const
2162{
2163 return elements[index];
2164}
2165
2166void
2167ElementList::pop_front (size_t const count)
2168{
2169 if (!count)
2170 return;
2171
41d00cd3 2172 memmove(elements, &elements[count], (elementcount - count) * sizeof (ESIElement::Pointer));
43ae1d95 2173
2174 elementcount -= count;
2175}
2176
2177void
2178ElementList::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
2188size_t
2189ElementList::size() const
2190{
2191 return elementcount;
2192}
2193
2194/* esiWhen */
a5488e5d 2195esiWhen::esiWhen(esiTreeParentPtr aParent, int attrcount, const char **attr,ESIVarState *aVar) :
f53969cc
SM
2196 esiSequence(aParent),
2197 testValue(false),
2198 unevaluatedExpression(NULL),
2199 varState(NULL)
43ae1d95 2200{
43ae1d95 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 */
bf8fe701 2206 debugs(86, 5, "esiWhen::esiWhen: Evaluating '" << attr[loopCounter+1] << "'");
43ae1d95 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 */
e0236918 2214 debugs(86, DBG_IMPORTANT, "Found misttyped attribute on ESI When clause");
43ae1d95 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
2229esiWhen::~esiWhen()
2230{
2231 safe_free (unevaluatedExpression);
2232
2233 if (varState)
2234 cbdataReferenceDone (varState);
2235}
2236
2237void
2238esiWhen::evaluate()
2239{
2240 if (!unevaluatedExpression)
2241 return;
2242
a5488e5d 2243 assert(varState);
43ae1d95 2244
2245 varState->feedData(unevaluatedExpression, strlen (unevaluatedExpression));
2246
2247 char const *expression = varState->extractChar ();
2248
924f73bc 2249 setTestResult(ESIExpression::Evaluate (expression));
43ae1d95 2250
2251 safe_free (expression);
2252}
2253
a5488e5d 2254esiWhen::esiWhen(esiWhen const &old) :
f53969cc
SM
2255 esiSequence(old),
2256 testValue(false),
2257 unevaluatedExpression(NULL),
2258 varState(NULL)
43ae1d95 2259{
43ae1d95 2260 if (old.unevaluatedExpression)
2261 unevaluatedExpression = xstrdup(old.unevaluatedExpression);
43ae1d95 2262}
2263
2264ESIElement::Pointer
2265esiWhen::makeCacheable() const
2266{
2267 return new esiWhen(*this);
2268}
2269
2270ESIElement::Pointer
924f73bc 2271esiWhen::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
43ae1d95 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
43ae1d95 2282/* TODO: implement surrogate targeting and control processing */
2283int
2284esiEnableProcessing (HttpReply *rep)
2285{
2286 int rv = 0;
2287
3a5e0b03 2288 if (rep->surrogate_control) {
45a58345 2289 HttpHdrScTarget *sctusable =
633b9845 2290 rep->surrogate_control->getMergedTarget(Config.Accel.surrogate_id);
43ae1d95 2291
bf574df8
AJ
2292 // found something targeted at us
2293 if (sctusable &&
2294 sctusable->hasContent() &&
2295 sctusable->content().pos("ESI/1.0")) {
43ae1d95 2296 rv = 1;
bf574df8 2297 }
43ae1d95 2298
45a58345 2299 delete sctusable;
43ae1d95 2300 }
2301
2302 return rv;
2303}
2304
454e8283 2305#endif /* USE_SQUID_ESI == 1 */
f53969cc 2306