1 /*
2  * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3  *
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.
7  */
8 
9 /* DEBUG: section 86    ESI processing */
10 
11 #include "squid.h"
12 #include "Debug.h"
13 #include "fatal.h"
14 
15 /* MS Visual Studio Projects are monolithic, so we need the following
16  * #if to exclude the ESI code from compile process when not needed.
17  */
18 #if (USE_SQUID_ESI == 1)
19 
20 #include "esi/Attempt.h"
21 #include "esi/Except.h"
22 #include "esi/Literal.h"
23 #include "esi/Sequence.h"
24 
25 class esiExcept;
26 
~esiSequence()27 esiSequence::~esiSequence ()
28 {
29     debugs(86, 5, "esiSequence::~esiSequence " << this);
30     FinishAllElements(elements); // finish if not already done
31 }
32 
esiSequence(esiTreeParentPtr aParent,bool incrementalFlag)33 esiSequence::esiSequence(esiTreeParentPtr aParent, bool incrementalFlag) :
34     elements(),
35     processedcount(0),
36     parent(aParent),
37     mayFail_(true),
38     failed(false),
39     provideIncrementalData(incrementalFlag),
40     processing(false),
41     processingResult(ESI_PROCESS_COMPLETE),
42     nextElementToProcess_(0)
43 {
44     memset(&flags, 0, sizeof(flags));
45 }
46 
47 size_t
nextElementToProcess() const48 esiSequence::nextElementToProcess() const
49 {
50     return nextElementToProcess_;
51 }
52 
53 void
nextElementToProcess(size_t const & aSizeT)54 esiSequence::nextElementToProcess(size_t const &aSizeT)
55 {
56     nextElementToProcess_ = aSizeT;
57 }
58 
59 bool
finishedProcessing() const60 esiSequence::finishedProcessing() const
61 {
62     return nextElementToProcess() >= elements.size();
63 }
64 
65 bool
mayFail() const66 esiSequence::mayFail () const
67 {
68     if (failed)
69         return true;
70 
71     return mayFail_;
72 }
73 
74 void
wontFail()75 esiSequence::wontFail()
76 {
77     assert (!failed);
78     mayFail_ = false;
79 }
80 
81 void
render(ESISegment::Pointer output)82 esiSequence::render(ESISegment::Pointer output)
83 {
84     /* append all processed elements, and trim processed
85      * and rendered elements
86      */
87     assert (output->next == NULL);
88     debugs (86,5, "esiSequenceRender: rendering " << processedcount << " elements");
89 
90     for (size_t i = 0; i < processedcount; ++i) {
91         elements[i]->render(output);
92         FinishAnElement(elements[i], i);
93         /* FIXME: pass a ESISegment ** ? */
94         output = output->tail();
95     }
96 
97     // prune completed elements
98     elements.erase(elements.begin(), elements.begin() + processedcount);
99     processedcount = 0;
100     assert (output->next == NULL);
101 }
102 
103 void
finish()104 esiSequence::finish()
105 {
106     debugs(86, 5, "esiSequence::finish: " << this << " is finished");
107     FinishAllElements(elements);
108     parent = NULL;
109 }
110 
111 void
provideData(ESISegment::Pointer data,ESIElement * source)112 esiSequence::provideData (ESISegment::Pointer data, ESIElement *source)
113 {
114     ESIElement::Pointer lockthis = this;
115 
116     if (processing)
117         debugs(86, 5, "esiSequence::provideData: " << this << " data provided during processing");
118     debugs(86, 5, "esiSequence::provideData " << this << " " << data.getRaw() << " " << source);
119 
120     /* when data is provided, the element *must* be completed */
121     /* XXX: when the callback model is complete,
122      * we can introduce 'finished'. And then this rule can be
123      * relaxed
124      */
125     /* find the index */
126     int index = elementIndex (source);
127 
128     assert (index >= 0);
129 
130     /* remove the current node */
131     FinishAnElement(elements[index], index);
132 
133     /* create a literal */
134     esiLiteral *temp = new esiLiteral (data);
135 
136     /* insert the literal */
137     elements[index] = temp;
138 
139     /* XXX: TODO push any pushable data upwards */
140     /* fail() not done */
141     if (processing)
142         return;
143 
144     assert (process (flags.dovars) != ESI_PROCESS_FAILED);
145 }
146 
147 bool
addElement(ESIElement::Pointer element)148 esiSequence::addElement (ESIElement::Pointer element)
149 {
150     /* add an element to the output list */
151     /* Some elements require specific parents */
152 
153     if (dynamic_cast<esiAttempt*>(element.getRaw()) ||
154             dynamic_cast<esiExcept*>(element.getRaw())) {
155         debugs(86, DBG_CRITICAL, "esiSequenceAdd: misparented Attempt or Except element (section 3.4)");
156         return false;
157     }
158 
159     /* Tie literals together for efficiency */
160     if (elements.size() && dynamic_cast<esiLiteral*>(element.getRaw()) &&
161             dynamic_cast<esiLiteral*>(elements[elements.size() - 1].getRaw())) {
162         debugs(86, 5, "esiSequenceAdd: tying Literals " <<
163                elements[elements.size() - 1].getRaw() << " and " <<
164                element.getRaw() << " together");
165 
166         ESISegment::ListTransfer (((esiLiteral *)element.getRaw())->buffer,
167                                   ((esiLiteral *)elements[elements.size() - 1].getRaw())->buffer);
168         return true;
169     }
170 
171     elements.push_back(element);
172     debugs (86,3, "esiSequenceAdd: Added a new element, elements = " << elements.size());
173     return true;
174 }
175 
176 int
elementIndex(ESIElement::Pointer anElement) const177 esiSequence::elementIndex(ESIElement::Pointer anElement) const
178 {
179     for (size_t i = 0; i < elements.size(); ++i)
180         if (elements[i] == anElement)
181             return i;
182 
183     return -1;
184 }
185 
186 void
processStep(int dovars)187 esiSequence::processStep(int dovars)
188 {
189     size_t elementToProcess = nextElementToProcess();
190     nextElementToProcess(elementToProcess + 1);
191     esiProcessResult_t tempResult = processOne(dovars, elementToProcess);
192 
193     if (processingResult < tempResult) {
194         debugs(86, 5, "esiSequence::process: processingResult was " << processingResult << ", increasing to " << tempResult);
195         processingResult = tempResult;
196     }
197 }
198 
199 esiProcessResult_t
processOne(int dovars,size_t index)200 esiSequence::processOne(int dovars, size_t index)
201 {
202     debugs (86,5, "esiSequence::process " << this << " about to process element[" << index << "] " << elements[index].getRaw());
203 
204     switch (elements[index]->process(dovars)) {
205 
206     case ESI_PROCESS_COMPLETE:
207         debugs(86, 5, "esiSequenceProcess: " << this << " element " << elements[index].getRaw() << " Processed OK");
208 
209         if (index == processedcount)
210             /* another completely ready */
211             ++processedcount;
212 
213         return ESI_PROCESS_COMPLETE;
214 
215     case ESI_PROCESS_PENDING_WONTFAIL:
216         debugs(86, 5, "esiSequenceProcess: element Processed PENDING OK");
217 
218         return ESI_PROCESS_PENDING_WONTFAIL;
219 
220     case ESI_PROCESS_PENDING_MAYFAIL:
221         debugs(86, 5, "eseSequenceProcess: element Processed PENDING UNKNOWN");
222 
223         return ESI_PROCESS_PENDING_MAYFAIL;
224 
225     case ESI_PROCESS_FAILED:
226         debugs(86, 5, "esiSequenceProcess: element Processed FAILED");
227 
228         return ESI_PROCESS_FAILED;
229 
230     default:
231         fatal ("unexpected code in esiSequence::processOne\n");
232 
233         return ESI_PROCESS_FAILED;
234     }
235 }
236 
237 esiProcessResult_t
process(int inheritedVarsFlag)238 esiSequence::process (int inheritedVarsFlag)
239 {
240     debugs(86, 5, "esiSequence::process: " << this << " processing");
241 
242     if (processing) {
243         debugs(86, 5, "esiSequence::process: " << this <<
244                " reentry attempt during processing");
245     }
246 
247     /* process as much of the list as we can, stopping only on
248      * faliures
249      */
250     if (!processing || processedcount == 0)
251         processingResult = ESI_PROCESS_COMPLETE;
252 
253     int dovars = inheritedVarsFlag;
254 
255     if (flags.dovars)
256         dovars = 1;
257 
258     debugs(86, 5, "esiSequence::process: Processing " << this << " with" <<
259            (dovars ? "" : "out") << " variable processing");
260 
261     processing = true;
262 
263     nextElementToProcess(processedcount);
264 
265     while (!finishedProcessing()) {
266         processStep(dovars);
267 
268         if (!processing)
269             return processingResult;
270 
271         if (processingResult == ESI_PROCESS_FAILED) {
272             FinishAllElements(elements);
273             failed = true;
274             parent = NULL;
275             processing = false;
276             return processingResult;
277         }
278     }
279 
280     assert (processingResult != ESI_PROCESS_COMPLETE || processedcount == elements.size());
281 
282     if (processingResult == ESI_PROCESS_COMPLETE || processingResult == ESI_PROCESS_PENDING_WONTFAIL)
283         wontFail();
284 
285     if (processedcount == elements.size() || provideIncrementalData) {
286         ESISegment::Pointer temp(new ESISegment);
287         render (temp);
288 
289         if (temp->next.getRaw() || temp->len)
290             parent->provideData(temp, this);
291         else
292             ESISegmentFreeList (temp);
293     }
294 
295     /* Depends on full parsing before processing */
296     if (processedcount == elements.size())
297         parent = NULL;
298 
299     debugs(86, 5, "esiSequence::process: " << this << " completed");
300 
301     processing = false;
302 
303     return processingResult;
304 }
305 
306 void
fail(ESIElement * source,char const * anError)307 esiSequence::fail (ESIElement *source, char const *anError)
308 {
309     failed = true;
310 
311     if (processing) {
312         debugs(86, 5, "esiSequence::fail: " << this << " failure callback during processing");
313         return;
314     }
315 
316     debugs(86, 5, "esiSequence::fail: " << this << " has failed.");
317     parent->fail (this, anError);
318     FinishAllElements(elements);
319     parent = NULL;
320 }
321 
esiSequence(esiSequence const & old)322 esiSequence::esiSequence(esiSequence const &old) :
323     processedcount(0),
324     parent(NULL),
325     mayFail_(old.mayFail_),
326     failed(old.failed),
327     provideIncrementalData(old.provideIncrementalData),
328     processing(false),
329     processingResult(ESI_PROCESS_COMPLETE),
330     nextElementToProcess_(0)
331 {
332     flags.dovars = old.flags.dovars;
333 }
334 
335 void
makeCachableElements(esiSequence const & old)336 esiSequence::makeCachableElements(esiSequence const &old)
337 {
338     for (size_t counter = 0; counter < old.elements.size(); ++counter) {
339         ESIElement::Pointer newElement = old.elements[counter]->makeCacheable();
340 
341         if (newElement.getRaw())
342             assert (addElement(newElement));
343     }
344 }
345 
346 void
makeUsableElements(esiSequence const & old,ESIVarState & newVarState)347 esiSequence::makeUsableElements(esiSequence const &old, ESIVarState &newVarState)
348 {
349     for (size_t counter = 0; counter < old.elements.size(); ++counter) {
350         ESIElement::Pointer newElement = old.elements[counter]->makeUsable (this, newVarState);
351 
352         if (newElement.getRaw())
353             assert (addElement(newElement));
354     }
355 }
356 
357 ESIElement::Pointer
makeCacheable() const358 esiSequence::makeCacheable() const
359 {
360     debugs(86, 5, "esiSequence::makeCacheable: Making cachable sequence from " << this);
361     assert (processedcount == 0);
362     assert (!failed);
363 
364     if (elements.size() == 0) {
365         debugs(86, 5, "esiSequence::makeCacheable: No elements in sequence " << this << ", returning NULL");
366         return NULL;
367     }
368 
369     esiSequence * resultS = new esiSequence (*this);
370     ESIElement::Pointer result = resultS;
371     resultS->makeCachableElements(*this);
372     debugs(86, 5, "esiSequence::makeCacheable: " << this << " created " << result.getRaw());
373     return result;
374 }
375 
376 ESIElement::Pointer
makeUsable(esiTreeParentPtr newParent,ESIVarState & newVarState) const377 esiSequence::makeUsable(esiTreeParentPtr newParent, ESIVarState &newVarState) const
378 {
379     debugs(86, 5, "esiSequence::makeUsable: Creating usable Sequence");
380     assert (processedcount == 0);
381     assert (!failed);
382 
383     if (elements.size() == 0) {
384         debugs(86, 5, "esiSequence::makeUsable: No elements in sequence " << this << ", returning NULL");
385         return NULL;
386     }
387 
388     esiSequence * resultS = new esiSequence (*this);
389     ESIElement::Pointer result = resultS;
390     resultS->parent = newParent;
391     resultS->makeUsableElements(*this, newVarState);
392     return result;
393 }
394 
395 #endif /* USE_SQUID_ESI == 1 */
396 
397