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