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