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