1 /** @file
2 
3   A brief file description
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 /* bnull-transform.c:  an example program that illustrates a buffered
25  *                     null transform.
26  *
27  *
28  *
29  *    Usage:
30  *      bnull-transform.so
31  *
32  *
33  */
34 
35 /* set tab stops to four. */
36 
37 #include <stdio.h>
38 
39 #include "ts/ts.h"
40 #include "tscore/ink_defs.h"
41 
42 #define PLUGIN_NAME "bnull_transform"
43 
44 #define TS_NULL_MUTEX NULL
45 #define STATE_BUFFER_DATA 0
46 #define STATE_OUTPUT_DATA 1
47 
48 typedef struct {
49   int state;
50   TSVIO output_vio;
51   TSIOBuffer output_buffer;
52   TSIOBufferReader output_reader;
53 } MyData;
54 
55 static MyData *
my_data_alloc()56 my_data_alloc()
57 {
58   MyData *data;
59 
60   data                = (MyData *)TSmalloc(sizeof(MyData));
61   data->state         = STATE_BUFFER_DATA;
62   data->output_vio    = NULL;
63   data->output_buffer = NULL;
64   data->output_reader = NULL;
65 
66   return data;
67 }
68 
69 static void
my_data_destroy(MyData * data)70 my_data_destroy(MyData *data)
71 {
72   if (data) {
73     if (data->output_buffer) {
74       TSIOBufferDestroy(data->output_buffer);
75     }
76     TSfree(data);
77   }
78 }
79 
80 static int
handle_buffering(TSCont contp,MyData * data)81 handle_buffering(TSCont contp, MyData *data)
82 {
83   TSVIO write_vio;
84   int64_t towrite;
85 
86   /* Get the write VIO for the write operation that was performed on
87      ourself. This VIO contains the buffer that we are to read from
88      as well as the continuation we are to call when the buffer is
89      empty. */
90   write_vio = TSVConnWriteVIOGet(contp);
91 
92   /* Create the output buffer and its associated reader */
93   if (!data->output_buffer) {
94     data->output_buffer = TSIOBufferCreate();
95     TSAssert(data->output_buffer);
96     data->output_reader = TSIOBufferReaderAlloc(data->output_buffer);
97     TSAssert(data->output_reader);
98   }
99 
100   /* We also check to see if the write VIO's buffer is non-NULL. A
101      NULL buffer indicates that the write operation has been
102      shutdown and that the continuation does not want us to send any
103      more WRITE_READY or WRITE_COMPLETE events. For this buffered
104      transformation that means we're done buffering data. */
105 
106   if (!TSVIOBufferGet(write_vio)) {
107     data->state = STATE_OUTPUT_DATA;
108     return 0;
109   }
110 
111   /* Determine how much data we have left to read. For this bnull
112      transform plugin this is also the amount of data we have left
113      to write to the output connection. */
114 
115   towrite = TSVIONTodoGet(write_vio);
116   if (towrite > 0) {
117     /* The amount of data left to read needs to be truncated by
118        the amount of data actually in the read buffer. */
119 
120     int64_t avail = TSIOBufferReaderAvail(TSVIOReaderGet(write_vio));
121     if (towrite > avail) {
122       towrite = avail;
123     }
124 
125     if (towrite > 0) {
126       /* Copy the data from the read buffer to the input buffer. */
127       TSIOBufferCopy(data->output_buffer, TSVIOReaderGet(write_vio), towrite, 0);
128 
129       /* Tell the read buffer that we have read the data and are no
130          longer interested in it. */
131       TSIOBufferReaderConsume(TSVIOReaderGet(write_vio), towrite);
132 
133       /* Modify the write VIO to reflect how much data we've
134          completed. */
135       TSVIONDoneSet(write_vio, TSVIONDoneGet(write_vio) + towrite);
136     }
137   }
138 
139   /* Now we check the write VIO to see if there is data left to read. */
140   if (TSVIONTodoGet(write_vio) > 0) {
141     if (towrite > 0) {
142       /* Call back the write VIO continuation to let it know that we
143          are ready for more data. */
144       TSContCall(TSVIOContGet(write_vio), TS_EVENT_VCONN_WRITE_READY, write_vio);
145     }
146   } else {
147     data->state = STATE_OUTPUT_DATA;
148 
149     /* Call back the write VIO continuation to let it know that we
150        have completed the write operation. */
151     TSContCall(TSVIOContGet(write_vio), TS_EVENT_VCONN_WRITE_COMPLETE, write_vio);
152   }
153 
154   return 1;
155 }
156 
157 static int
handle_output(TSCont contp,MyData * data)158 handle_output(TSCont contp, MyData *data)
159 {
160   /* Check to see if we need to initiate the output operation. */
161   if (!data->output_vio) {
162     TSVConn output_conn;
163 
164     /* Get the output connection where we'll write data to. */
165     output_conn = TSTransformOutputVConnGet(contp);
166 
167     data->output_vio = TSVConnWrite(output_conn, contp, data->output_reader, TSIOBufferReaderAvail(data->output_reader));
168 
169     TSAssert(data->output_vio);
170   }
171   return 1;
172 }
173 
174 static void
handle_transform(TSCont contp)175 handle_transform(TSCont contp)
176 {
177   MyData *data;
178   int done;
179 
180   /* Get our data structure for this operation. The private data
181      structure contains the output VIO and output buffer. If the
182      private data structure pointer is NULL, then we'll create it
183      and initialize its internals. */
184 
185   data = (MyData *)TSContDataGet(contp);
186   if (!data) {
187     data = my_data_alloc();
188     TSContDataSet(contp, (void *)data);
189   }
190 
191   do {
192     switch (data->state) {
193     case STATE_BUFFER_DATA:
194       done = handle_buffering(contp, data);
195       break;
196     case STATE_OUTPUT_DATA:
197       done = handle_output(contp, data);
198       break;
199     default:
200       done = 1;
201       break;
202     }
203   } while (!done);
204 }
205 
206 static int
bnull_transform(TSCont contp,TSEvent event,void * edata ATS_UNUSED)207 bnull_transform(TSCont contp, TSEvent event, void *edata ATS_UNUSED)
208 {
209   /* Check to see if the transformation has been closed by a
210      call to TSVConnClose. */
211 
212   if (TSVConnClosedGet(contp)) {
213     my_data_destroy((MyData *)TSContDataGet(contp));
214     TSContDestroy(contp);
215   } else {
216     switch (event) {
217     case TS_EVENT_ERROR: {
218       TSVIO write_vio;
219 
220       /* Get the write VIO for the write operation that was
221          performed on ourself. This VIO contains the continuation of
222          our parent transformation. */
223       write_vio = TSVConnWriteVIOGet(contp);
224 
225       /* Call back the write VIO continuation to let it know that we
226          have completed the write operation. */
227       TSContCall(TSVIOContGet(write_vio), TS_EVENT_ERROR, write_vio);
228       break;
229     }
230 
231     case TS_EVENT_VCONN_WRITE_COMPLETE:
232       /* When our output connection says that it has finished
233          reading all the data we've written to it then we should
234          shutdown the write portion of its connection to
235          indicate that we don't want to hear about it anymore. */
236 
237       TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1);
238       break;
239 
240     case TS_EVENT_VCONN_WRITE_READY:
241     default:
242       /* If we get a WRITE_READY event or any other type of event
243          (sent, perhaps, because we were reenabled) then we'll attempt
244          to transform more data. */
245       handle_transform(contp);
246       break;
247     }
248   }
249 
250   return 0;
251 }
252 
253 static int
transformable(TSHttpTxn txnp)254 transformable(TSHttpTxn txnp)
255 {
256   TSMBuffer bufp;
257   TSMLoc hdr_loc;
258   TSHttpStatus resp_status;
259   int retv = 0;
260 
261   /* We are only interested in transforming "200 OK" responses. */
262 
263   if (TS_SUCCESS == TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc)) {
264     resp_status = TSHttpHdrStatusGet(bufp, hdr_loc);
265     retv        = (resp_status == TS_HTTP_STATUS_OK);
266     TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
267   }
268 
269   return retv;
270 }
271 
272 static void
transform_add(TSHttpTxn txnp)273 transform_add(TSHttpTxn txnp)
274 {
275   TSVConn connp;
276 
277   connp = TSTransformCreate(bnull_transform, txnp);
278   TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, connp);
279   return;
280 }
281 
282 static int
transform_plugin(TSCont contp ATS_UNUSED,TSEvent event,void * edata)283 transform_plugin(TSCont contp ATS_UNUSED, TSEvent event, void *edata)
284 {
285   TSHttpTxn txnp = (TSHttpTxn)edata;
286 
287   switch (event) {
288   case TS_EVENT_HTTP_READ_RESPONSE_HDR:
289     if (transformable(txnp)) {
290       transform_add(txnp);
291     }
292     TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
293     return 0;
294   default:
295     break;
296   }
297 
298   return 0;
299 }
300 
301 void
TSPluginInit(int argc ATS_UNUSED,const char * argv[]ATS_UNUSED)302 TSPluginInit(int argc ATS_UNUSED, const char *argv[] ATS_UNUSED)
303 {
304   TSPluginRegistrationInfo info;
305   TSMutex mutex = TS_NULL_MUTEX;
306 
307   info.plugin_name   = PLUGIN_NAME;
308   info.vendor_name   = "Apache Software Foundation";
309   info.support_email = "dev@trafficserver.apache.org";
310 
311   if (TSPluginRegister(&info) != TS_SUCCESS) {
312     TSError("[%s] Plugin registration failed", PLUGIN_NAME);
313 
314     goto Lerror;
315   }
316 
317   /* This is call we could use if we need to protect global data */
318   /* TSReleaseAssert ((mutex = TSMutexCreate()) != TS_NULL_MUTEX); */
319 
320   TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK, TSContCreate(transform_plugin, mutex));
321   return;
322 
323 Lerror:
324   TSError("[%s] Plugin disabled", PLUGIN_NAME);
325 }
326