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 87    Client-side Stream routines. */
10 
11 #include "squid.h"
12 #include "client_side_request.h"
13 #include "clientStream.h"
14 #include "http/Stream.h"
15 #include "HttpReply.h"
16 #include "HttpRequest.h"
17 
18 /**
19  \defgroup ClientStreamInternal Client Streams Internals
20  \ingroup ClientStreamAPI
21  \par
22  * A client Stream is a uni directional pipe, with the usual non-blocking
23  * asynchronous approach present elsewhere in squid.
24  *
25  \par
26  * Each pipe node has a data push function, and a data request function.
27  * This limits flexability - the data flow is no longer assembled at each
28  * step.
29  *
30  \par
31  * An alternative approach is to pass each node in the pipe the call-
32  * back to use on each IO call. This allows the callbacks to be changed
33  * very easily by a participating node, but requires more maintenance
34  * in each node (store the callback to the most recent IO request in
35  * the nodes context.) Such an approach also prevents dynamically
36  * changing the pipeline from outside without an additional interface
37  * method to extract the callback and context from the next node.
38  *
39  \par
40  * One important characteristic of the stream is that the readfunc
41  * on the terminating node, and the callback on the first node
42  * will be NULL, and never used.
43  *
44  \section QuickNotes Quick Notes
45  \par
46  * Each node including the HEAD of the clientStream has a cbdataReference
47  * held by the stream. Freeing the stream then removes that reference
48  * and delete's every node.
49  * Any node with other References, and all nodes downstream will only
50  * free when those references are released.
51  * Stream nodes MAY hold references to the data member of the node.
52  *
53  \par
54  * Specifically - on creation no reference is made.
55  * If you pass a data variable to a node, give it an initial reference.
56  * If the data member is non-null on FREE, cbdataFree WILL be called.
57  * This you must never call cbdataFree on your own context without
58  * explicitly setting the stream node data member to NULL and
59  * cbdataReferenceDone'ing it.
60  *
61  \par
62  * No data member may hold a reference to it's stream node.
63  * The stream guarantees that DETACH will be called before
64  * freeing the node, alowing data members to cleanup.
65  *
66  \par
67  * If a node's data holds a reference to something that needs to
68  * free the stream a circular reference list will occur.
69  * This results no data being freed until that reference is removed.
70  * One way to accomplish thisObject is to explicitly remove the
71  * data from your own node before freeing the stream.
72  *
73  \code
74    mycontext = thisObject->data;
75    thisObject->data = NULL;
76    delete thisObject->head;
77    mycontext = NULL;
78    return;
79  \endcode
80  *
81  \todo rather than each node undeleting the next, have a clientStreamDelete that walks the list.
82  */
83 
84 CBDATA_CLASS_INIT(clientStreamNode);
85 
clientStreamNode(CSR * aReadfunc,CSCB * aCallback,CSD * aDetach,CSS * aStatus,ClientStreamData aData)86 clientStreamNode::clientStreamNode(CSR * aReadfunc, CSCB * aCallback, CSD * aDetach, CSS * aStatus, ClientStreamData aData) :
87     head(NULL),
88     readfunc(aReadfunc),
89     callback(aCallback),
90     detach(aDetach),
91     status(aStatus),
92     data(aData)
93 {}
94 
~clientStreamNode()95 clientStreamNode::~clientStreamNode()
96 {
97     debugs(87, 3, "Freeing clientStreamNode " << this);
98 
99     removeFromStream();
100     data = NULL;
101 }
102 
103 /**
104  \ingroup ClientStreamInternal
105  * Initialise a client Stream.
106  * list is the stream
107  * func is the read function for the head
108  * callback is the callback for the tail
109  * tailbuf and taillen are the initial buffer and length for the tail.
110  */
111 void
clientStreamInit(dlink_list * list,CSR * func,CSD * rdetach,CSS * readstatus,ClientStreamData readdata,CSCB * callback,CSD * cdetach,ClientStreamData callbackdata,StoreIOBuffer tailBuffer)112 clientStreamInit(dlink_list * list, CSR * func, CSD * rdetach, CSS * readstatus,
113                  ClientStreamData readdata, CSCB * callback, CSD * cdetach, ClientStreamData callbackdata,
114                  StoreIOBuffer tailBuffer)
115 {
116     clientStreamNode *temp = new clientStreamNode(func, NULL, rdetach, readstatus, readdata);
117     dlinkAdd(cbdataReference(temp), &temp->node, list);
118     temp->head = list;
119     clientStreamInsertHead(list, NULL, callback, cdetach, NULL, callbackdata);
120     temp = (clientStreamNode *)list->tail->data;
121     temp->readBuffer = tailBuffer;
122 }
123 
124 /**
125  \ingroup ClientStreamInternal
126  * Doesn't actually insert at head. Instead it inserts one *after*
127  * head. This is because HEAD is a special node, as is tail
128  * This function is not suitable for inserting the real HEAD.
129  */
130 void
clientStreamInsertHead(dlink_list * list,CSR * func,CSCB * callback,CSD * detach,CSS * status,ClientStreamData data)131 clientStreamInsertHead(dlink_list * list, CSR * func, CSCB * callback,
132                        CSD * detach, CSS * status, ClientStreamData data)
133 {
134     /* test preconditions */
135     assert(list != NULL);
136     assert(list->head);
137     clientStreamNode *temp = new clientStreamNode(func, callback, detach, status, data);
138     temp->head = list;
139     debugs(87, 3, "clientStreamInsertHead: Inserted node " << temp <<
140            " with data " << data.getRaw() << " after head");
141 
142     if (list->head->next)
143         temp->readBuffer = ((clientStreamNode *)list->head->next->data)->readBuffer;
144 
145     dlinkAddAfter(cbdataReference(temp), &temp->node, list->head, list);
146 }
147 
148 // API
149 void
clientStreamCallback(clientStreamNode * thisObject,ClientHttpRequest * http,HttpReply * rep,StoreIOBuffer replyBuffer)150 clientStreamCallback(clientStreamNode * thisObject, ClientHttpRequest * http,
151                      HttpReply * rep, StoreIOBuffer replyBuffer)
152 {
153     clientStreamNode *next;
154     assert(thisObject && http && thisObject->node.next);
155     next = thisObject->next();
156 
157     debugs(87, 3, "clientStreamCallback: Calling " << next->callback << " with cbdata " <<
158            next->data.getRaw() << " from node " << thisObject);
159     next->callback(next, http, rep, replyBuffer);
160 }
161 
162 /**
163  \ingroup ClientStreamInternal
164  * Call the previous node in the chain to read some data
165  *
166  \param thisObject  ??
167  \param http        ??
168  \param readBuffer  ??
169  */
170 void
clientStreamRead(clientStreamNode * thisObject,ClientHttpRequest * http,StoreIOBuffer readBuffer)171 clientStreamRead(clientStreamNode * thisObject, ClientHttpRequest * http,
172                  StoreIOBuffer readBuffer)
173 {
174     /* place the parameters on the 'stack' */
175     clientStreamNode *prev;
176     assert(thisObject && http && thisObject->prev());
177     prev = thisObject->prev();
178 
179     debugs(87, 3, "clientStreamRead: Calling " << prev->readfunc <<
180            " with cbdata " << prev->data.getRaw() << " from node " << thisObject);
181     thisObject->readBuffer = readBuffer;
182     prev->readfunc(prev, http);
183 }
184 
185 /**
186  \ingroup ClientStreamInternal
187  * Detach from the stream - only allowed for terminal members
188  *
189  \param thisObject  ??
190  \param http        ??
191  */
192 void
clientStreamDetach(clientStreamNode * thisObject,ClientHttpRequest * http)193 clientStreamDetach(clientStreamNode * thisObject, ClientHttpRequest * http)
194 {
195     clientStreamNode *temp = thisObject;
196 
197     assert(thisObject->node.next == NULL);
198     debugs(87, 3, "clientStreamDetach: Detaching node " << thisObject);
199     /* And clean up thisObject node */
200     /* ESI TODO: push refcount class through to head */
201     clientStreamNode *prev = NULL;
202 
203     if (thisObject->prev())
204         prev = cbdataReference(thisObject->prev());
205 
206     thisObject->removeFromStream();
207 
208     cbdataReferenceDone(temp);
209 
210     delete thisObject;
211 
212     /* and tell the prev that the detach has occurred */
213     /*
214      * We do it in thisObject order so that the detaching node is always
215      * at the end of the list
216      */
217 
218     if (prev) {
219         debugs(87, 3, "clientStreamDetach: Calling " << prev->detach << " with cbdata " << prev->data.getRaw());
220 
221         if (cbdataReferenceValid(prev))
222             prev->detach(prev, http);
223 
224         cbdataReferenceDone(prev);
225     }
226 }
227 
228 /**
229  \ingroup ClientStreamInternal
230  * Abort the stream - detach every node in the pipeline.
231  *
232  \param thisObject  ??
233  \param http        ??
234  */
235 void
clientStreamAbort(clientStreamNode * thisObject,ClientHttpRequest * http)236 clientStreamAbort(clientStreamNode * thisObject, ClientHttpRequest * http)
237 {
238     dlink_list *list;
239 
240     assert(thisObject != NULL);
241     assert(http != NULL);
242     list = thisObject->head;
243     debugs(87, 3, "clientStreamAbort: Aborting stream with tail " << list->tail);
244 
245     if (list->tail) {
246         clientStreamDetach((clientStreamNode *)list->tail->data, http);
247     }
248 }
249 
250 /**
251  \ingroup ClientStreamInternal
252  * Call the upstream node to find it's status
253  *
254  \param thisObject  ??
255  \param http        ??
256  */
257 clientStream_status_t
clientStreamStatus(clientStreamNode * thisObject,ClientHttpRequest * http)258 clientStreamStatus(clientStreamNode * thisObject, ClientHttpRequest * http)
259 {
260     clientStreamNode *prev;
261     assert(thisObject && http && thisObject->node.prev);
262     prev = (clientStreamNode *)thisObject->node.prev->data;
263     return prev->status(prev, http);
264 }
265 
266 void
removeFromStream()267 clientStreamNode::removeFromStream()
268 {
269     if (head)
270         dlinkDelete(&node, head);
271 
272     head = NULL;
273 }
274 
275 clientStreamNode *
prev() const276 clientStreamNode::prev() const
277 {
278     if (node.prev)
279         return (clientStreamNode *)node.prev->data;
280     else
281         return NULL;
282 }
283 
284 clientStreamNode *
next() const285 clientStreamNode::next() const
286 {
287     if (node.next)
288         return (clientStreamNode *)node.next->data;
289     else
290         return NULL;
291 }
292 
293