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