]> git.ipfire.org Git - thirdparty/squid.git/blame - src/esi/Esi.cc
Fixed some cases of variable shadowing
[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;
b0365bd9 1007 char *position;
43ae1d95 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));
b0365bd9 1023 position = localbuf + strlen (localbuf);
43ae1d95 1024
1025 for (i = 0; i < specifiedattcount && attr[i]; i += 2) {
b0365bd9 1026 *position++ = ' ';
43ae1d95 1027 /* TODO: handle thisNode gracefully */
b0365bd9
FC
1028 assert (xstrncpy (position, attr[i], sizeof(localbuf) + (position - localbuf)));
1029 position += strlen (position);
1030 *position++ = '=';
1031 *position++ = '\"';
dde08ec7
AJ
1032 const char *chPtr = attr[i + 1];
1033 char ch;
1034 while ((ch = *chPtr++) != '\0') {
1035 if (ch == '\"') {
b0365bd9
FC
1036 assert( xstrncpy(position, "&quot;", sizeof(localbuf) + (position-localbuf)) );
1037 position += 6;
dde08ec7 1038 } else {
b0365bd9 1039 *(position++) = ch;
dde08ec7
AJ
1040 }
1041 }
b0365bd9
FC
1042 position += strlen (position);
1043 *position++ = '\"';
43ae1d95 1044 }
1045
b0365bd9
FC
1046 *position++ = '>';
1047 *position = '\0';
43ae1d95 1048
b0365bd9 1049 addLiteral (localbuf, position - localbuf);
bf8fe701 1050 debugs(86, 5, "esi stack depth " << parserState.stackdepth);
43ae1d95 1051 return;
1052 break;
1053
1054 case ESIElement::ESI_ELEMENT_COMMENT:
1055 /* Put on the stack to allow skipping of 'invalid' markup */
1056 element = new esiComment ();
1057 break;
1058
1059 case ESIElement::ESI_ELEMENT_INCLUDE:
1060 /* Put on the stack to allow skipping of 'invalid' markup */
924f73bc 1061 element = new ESIInclude (parserState.top().getRaw(), specifiedattcount, attr, this);
43ae1d95 1062 break;
1063
1064 case ESIElement::ESI_ELEMENT_REMOVE:
1065 /* Put on the stack to allow skipping of 'invalid' markup */
1066 element = esiRemoveNew ();
1067 break;
1068
1069 case ESIElement::ESI_ELEMENT_TRY:
1070 /* Put on the stack to allow skipping of 'invalid' markup */
1071 element = new esiTry (parserState.top().getRaw());
1072 break;
1073
1074 case ESIElement::ESI_ELEMENT_ATTEMPT:
1075 /* Put on the stack to allow skipping of 'invalid' markup */
1076 element = new esiAttempt (parserState.top().getRaw());
1077 break;
1078
1079 case ESIElement::ESI_ELEMENT_EXCEPT:
1080 /* Put on the stack to allow skipping of 'invalid' markup */
1081 element = new esiExcept (parserState.top().getRaw());
1082 break;
1083
1084 case ESIElement::ESI_ELEMENT_VARS:
1085 /* Put on the stack to allow skipping of 'invalid' markup */
924f73bc 1086 element = new ESIVar (parserState.top().getRaw());
43ae1d95 1087 break;
1088
1089 case ESIElement::ESI_ELEMENT_CHOOSE:
1090 /* Put on the stack to allow skipping of 'invalid' markup */
1091 element = new esiChoose (parserState.top().getRaw());
1092 break;
1093
1094 case ESIElement::ESI_ELEMENT_WHEN:
1095 /* Put on the stack to allow skipping of 'invalid' markup */
1096 element = new esiWhen (parserState.top().getRaw(), specifiedattcount, attr, varState);
1097 break;
1098
1099 case ESIElement::ESI_ELEMENT_OTHERWISE:
1100 /* Put on the stack to allow skipping of 'invalid' markup */
1101 element = new esiOtherwise (parserState.top().getRaw());
1102 break;
924f73bc 1103
1104 case ESIElement::ESI_ELEMENT_ASSIGN:
1105 /* Put on the stack to allow skipping of 'invalid' markup */
1106 element = new ESIAssign (parserState.top().getRaw(), specifiedattcount, attr, this);
1107 break;
43ae1d95 1108 }
1109
1110 addStackElement(element);
1111
bf8fe701 1112 debugs(86, 5, "esi stack depth " << parserState.stackdepth);
43ae1d95 1113
1114} /* End of start handler */
1115
1116void
1117ESIContext::end(const char *el)
1118{
1119 unsigned int ellen = strlen (el);
1120 char localbuf [HTTP_REQBUF_SZ];
b0365bd9 1121 char *position;
43ae1d95 1122
1123 if (flags.error)
1124 /* waiting for expat to finish the buffer we gave it */
1125 return;
1126
1127 switch (ESIElement::IdentifyElement (el)) {
1128
1129 case ESIElement::ESI_ELEMENT_NONE:
1130 assert (ellen < sizeof (localbuf)); /* prevent unexpected overruns. */
1131 /* Add elements we aren't interested in */
1132 localbuf[0] = '<';
1133 localbuf[1] = '/';
1134 assert (xstrncpy (&localbuf[2], el, sizeof(localbuf) - 3));
b0365bd9
FC
1135 position = localbuf + strlen (localbuf);
1136 *position++ = '>';
1137 *position = '\0';
1138 addLiteral (localbuf, position - localbuf);
43ae1d95 1139 break;
1140
1141 case ESIElement::ESI_ELEMENT_COMMENT:
1142
1143 case ESIElement::ESI_ELEMENT_INCLUDE:
1144
1145 case ESIElement::ESI_ELEMENT_REMOVE:
1146
1147 case ESIElement::ESI_ELEMENT_TRY:
1148
1149 case ESIElement::ESI_ELEMENT_ATTEMPT:
1150
1151 case ESIElement::ESI_ELEMENT_EXCEPT:
1152
1153 case ESIElement::ESI_ELEMENT_VARS:
1154
1155 case ESIElement::ESI_ELEMENT_CHOOSE:
1156
1157 case ESIElement::ESI_ELEMENT_WHEN:
1158
1159 case ESIElement::ESI_ELEMENT_OTHERWISE:
924f73bc 1160
1161 case ESIElement::ESI_ELEMENT_ASSIGN:
43ae1d95 1162 /* pop of the stack */
1163 parserState.stack[--parserState.stackdepth] = NULL;
1164 break;
1165 }
1166} /* End of end handler */
1167
1168void
1169ESIContext::parserDefault (const char *s, int len)
1170{
1171 if (failed())
1172 return;
1173
1174 /* handle any skipped data */
1175 addLiteral (s, len);
1176}
1177
1178void
1179ESIContext::parserComment (const char *s)
1180{
1181 if (failed())
1182 return;
1183
1184 if (!strncmp(s, "esi",3)) {
bf8fe701 1185 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block encountered");
43ae1d95 1186 ESIParser::Pointer tempParser = ESIParser::NewParser (this);
1187
1188 /* wrap the comment in some tags */
1189
1190 if (!tempParser->parse("<div>", 5,0) ||
1191 !tempParser->parse(s + 3, strlen(s) - 3, 0) ||
1192 !tempParser->parse("</div>",6,1)) {
bf8fe701 1193 debugs(86, 0, "ESIContext::parserComment: Parsing fragment '" << s + 3 << "' failed.");
43ae1d95 1194 setError();
1195 char tempstr[1024];
137a13ea 1196 snprintf(tempstr, 1023, "ESIContext::parserComment: Parse error at line %ld:\n%s\n",
43ae1d95 1197 tempParser->lineNumber(),
1198 tempParser->errorString());
bf8fe701 1199 debugs(86, 0, "" << tempstr << "");
43ae1d95 1200
924f73bc 1201 setErrorMessage(tempstr);
43ae1d95 1202 }
1203
bf8fe701 1204 debugs(86, 5, "ESIContext::parserComment: ESI <!-- block parsed");
43ae1d95 1205 return;
1206 } else {
1207 char localbuf [HTTP_REQBUF_SZ];
1208 unsigned int len;
bf8fe701 1209 debugs(86, 5, "ESIContext::parserComment: Regenerating comment block");
43ae1d95 1210 len = strlen (s);
1211
1212 if (len > sizeof (localbuf) - 9) {
bf8fe701 1213 debugs(86, 0, "ESIContext::parserComment: Truncating long comment");
43ae1d95 1214 len = sizeof (localbuf) - 9;
1215 }
1216
1217 xstrncpy(localbuf, "<!--", 5);
1218 xstrncpy(localbuf + 4, s, len + 1);
1219 xstrncpy(localbuf + 4 + len, "-->", 4);
1220 addLiteral (localbuf,len + 7);
1221 }
1222}
1223
1224void
1225ESIContext::addLiteral (const char *s, int len)
1226{
1227 /* handle any skipped data */
1228 assert (len);
bf8fe701 1229 debugs(86, 5, "literal length is " << len);
43ae1d95 1230 /* give a literal to the current element */
1231 assert (parserState.stackdepth <11);
1232 ESIElement::Pointer element (new esiLiteral (this, s, len));
1233
1234 if (!parserState.top()->addElement(element)) {
bf8fe701 1235 debugs(86, 1, "ESIContext::addLiteral: failed to add esi node, probable error in ESI template");
43ae1d95 1236 flags.error = 1;
1237 }
1238}
1239
1240void
1241ESIContext::ParserState::init(ESIParserClient *userData)
1242{
1243 theParser = ESIParser::NewParser (userData);
1244 inited_ = true;
1245}
1246
1247void
1248ESIContext::parseOneBuffer()
1249{
1250 assert (buffered.getRaw());
1251
137a13ea 1252 debugs (86,9,"ESIContext::parseOneBuffer: " << buffered->len << " bytes");
43ae1d95 1253 bool lastBlock = buffered->next.getRaw() == NULL && flags.finishedtemplate ? true : false;
1254
1255 if (! parserState.theParser->parse(buffered->buf, buffered->len, lastBlock)) {
1256 setError();
1257 char tempstr[1024];
137a13ea 1258 snprintf (tempstr, 1023, "esiProcess: Parse error at line %ld:\n%s\n",
43ae1d95 1259 parserState.theParser->lineNumber(),
1260 parserState.theParser->errorString());
bf8fe701 1261 debugs(86, 0, "" << tempstr << "");
43ae1d95 1262
924f73bc 1263 setErrorMessage(tempstr);
43ae1d95 1264
1265 assert (flags.error);
1266
1267 return;
1268 }
1269
1270 if (flags.error) {
1271 setError();
1272 return;
1273 }
1274
1275 ESISegment::Pointer temp = buffered;
1276 buffered = temp->next;
1277}
1278
1279void
1280ESIContext::parse()
1281{
1282 if (!parserState.stackdepth) {
bf8fe701 1283 debugs(86, 5, "empty parser stack, inserting the top level node");
43ae1d95 1284 assert (tree.getRaw());
1285 parserState.stack[parserState.stackdepth++] = tree;
1286 }
1287
1288 if (rep && !parserState.inited())
1289 parserState.init(this);
1290
1291 /* we have data */
1292 if (buffered.getRaw()) {
1293 parserState.parsing = 1;
1294 /* we don't keep any data around */
1295
1296 PROF_start(esiParsing);
1297
1298 while (buffered.getRaw() && !flags.error)
1299 parseOneBuffer();
1300
1301 PROF_stop(esiParsing);
1302
1303 /* Tel the read code to allocate a new buffer */
1304 incoming = NULL;
1305
1306 parserState.parsing = 0;
1307 }
1308}
1309
1310esiProcessResult_t
1311ESIContext::process ()
1312{
1313 /* parsing:
1314 * read through buffered, skipping plain text, and skipping any
1315 * <...> entry that is not an <esi: entry.
1316 * when it's found, hand an esiLiteral of the preceeding data to our current
1317 * context
1318 */
1319
1320 if (parserState.parsing) {
1321 /* in middle of parsing - finish here */
1322 return ESI_PROCESS_PENDING_MAYFAIL;
1323 }
1324
1325 assert (flags.finished == 0);
1326
1327 assert (!flags.error);
1328
1329 if (!hasCachedAST())
1330 parse();
1331 else if (!flags.finishedtemplate)
1332 getCachedAST();
1333
1334 if (flags.error) {
bf8fe701 1335 debugs(86, 5, "ESIContext::process: Parsing failed");
43ae1d95 1336 finishChildren ();
1337 parserState.popAll();
1338 return ESI_PROCESS_FAILED;
1339 }
1340
1341 if (!flags.finishedtemplate && !incoming.getRaw() && !cachedASTInUse) {
1342 buffered = new ESISegment;
1343 incoming = buffered;
1344 }
1345
1346 if (!flags.finishedtemplate && !cachedASTInUse) {
1347 return ESI_PROCESS_PENDING_MAYFAIL;
1348 }
1349
1350 assert (flags.finishedtemplate || cachedASTInUse);
1351 updateCachedAST();
1352 /* ok, we've done all we can with the data. What can we process now?
1353 */
1354 {
1355 esiProcessResult_t status;
1356 PROF_start(esiProcessing);
1357 processing = true;
1358 status = tree->process(0);
1359 processing = false;
1360
26ac0430 1361 switch (status) {
43ae1d95 1362
1363 case ESI_PROCESS_COMPLETE:
bf8fe701 1364 debugs(86, 5, "esiProcess: tree Processed OK");
43ae1d95 1365 break;
1366
1367 case ESI_PROCESS_PENDING_WONTFAIL:
bf8fe701 1368 debugs(86, 5, "esiProcess: tree Processed PENDING OK");
43ae1d95 1369 break;
1370
1371 case ESI_PROCESS_PENDING_MAYFAIL:
bf8fe701 1372 debugs(86, 5, "esiProcess: tree Processed PENDING UNKNOWN");
43ae1d95 1373 break;
1374
1375 case ESI_PROCESS_FAILED:
bf8fe701 1376 debugs(86, 0, "esiProcess: tree Processed FAILED");
43ae1d95 1377 setError();
1378
924f73bc 1379 setErrorMessage("esiProcess: ESI template Processing failed.");
43ae1d95 1380
1381 PROF_stop(esiProcessing);
1382
1383 return ESI_PROCESS_FAILED;
1384
1385 break;
1386 }
1387
26ac0430 1388 if (status != ESI_PROCESS_PENDING_MAYFAIL && (flags.finishedtemplate || cachedASTInUse)) {
43ae1d95 1389 /* We've read the entire template, and no nodes will
1390 * return failure
1391 */
bf8fe701 1392 debugs(86, 5, "esiProcess, request will succeed");
43ae1d95 1393 flags.oktosend = 1;
1394 }
1395
1396 if (status == ESI_PROCESS_COMPLETE
26ac0430 1397 && (flags.finishedtemplate || cachedASTInUse)) {
43ae1d95 1398 /* we've finished all processing. Render and send. */
bf8fe701 1399 debugs(86, 5, "esiProcess, processing complete");
43ae1d95 1400 flags.finished = 1;
1401 }
1402
1403 PROF_stop(esiProcessing);
1404 return status; /* because we have no callbacks */
1405 }
1406}
1407
1408void
1409ESIContext::ParserState::freeResources()
1410{
1411 theParser = NULL;
1412 inited_ = false;
1413}
1414
1415void
1416ESIContext::ParserState::popAll()
1417{
1418 while (stackdepth)
1419 stack[--stackdepth] = NULL;
1420}
1421
1422void
1423ESIContext::freeResources ()
1424{
b9fde3d8 1425 debugs(86, 5, HERE << "Freeing for this=" << this);
43ae1d95 1426
b9fde3d8 1427 HTTPMSGUNLOCK(rep);
43ae1d95 1428
1429 finishChildren ();
1430
1431 if (parserState.inited()) {
1432 parserState.freeResources();
1433 }
1434
1435 parserState.popAll();
1436 ESISegmentFreeList (buffered);
1437 ESISegmentFreeList (outbound);
1438 ESISegmentFreeList (outboundtail);
00d77d6b 1439 delete varState;
6269c7b1 1440 varState=NULL;
43ae1d95 1441 /* don't touch incoming, it's a pointer into buffered anyway */
1442}
1443
ad61a2b4 1444extern ErrorState *clientBuildError (err_type, http_status, char const *, IpAddress &, HttpRequest *);
43ae1d95 1445
1446
1447/* This can ONLY be used before we have sent *any* data to the client */
1448void
1449ESIContext::fail ()
1450{
bf8fe701 1451 debugs(86, 5, "ESIContext::fail: this=" << this);
43ae1d95 1452 /* check preconditions */
1453 assert (pos == 0);
1454 /* cleanup current state */
1455 freeResources ();
1456 /* Stop altering thisNode request */
1457 flags.oktosend = 1;
1458 flags.finished = 1;
1459 /* don't honour range requests - for errors we send it all */
1460 flags.error = 1;
1461 /* create an error object */
cc192b50 1462 ErrorState * err = clientBuildError(errorpage, errorstatus, NULL, http->getConn()->peer, http->request);
43ae1d95 1463 err->err_msg = errormessage;
1464 errormessage = NULL;
c70281f8 1465 rep = err->BuildHttpReply();
952f6361 1466 assert (rep->body.mb->contentSize() >= 0);
1467 size_t errorprogress = rep->body.mb->contentSize();
43ae1d95 1468 /* Tell esiSend where to start sending from */
1469 outbound_offset = 0;
1470 /* copy the membuf from the reply to outbound */
1471
952f6361 1472 while (errorprogress < (size_t)rep->body.mb->contentSize()) {
43ae1d95 1473 appendOutboundData(new ESISegment);
952f6361 1474 errorprogress += outboundtail->append(rep->body.mb->content() + errorprogress, rep->body.mb->contentSize() - errorprogress);
43ae1d95 1475 }
1476
1477 /* the esiCode now thinks that the error is the outbound,
1478 * and all processing has finished. */
1479 /* Send as much as we can */
1480 send ();
1481
1482 /* don't cancel anything. The stream nodes will clean up after
26ac0430 1483 * themselves when the reply is freed - and we don't know what to
43ae1d95 1484 * clean anyway.
1485 */
1486}
1487
43ae1d95 1488/* Implementation of ESIElements */
1489
1490/* esiComment */
1491esiComment::~esiComment()
1492{
bf8fe701 1493 debugs(86, 5, "esiComment::~esiComment " << this);
43ae1d95 1494}
1495
43ae1d95 1496esiComment::esiComment()
1497{}
1498
1499void
1500esiComment::finish()
1501{}
1502
1503void
1504esiComment::render(ESISegment::Pointer output)
1505{
1506 /* Comments do nothing dude */
bf8fe701 1507 debugs(86, 5, "esiCommentRender: Rendering comment " << this << " into " << output.getRaw());
43ae1d95 1508}
1509
1510ESIElement::Pointer
1511esiComment::makeCacheable() const
1512{
bf8fe701 1513 debugs(86, 5, "esiComment::makeCacheable: returning NULL");
43ae1d95 1514 return NULL;
1515}
1516
1517ESIElement::Pointer
924f73bc 1518esiComment::makeUsable(esiTreeParentPtr, ESIVarState &) const
43ae1d95 1519{
1520 fatal ("esiComment::Usable: unreachable code!\n");
1521 return NULL;
1522}
1523
1524/* esiLiteral */
43ae1d95 1525esiLiteral::~esiLiteral()
1526{
bf8fe701 1527 debugs(86, 5, "esiLiteral::~esiLiteral: " << this);
43ae1d95 1528 ESISegmentFreeList (buffer);
1529 cbdataReferenceDone (varState);
1530}
1531
1532esiLiteral::esiLiteral(ESISegment::Pointer aSegment)
1533{
1534 buffer = aSegment;
1535 /* we've been handed a complete, processed string */
1536 varState = NULL;
1537 /* Nothing to do */
1538 flags.donevars = 1;
1539}
1540
1541void
1542esiLiteral::finish()
1543{}
1544
1545/* precondition: the buffer chain has at least start + length bytes of data
1546 */
1547esiLiteral::esiLiteral(ESIContext *context, const char *s, int numberOfCharacters)
1548{
1549 assert (s);
1550 buffer = new ESISegment;
1551 ESISegment::Pointer local = buffer;
57d55dfa 1552 size_t start = 0;
43ae1d95 1553 int remainingCharacters = numberOfCharacters;
1554
1555 while (remainingCharacters > 0) {
1556 if (local->len == sizeof (local->buf)) {
1557 local->next = new ESISegment;
1558 local=local->next;
1559 }
1560
1561 size_t len = local->append (&s[start], remainingCharacters);
1562 start += len;
1563 remainingCharacters -= len;
1564 }
1565
1566 varState = cbdataReference (context->varState);
1567}
1568
1569void
1570esiLiteral::render (ESISegment::Pointer output)
1571{
bf8fe701 1572 debugs(86, 9, "esiLiteral::render: Rendering " << this);
43ae1d95 1573 /* append the entire chain */
1574 assert (output->next.getRaw() == NULL);
1575 output->next = buffer;
1576 buffer = NULL;
1577}
1578
1579esiProcessResult_t
1580esiLiteral::process (int dovars)
1581{
1582 if (flags.donevars)
1583 return ESI_PROCESS_COMPLETE;
1584
1585 if (dovars) {
1586 ESISegment::Pointer temp = buffer;
1587 /* Ensure variable state is clean */
1588
1589 while (temp.getRaw()) {
6c3b212b 1590 varState->feedData(temp->buf,temp->len);
43ae1d95 1591 temp = temp->next;
1592 }
1593
1594 /* free the pre-processed content */
1595 ESISegmentFreeList (buffer);
1596
1597 buffer = varState->extractList ();
1598 }
1599
1600 flags.donevars = 1;
1601 return ESI_PROCESS_COMPLETE;
1602}
1603
1604esiLiteral::esiLiteral(esiLiteral const &old) : buffer (old.buffer->cloneList()),
1605 varState (NULL)
1606{
1607 flags.donevars = 0;
1608}
1609
1610ESIElement::Pointer
1611esiLiteral::makeCacheable() const
1612{
1613 return new esiLiteral (*this);
1614}
1615
1616ESIElement::Pointer
924f73bc 1617esiLiteral::makeUsable(esiTreeParentPtr , ESIVarState &newVarState) const
43ae1d95 1618{
bf8fe701 1619 debugs(86, 5, "esiLiteral::makeUsable: Creating usable literal");
43ae1d95 1620 esiLiteral * result = new esiLiteral (*this);
1621 result->varState = cbdataReference (&newVarState);
1622 return result;
1623}
1624
924f73bc 1625/* esiRemove */
43ae1d95 1626void
924f73bc 1627esiRemoveFree (void *data)
43ae1d95 1628{
924f73bc 1629 esiRemove *thisNode = (esiRemove *)data;
bf8fe701 1630 debugs(86, 5, "esiRemoveFree " << thisNode);
43ae1d95 1631}
1632
1633void *
924f73bc 1634esiRemove::operator new(size_t byteCount)
43ae1d95 1635{
924f73bc 1636 assert (byteCount == sizeof (esiRemove));
1637 void *rv;
1638 CBDATA_INIT_TYPE_FREECB(esiRemove, esiRemoveFree);
1639 rv = (void *)cbdataAlloc (esiRemove);
1640 return rv;
43ae1d95 1641}
1642
1643void
924f73bc 1644esiRemove::operator delete (void *address)
43ae1d95 1645{
924f73bc 1646 cbdataFree (address);
43ae1d95 1647}
1648
924f73bc 1649ESIElement *
1650esiRemoveNew ()
43ae1d95 1651{
924f73bc 1652 return new esiRemove;
43ae1d95 1653}
1654
924f73bc 1655esiRemove::esiRemove()
1656{}
43ae1d95 1657
924f73bc 1658void
1659esiRemove::finish()
1660{}
43ae1d95 1661
924f73bc 1662void
1663esiRemove::render(ESISegment::Pointer output)
1664{
1665 /* Removes do nothing dude */
bf8fe701 1666 debugs(86, 5, "esiRemoveRender: Rendering remove " << this);
43ae1d95 1667}
1668
924f73bc 1669/* Accept non-ESI children */
1670bool
1671esiRemove::addElement (ESIElement::Pointer element)
43ae1d95 1672{
924f73bc 1673 if (!dynamic_cast<esiLiteral*>(element.getRaw())) {
bf8fe701 1674 debugs(86, 5, "esiRemoveAdd: Failed for " << this);
924f73bc 1675 return false;
1676 }
43ae1d95 1677
924f73bc 1678 return true;
43ae1d95 1679}
1680
924f73bc 1681ESIElement::Pointer
1682esiRemove::makeCacheable() const
43ae1d95 1683{
bf8fe701 1684 debugs(86, 5, "esiRemove::makeCacheable: Returning NULL");
924f73bc 1685 return NULL;
1686}
43ae1d95 1687
924f73bc 1688ESIElement::Pointer
1689esiRemove::makeUsable(esiTreeParentPtr, ESIVarState &) const
1690{
1691 fatal ("esiRemove::Usable: unreachable code!\n");
1692 return NULL;
1693}
43ae1d95 1694
924f73bc 1695/* esiTry */
1696esiTry::~esiTry()
1697{
bf8fe701 1698 debugs(86, 5, "esiTry::~esiTry " << this);
924f73bc 1699}
43ae1d95 1700
43ae1d95 1701esiTry::esiTry(esiTreeParentPtr aParent) : parent (aParent) , exceptbuffer(NULL)
1702{}
1703
1704void
1705esiTry::render (ESISegment::Pointer output)
1706{
1707 /* Try renders from it's children */
1708 assert (this);
1709 assert (attempt.getRaw());
1710 assert (except.getRaw());
bf8fe701 1711 debugs(86, 5, "esiTryRender: Rendering Try " << this);
43ae1d95 1712
1713 if (flags.attemptok) {
1714 attempt->render(output);
1715 } else if (flags.exceptok) {
1716 /* prerendered */
1717
1718 if (exceptbuffer.getRaw())
1719 ESISegment::ListTransfer(exceptbuffer, output);
1720 else
1721 except->render(output);
1722 } else
bf8fe701 1723 debugs(86, 5, "esiTryRender: Neither except nor attempt succeeded?!?");
43ae1d95 1724}
1725
1726/* Accept attempt and except only */
1727bool
1728esiTry::addElement(ESIElement::Pointer element)
1729{
bf8fe701 1730 debugs(86, 5, "esiTryAdd: Try " << this << " adding element " <<
1731 element.getRaw());
43ae1d95 1732
1733 if (dynamic_cast<esiLiteral*>(element.getRaw())) {
1734 /* Swallow whitespace */
bf8fe701 1735 debugs(86, 5, "esiTryAdd: Try " << this << " skipping whitespace " << element.getRaw());
43ae1d95 1736 return true;
1737 }
1738
1739 if (dynamic_cast<esiAttempt*>(element.getRaw())) {
1740 if (attempt.getRaw()) {
bf8fe701 1741 debugs(86, 1, "esiTryAdd: Failed for " << this << " - try allready has an attempt node (section 3.4)");
43ae1d95 1742 return false;
1743 }
1744
1745 attempt = element;
1746 return true;
1747 }
1748
1749 if (dynamic_cast<esiExcept*>(element.getRaw())) {
1750 if (except.getRaw()) {
bf8fe701 1751 debugs(86, 1, "esiTryAdd: Failed for " << this << " - try already has an except node (section 3.4)");
43ae1d95 1752 return false;
1753 }
1754
1755 except = element;
1756 return true;
1757 }
1758
bf8fe701 1759 debugs(86, 1, "esiTryAdd: Failed to add element " << element.getRaw() << " to try " << this << ", incorrect element type (see section 3.4)");
43ae1d95 1760 return false;
1761}
1762
1763esiProcessResult_t
1764esiTry::bestAttemptRV() const
1765{
1766 if (flags.attemptfailed)
1767 return ESI_PROCESS_COMPLETE;
1768 else
1769 return ESI_PROCESS_PENDING_MAYFAIL;
1770}
1771
1772esiProcessResult_t
1773esiTry::process (int dovars)
1774{
1775 esiProcessResult_t rv = ESI_PROCESS_PENDING_MAYFAIL;
1776 assert (this);
1777
1778 if (!attempt.getRaw()) {
bf8fe701 1779 debugs(86, 0, "esiTryProcess: Try has no attempt element - ESI template is invalid (section 3.4)");
43ae1d95 1780 return ESI_PROCESS_FAILED;
1781 }
1782
1783 if (!except.getRaw()) {
bf8fe701 1784 debugs(86, 0, "esiTryProcess: Try has no except element - ESI template is invalid (section 3.4)");
43ae1d95 1785 return ESI_PROCESS_FAILED;
1786 }
1787
1788 if (!flags.attemptfailed)
1789 /* Try the attempt branch */
1790 switch ((rv = attempt->process(dovars))) {
1791
1792 case ESI_PROCESS_COMPLETE:
bf8fe701 1793 debugs(86, 5, "esiTryProcess: attempt Processed OK");
43ae1d95 1794 flags.attemptok = 1;
1795 return ESI_PROCESS_COMPLETE;
1796
1797 case ESI_PROCESS_PENDING_WONTFAIL:
bf8fe701 1798 debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
43ae1d95 1799 /* We're not done yet, but don't need to test except */
1800 return ESI_PROCESS_PENDING_WONTFAIL;
1801
1802 case ESI_PROCESS_PENDING_MAYFAIL:
bf8fe701 1803 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
43ae1d95 1804 break;
1805
1806 case ESI_PROCESS_FAILED:
bf8fe701 1807 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
43ae1d95 1808 flags.attemptfailed = 1;
1809 break;
1810 }
1811
1812 /* attempt is either MAYFAIL or FAILED */
1813 if (flags.exceptok)
1814 return bestAttemptRV();
1815
1816 /* query except to see if it has a definite result */
1817 if (!flags.exceptfailed)
1818 /* Try the except branch */
1819 switch (except->process(dovars)) {
1820
1821 case ESI_PROCESS_COMPLETE:
bf8fe701 1822 debugs(86, 5, "esiTryProcess: except Processed OK");
43ae1d95 1823 flags.exceptok = 1;
1824 return bestAttemptRV();
1825
1826 case ESI_PROCESS_PENDING_WONTFAIL:
bf8fe701 1827 debugs(86, 5, "esiTryProcess: attempt Processed PENDING OK");
43ae1d95 1828 /* We're not done yet, but can't fail */
1829 return ESI_PROCESS_PENDING_WONTFAIL;
1830
1831 case ESI_PROCESS_PENDING_MAYFAIL:
bf8fe701 1832 debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
43ae1d95 1833 /* The except branch fail fail */
1834 return ESI_PROCESS_PENDING_MAYFAIL;
1835
1836 case ESI_PROCESS_FAILED:
bf8fe701 1837 debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
43ae1d95 1838 flags.exceptfailed = 1;
1839 break;
1840 }
1841
1842 if (flags.exceptfailed && flags.attemptfailed)
1843 return ESI_PROCESS_FAILED;
1844
1845 /* one of attempt or except returned PENDING MAYFAIL */
1846 return ESI_PROCESS_PENDING_MAYFAIL;
1847}
1848
1849void
1850esiTry::notifyParent()
1851{
1852 if (flags.attemptfailed) {
1853 if (flags.exceptok) {
1854 parent->provideData (exceptbuffer, this);
1855 exceptbuffer = NULL;
1856 } else if (flags.exceptfailed || except.getRaw() == NULL) {
924f73bc 1857 parent->fail (this, "esi:try - except claused failed, or no except clause found");
43ae1d95 1858 }
1859 }
1860
1861 /* nothing to do when except fails and attempt hasn't */
1862}
1863
1864void
924f73bc 1865esiTry::fail(ESIElement *source, char const *anError)
43ae1d95 1866{
1867 assert (source);
1868 assert (source == attempt || source == except);
bf8fe701 1869 debugs(86, 5, "esiTry::fail: this=" << this << ", source=" << source << ", message=" << anError);
43ae1d95 1870
1871 if (source == except) {
1872 flags.exceptfailed = 1;
1873 } else {
1874 flags.attemptfailed = 1;
1875 }
1876
1877 notifyParent();
1878}
1879
1880void
1881esiTry::provideData (ESISegment::Pointer data, ESIElement* source)
1882{
1883 if (source == attempt) {
1884 flags.attemptok = 1;
1885 parent->provideData (data, this);
1886 } else if (source == except) {
1887 flags.exceptok = 1;
1888 assert (exceptbuffer == NULL);
1889 ESISegment::ListTransfer (data, exceptbuffer);
1890 notifyParent();
1891 }
1892}
1893
1894esiTry::esiTry(esiTry const &old)
1895{
1896 attempt = NULL;
1897 except = NULL;
1898 flags.attemptok = 0;
1899 flags.exceptok = 0;
1900 flags.attemptfailed = 0;
1901 flags.exceptfailed = 0;
1902 parent = NULL;
1903 exceptbuffer = NULL;
1904}
1905
1906ESIElement::Pointer
1907esiTry::makeCacheable() const
1908{
bf8fe701 1909 debugs(86, 5, "esiTry::makeCacheable: making cachable Try from " << this);
43ae1d95 1910 esiTry *resultT = new esiTry (*this);
1911 ESIElement::Pointer result = resultT;
1912
1913 if (attempt.getRaw())
1914 resultT->attempt = attempt->makeCacheable();
1915
1916 if (except.getRaw())
1917 resultT->except = except->makeCacheable();
1918
1919 return result;
1920}
1921
1922ESIElement::Pointer
924f73bc 1923esiTry::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
43ae1d95 1924{
bf8fe701 1925 debugs(86, 5, "esiTry::makeUsable: making usable Try from " << this);
43ae1d95 1926 esiTry *resultT = new esiTry (*this);
1927 ESIElement::Pointer result = resultT;
1928
1929 resultT->parent = newParent;
1930
1931 if (attempt.getRaw())
1932 resultT->attempt = attempt->makeUsable(resultT, newVarState);
1933
1934 if (except.getRaw())
1935 resultT->except = except->makeUsable(resultT, newVarState);
1936
1937 return result;
1938}
1939
1940void
1941esiTry::finish()
1942{
1943 parent = NULL;
1944
1945 if (attempt.getRaw())
1946 attempt->finish();
1947
1948 attempt = NULL;
1949
1950 if (except.getRaw())
1951 except->finish();
1952
1953 except = NULL;
1954}
1955
1956/* esiAttempt */
1957#if 0
1958void *
1959esiAttempt::operator new(size_t byteCount)
1960{
1961 assert (byteCount == sizeof (esiAttempt));
1962
1963}
1964
1965void
1966esiAttempt::operator delete (void *address)
1967{
101a3d6f 1968 cbdataFree (address);
43ae1d95 1969}
1970
1971#endif
43ae1d95 1972
1973/* esiExcept */
1974#if 0
1975void *
1976esiExcept::operator new(size_t byteCount)
1977{
1978 assert (byteCount == sizeof (esiExcept));
1979 void *rv;
1980 CBDATA_INIT_TYPE_FREECB(esiExcept, esiSequence::Free);
1981 rv = (void *)cbdataAlloc (esiExcept);
1982 return rv;
1983}
1984
1985void
1986esiExcept::operator delete (void *address)
1987{
101a3d6f 1988 cbdataFree (address);
43ae1d95 1989}
1990
1991#endif
43ae1d95 1992
924f73bc 1993/* ESIVar */
43ae1d95 1994#if 0
1995void *
1996esiVar::operator new(size_t byteCount)
1997{
1998 assert (byteCount == sizeof (esiVar));
1999 void *rv;
2000 CBDATA_INIT_TYPE_FREECB(esiVar, esiSequence::Free);
2001 rv = (void *)cbdataAlloc (esiVar);
2002 return rv;
2003}
2004
2005void
2006esiVar::operator delete (void *address)
2007{
101a3d6f 2008 cbdataFree (address);
43ae1d95 2009}
2010
2011#endif
2012
43ae1d95 2013/* esiChoose */
2014esiChoose::~esiChoose()
2015{
bf8fe701 2016 debugs(86, 5, "esiChoose::~esiChoose " << this);
43ae1d95 2017}
2018
43ae1d95 2019esiChoose::esiChoose(esiTreeParentPtr aParent) : elements (), chosenelement (-1),parent (aParent)
2020{}
2021
2022void
2023esiChoose::render(ESISegment::Pointer output)
2024{
2025 /* append all processed elements, and trim processed and rendered elements */
2026 assert (output->next == NULL);
2027 assert (elements.size() || otherwise.getRaw());
bf8fe701 2028 debugs(86, 5, "esiChooseRender: rendering");
43ae1d95 2029
2030 if (chosenelement >= 0)
2031 elements[chosenelement]->render(output);
2032 else if (otherwise.getRaw())
2033 otherwise->render(output);
2034}
2035
2036bool
2037esiChoose::addElement(ESIElement::Pointer element)
2038{
2039 /* add an element to the output list */
2040
2041 if (dynamic_cast<esiLiteral*>(element.getRaw())) {
2042 /* Swallow whitespace */
bf8fe701 2043 debugs(86, 5, "esiChooseAdd: Choose " << this << " skipping whitespace " << element.getRaw());
43ae1d95 2044 return true;
2045 }
2046
2047 /* Some elements require specific parents */
2048 if (!(dynamic_cast<esiWhen*>(element.getRaw()) || dynamic_cast<esiOtherwise*>(element.getRaw()))) {
bf8fe701 2049 debugs(86, 0, "esiChooseAdd: invalid child node for esi:choose (section 3.3)");
43ae1d95 2050 return false;
2051 }
2052
2053 if (dynamic_cast<esiOtherwise*>(element.getRaw())) {
2054 if (otherwise.getRaw()) {
bf8fe701 2055 debugs(86, 0, "esiChooseAdd: only one otherwise node allowed for esi:choose (section 3.3)");
43ae1d95 2056 return false;
2057 }
2058
2059 otherwise = element;
2060 } else {
2061 elements.push_back (element);
2062
137a13ea 2063 debugs (86,3, "esiChooseAdd: Added a new element, elements = " << elements.size());
43ae1d95 2064
2065 if (chosenelement == -1)
2066 if ((dynamic_cast<esiWhen *>(element.getRaw()))->
2067 testsTrue()) {
2068 chosenelement = elements.size() - 1;
137a13ea 2069 debugs (86,3, "esiChooseAdd: Chose element " << elements.size());
43ae1d95 2070 }
2071 }
2072
2073 return true;
2074}
2075
2076void
2077esiChoose::selectElement()
2078{
2079 if (chosenelement > -1)
2080 return;
2081
2082 for (size_t counter = 0; counter < elements.size(); ++counter) {
2083 if ((dynamic_cast<esiWhen *>(elements[counter].getRaw()))->
2084 testsTrue()) {
2085 chosenelement = counter;
137a13ea 2086 debugs (86,3, "esiChooseAdd: Chose element " << counter + 1);
43ae1d95 2087 return;
2088 }
2089 }
2090}
2091
2092void
2093esiChoose::finish()
2094{
2095 elements.setNULL(0, elements.size());
2096
2097 if (otherwise.getRaw())
2098 otherwise->finish();
2099
2100 otherwise = NULL;
2101
2102 parent = NULL;
2103}
2104
2105void
2106ElementList::setNULL (int start, int end)
2107{
2108 assert (start >= 0 && start <= elementcount);
2109 assert (end >= 0 && end <= elementcount);
2110
2111 for (int loopPosition = start; loopPosition < end; ++loopPosition) {
2112 if (elements[loopPosition].getRaw())
2113 elements[loopPosition]->finish();
2114
26ac0430
AJ
2115 debugs(86, 5, "esiSequence::NULLElements: Setting index " <<
2116 loopPosition << ", pointer " <<
2117 elements[loopPosition].getRaw() << " to NULL");
43ae1d95 2118
2119 elements[loopPosition] = NULL;
2120 }
2121}
2122
2123void
2124esiChoose::NULLUnChosen()
2125{
2126 if (chosenelement >= 0) {
2127 if (otherwise.getRaw())
2128 otherwise->finish();
2129
2130 otherwise = NULL;
2131
2132 elements.setNULL (0, chosenelement);
2133
2134 elements.setNULL (chosenelement + 1, elements.size());
2135 } else if (otherwise.getRaw()) {
2136 elements.setNULL (0, elements.size());
2137 }
2138}
2139
2140esiProcessResult_t
2141esiChoose::process (int dovars)
2142{
2143 /* process as much of the list as we can, stopping only on
2144 * faliures
2145 */
2146 /* We MUST have a when clause */
2147 NULLUnChosen();
2148
2149 if (!elements.size()) {
2150 parent->fail(this);
2151
2152 if (otherwise.getRaw())
2153 otherwise->finish();
2154
2155 otherwise = NULL;
2156
2157 parent = NULL;
2158
2159 return ESI_PROCESS_FAILED;
2160 }
2161
2162 if (chosenelement >= 0) {
2163 return elements[chosenelement]->process(dovars);
2164 } else if (otherwise.getRaw())
2165 return otherwise->process(dovars);
2166 else
2167 return ESI_PROCESS_COMPLETE;
2168}
2169
2170void
2171esiChoose::checkValidSource (ESIElement::Pointer source) const
2172{
2173 if (!elements.size())
2174 fatal ("invalid callback = no when clause\n");
2175
2176 if (chosenelement >= 0)
2177 assert (source == elements[chosenelement]);
2178 else if (otherwise.getRaw())
2179 assert (source == otherwise);
2180 else
2181 fatal ("esiChoose::checkValidSource: invalid callback - no elements chosen\n");
2182}
2183
2184void
924f73bc 2185esiChoose::fail(ESIElement * source, char const *anError)
43ae1d95 2186{
2187 checkValidSource (source);
2188 elements.setNULL (0, elements.size());
2189
2190 if (otherwise.getRaw())
2191 otherwise->finish();
2192
2193 otherwise = NULL;
2194
924f73bc 2195 parent->fail(this, anError);
43ae1d95 2196
2197 parent = NULL;
2198}
2199
2200void
2201esiChoose::provideData (ESISegment::Pointer data, ESIElement*source)
2202{
2203 checkValidSource (source);
2204 parent->provideData (data, this);
2205}
2206
2207
2208esiChoose::esiChoose(esiChoose const &old) : chosenelement(-1), otherwise (NULL), parent (NULL)
2209{
2210 for (size_t counter = 0; counter < old.elements.size(); ++counter) {
2211 ESIElement::Pointer newElement = old.elements[counter]->makeCacheable();
2212
2213 if (newElement.getRaw())
2214 assert (addElement(newElement));
2215 }
2216}
2217
2218void
2219esiChoose::makeCachableElements(esiChoose const &old)
2220{
2221 for (size_t counter = 0; counter < old.elements.size(); ++counter) {
2222 ESIElement::Pointer newElement = old.elements[counter]->makeCacheable();
2223
2224 if (newElement.getRaw())
2225 assert (addElement(newElement));
2226 }
2227}
2228
2229void
924f73bc 2230esiChoose::makeUsableElements(esiChoose const &old, ESIVarState &newVarState)
43ae1d95 2231{
2232 for (size_t counter = 0; counter < old.elements.size(); ++counter) {
2233 ESIElement::Pointer newElement = old.elements[counter]->makeUsable (this, newVarState);
2234
2235 if (newElement.getRaw())
2236 assert (addElement(newElement));
2237 }
2238}
2239
2240ESIElement::Pointer
2241esiChoose::makeCacheable() const
2242{
2243 esiChoose *resultC = new esiChoose (*this);
2244 ESIElement::Pointer result = resultC;
2245 resultC->makeCachableElements(*this);
2246
2247 if (otherwise.getRaw())
2248 resultC->otherwise = otherwise->makeCacheable();
2249
2250 return result;
2251}
2252
2253ESIElement::Pointer
924f73bc 2254esiChoose::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
43ae1d95 2255{
2256 esiChoose *resultC = new esiChoose (*this);
2257 ESIElement::Pointer result = resultC;
2258 resultC->parent = newParent;
2259 resultC->makeUsableElements(*this, newVarState);
2260 resultC->selectElement();
2261
2262 if (otherwise.getRaw())
2263 resultC->otherwise = otherwise->makeUsable(resultC, newVarState);
2264
2265 return result;
2266}
2267
2268/* ElementList */
2269ElementList::ElementList () : elements(NULL), allocedcount(0), allocedsize(0), elementcount (0)
2270{}
2271
2272ElementList::~ElementList()
2273{
bf8fe701 2274 debugs(86, 5, "ElementList::~ElementList " << this);
43ae1d95 2275 setNULL(0, elementcount);
2276
2277 if (elements)
2278 memFreeBuf (allocedsize, elements);
2279}
2280
2281ESIElement::Pointer &
2282ElementList::operator [] (int index)
2283{
2284 return elements[index];
2285}
2286
2287ESIElement::Pointer const &
2288ElementList::operator [] (int index) const
2289{
2290 return elements[index];
2291}
2292
2293void
2294ElementList::pop_front (size_t const count)
2295{
2296 if (!count)
2297 return;
2298
2299 xmemmove (elements, &elements[count], (elementcount - count) * sizeof (ESIElement::Pointer));
2300
2301 elementcount -= count;
2302}
2303
2304void
2305ElementList::push_back(ESIElement::Pointer &newElement)
2306{
2307 elements = (ESIElement::Pointer *)memReallocBuf (elements, ++elementcount * sizeof (ESIElement::Pointer),
2308 &allocedsize);
2309 assert (elements);
2310 allocedcount = elementcount;
2311 memset(&elements[elementcount - 1], '\0', sizeof (ESIElement::Pointer));
2312 elements[elementcount - 1] = newElement;
2313}
2314
2315size_t
2316ElementList::size() const
2317{
2318 return elementcount;
2319}
2320
2321/* esiWhen */
924f73bc 2322esiWhen::esiWhen (esiTreeParentPtr aParent, int attrcount, const char **attr,ESIVarState *aVar) : esiSequence (aParent)
43ae1d95 2323{
2324 varState = NULL;
2325 char const *expression = NULL;
2326
2327 for (int loopCounter = 0; loopCounter < attrcount && attr[loopCounter]; loopCounter += 2) {
2328 if (!strcmp(attr[loopCounter],"test")) {
2329 /* evaluate test */
bf8fe701 2330 debugs(86, 5, "esiWhen::esiWhen: Evaluating '" << attr[loopCounter+1] << "'");
43ae1d95 2331 /* TODO: warn the user instead of asserting */
2332 assert (expression == NULL);
2333 expression = attr[loopCounter+1];
2334 } else {
2335 /* ignore mistyped attributes.
2336 * TODO:? error on these for user feedback - config parameter needed
2337 */
26ac0430 2338 debugs(86, 1, "Found misttyped attribute on ESI When clause");
43ae1d95 2339 }
2340 }
2341
2342 /* No expression ? default is not matching */
2343 if (!expression)
2344 return;
2345
2346 unevaluatedExpression = xstrdup(expression);
2347
2348 varState = cbdataReference (aVar);
2349
2350 evaluate();
2351}
2352
2353esiWhen::~esiWhen()
2354{
2355 safe_free (unevaluatedExpression);
2356
2357 if (varState)
2358 cbdataReferenceDone (varState);
2359}
2360
2361void
2362esiWhen::evaluate()
2363{
2364 if (!unevaluatedExpression)
2365 return;
2366
2367 assert (varState);
2368
2369 varState->feedData(unevaluatedExpression, strlen (unevaluatedExpression));
2370
2371 char const *expression = varState->extractChar ();
2372
924f73bc 2373 setTestResult(ESIExpression::Evaluate (expression));
43ae1d95 2374
2375 safe_free (expression);
2376}
2377
2378esiWhen::esiWhen(esiWhen const &old) : esiSequence (old)
2379{
2380 unevaluatedExpression = NULL;
2381
2382 if (old.unevaluatedExpression)
2383 unevaluatedExpression = xstrdup(old.unevaluatedExpression);
2384
2385 varState = NULL;
2386}
2387
2388ESIElement::Pointer
2389esiWhen::makeCacheable() const
2390{
2391 return new esiWhen(*this);
2392}
2393
2394ESIElement::Pointer
924f73bc 2395esiWhen::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
43ae1d95 2396{
2397 esiWhen *resultW = new esiWhen (*this);
2398 ESIElement::Pointer result = resultW;
2399 resultW->parent = newParent;
2400 resultW->makeUsableElements(*this, newVarState);
2401 resultW->varState = cbdataReference (&newVarState);
2402 resultW->evaluate();
2403 return result;
2404}
2405
2406/* esiOtherwise */
2407#if 0
2408void *
2409esiOtherwise::operator new(size_t byteCount)
2410{
2411 assert (byteCount == sizeof (esiOtherwise));
2412 void *rv;
2413 CBDATA_INIT_TYPE_FREECB(esiOtherwise, esiSequence::Free);
2414 rv = (void *)cbdataAlloc (esiOtherwise);
2415 return rv;
2416}
2417
2418void
2419esiOtherwise::operator delete (void *address)
2420{
101a3d6f 2421 cbdataFree (address);
43ae1d95 2422}
2423
2424#endif
2425
43ae1d95 2426/* TODO: implement surrogate targeting and control processing */
2427int
2428esiEnableProcessing (HttpReply *rep)
2429{
2430 int rv = 0;
2431
4dd8a5fd 2432 if (rep->header.has(HDR_SURROGATE_CONTROL)) {
43ae1d95 2433 HttpHdrScTarget *sctusable = httpHdrScGetMergedTarget (rep->surrogate_control,
2434 Config.Accel.surrogate_id);
2435
2436 if (!sctusable || sctusable->content.size() == 0)
2437 /* Nothing generic or targeted at us, or no
26ac0430 2438 * content processing requested
43ae1d95 2439 */
2440 return 0;
2441
bb790702 2442 if (sctusable->content.pos("ESI/1.0") != NULL)
43ae1d95 2443 rv = 1;
2444
2445 httpHdrScTargetDestroy (sctusable);
2446 }
2447
2448 return rv;
2449}
2450
454e8283 2451#endif /* USE_SQUID_ESI == 1 */