1 /** @file
2 
3   @brief An example program that sends response content to a server to be transformed and sends the
4          transformed content to the client.
5 
6   The protocol spoken with the server is simple. The plugin sends the
7   content-length of the document being transformed as a 4-byte
8   integer and then it sends the document itself. The first 4-bytes of
9   the server response are a status code/content length. If the code
10   is greater than 0 then the plugin assumes transformation was
11   successful and uses the code as the content length of the
12   transformed document. If the status code is less than or equal to 0
13   then the plugin bypasses transformation and sends the original
14   document on through.
15 
16   The plugin does a fair amount of error checking and tries to bypass
17   transformation in many cases such as when it can't connect to the
18   server. This example plugin simply connects to port 7 on localhost,
19   which on our solaris machines (and most unix machines) is the echo
20   port. One nicety about the protocol is that simply having the
21   server echo back what it is sent results in a "null"
22   transformation. (i.e. A transformation which does not modify the
23   content).
24 
25   @section license License
26 
27   Licensed to the Apache Software Foundation (ASF) under one
28   or more contributor license agreements.  See the NOTICE file
29   distributed with this work for additional information
30   regarding copyright ownership.  The ASF licenses this file
31   to you under the Apache License, Version 2.0 (the
32   "License"); you may not use this file except in compliance
33   with the License.  You may obtain a copy of the License at
34 
35       http://www.apache.org/licenses/LICENSE-2.0
36 
37   Unless required by applicable law or agreed to in writing, software
38   distributed under the License is distributed on an "AS IS" BASIS,
39   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
40   See the License for the specific language governing permissions and
41   limitations under the License.
42 */
43 
44 #include <string.h>
45 #include <stdio.h>
46 
47 #include <netinet/in.h>
48 
49 #include "ts/ts.h"
50 #include "tscore/ink_defs.h"
51 
52 #define PLUGIN_NAME "server-transform"
53 
54 #define STATE_BUFFER 1
55 #define STATE_CONNECT 2
56 #define STATE_WRITE 3
57 #define STATE_READ_STATUS 4
58 #define STATE_READ 5
59 #define STATE_BYPASS 6
60 
61 typedef struct {
62   int state;
63   TSHttpTxn txn;
64 
65   TSIOBuffer input_buf;
66   TSIOBufferReader input_reader;
67 
68   TSIOBuffer output_buf;
69   TSIOBufferReader output_reader;
70   TSVConn output_vc;
71   TSVIO output_vio;
72 
73   TSAction pending_action;
74   TSVConn server_vc;
75   TSVIO server_vio;
76 
77   int content_length;
78 } TransformData;
79 
80 static int transform_handler(TSCont contp, TSEvent event, void *edata);
81 
82 static in_addr_t server_ip;
83 static int server_port;
84 
85 static TSCont
transform_create(TSHttpTxn txnp)86 transform_create(TSHttpTxn txnp)
87 {
88   TSCont contp;
89   TransformData *data;
90 
91   contp = TSTransformCreate(transform_handler, txnp);
92 
93   data                 = (TransformData *)TSmalloc(sizeof(TransformData));
94   data->state          = STATE_BUFFER;
95   data->txn            = txnp;
96   data->input_buf      = NULL;
97   data->input_reader   = NULL;
98   data->output_buf     = NULL;
99   data->output_reader  = NULL;
100   data->output_vio     = NULL;
101   data->output_vc      = NULL;
102   data->pending_action = NULL;
103   data->server_vc      = NULL;
104   data->server_vio     = NULL;
105   data->content_length = 0;
106 
107   TSContDataSet(contp, data);
108   return contp;
109 }
110 
111 static void
transform_destroy(TSCont contp)112 transform_destroy(TSCont contp)
113 {
114   TransformData *data;
115 
116   data = TSContDataGet(contp);
117   if (data != NULL) {
118     if (data->input_buf) {
119       TSIOBufferDestroy(data->input_buf);
120     }
121 
122     if (data->output_buf) {
123       TSIOBufferDestroy(data->output_buf);
124     }
125 
126     if (data->pending_action) {
127       TSActionCancel(data->pending_action);
128     }
129 
130     if (data->server_vc) {
131       TSVConnAbort(data->server_vc, 1);
132     }
133 
134     TSfree(data);
135   } else {
136     TSError("[%s] Unable to get Continuation's Data. TSContDataGet returns NULL", PLUGIN_NAME);
137   }
138 
139   TSContDestroy(contp);
140 }
141 
142 static int
transform_connect(TSCont contp,TransformData * data)143 transform_connect(TSCont contp, TransformData *data)
144 {
145   TSAction action;
146   int content_length;
147   struct sockaddr_in ip_addr;
148 
149   data->state = STATE_CONNECT;
150 
151   content_length = TSIOBufferReaderAvail(data->input_reader);
152   if (content_length >= 0) {
153     data->content_length = content_length;
154     data->content_length = htonl(data->content_length);
155 
156     /* Prepend the content length to the buffer.
157      * If we decide to not send the content to the transforming
158      * server then we need to make sure and skip input_reader
159      * over the content length.
160      */
161 
162     {
163       TSIOBuffer temp;
164       TSIOBufferReader tempReader;
165 
166       temp       = TSIOBufferCreate();
167       tempReader = TSIOBufferReaderAlloc(temp);
168 
169       TSIOBufferWrite(temp, (const char *)&content_length, sizeof(int));
170       TSIOBufferCopy(temp, data->input_reader, content_length, 0);
171 
172       TSIOBufferReaderFree(data->input_reader);
173       TSIOBufferDestroy(data->input_buf);
174       data->input_buf    = temp;
175       data->input_reader = tempReader;
176     }
177   } else {
178     TSError("[%s] TSIOBufferReaderAvail returns TS_ERROR", PLUGIN_NAME);
179     return 0;
180   }
181 
182   /* TODO: This only supports IPv4, probably should be changed at some point, but
183      it's an example ... */
184   memset(&ip_addr, 0, sizeof(ip_addr));
185   ip_addr.sin_family      = AF_INET;
186   ip_addr.sin_addr.s_addr = server_ip; /* Should be in network byte order */
187   ip_addr.sin_port        = server_port;
188   TSDebug(PLUGIN_NAME, "net connect.");
189   action = TSNetConnect(contp, (struct sockaddr const *)&ip_addr);
190 
191   if (!TSActionDone(action)) {
192     data->pending_action = action;
193   }
194 
195   return 0;
196 }
197 
198 static int
transform_write(TSCont contp,TransformData * data)199 transform_write(TSCont contp, TransformData *data)
200 {
201   int content_length;
202 
203   data->state = STATE_WRITE;
204 
205   content_length = TSIOBufferReaderAvail(data->input_reader);
206   if (content_length >= 0) {
207     data->server_vio = TSVConnWrite(data->server_vc, contp, TSIOBufferReaderClone(data->input_reader), content_length);
208   } else {
209     TSError("[%s] TSIOBufferReaderAvail returns TS_ERROR", PLUGIN_NAME);
210   }
211   return 0;
212 }
213 
214 static int
transform_read_status(TSCont contp,TransformData * data)215 transform_read_status(TSCont contp, TransformData *data)
216 {
217   data->state = STATE_READ_STATUS;
218 
219   data->output_buf    = TSIOBufferCreate();
220   data->output_reader = TSIOBufferReaderAlloc(data->output_buf);
221   if (data->output_reader != NULL) {
222     data->server_vio = TSVConnRead(data->server_vc, contp, data->output_buf, sizeof(int));
223   } else {
224     TSError("[%s] Error in Allocating a Reader to output buffer. TSIOBufferReaderAlloc returns NULL", PLUGIN_NAME);
225   }
226 
227   return 0;
228 }
229 
230 static int
transform_read(TSCont contp,TransformData * data)231 transform_read(TSCont contp, TransformData *data)
232 {
233   data->state = STATE_READ;
234 
235   TSIOBufferDestroy(data->input_buf);
236   data->input_buf    = NULL;
237   data->input_reader = NULL;
238 
239   data->server_vio = TSVConnRead(data->server_vc, contp, data->output_buf, data->content_length);
240   data->output_vc  = TSTransformOutputVConnGet((TSVConn)contp);
241   if (data->output_vc == NULL) {
242     TSError("[%s] TSTransformOutputVConnGet returns NULL", PLUGIN_NAME);
243   } else {
244     data->output_vio = TSVConnWrite(data->output_vc, contp, data->output_reader, data->content_length);
245     if (data->output_vio == NULL) {
246       TSError("[%s] TSVConnWrite returns NULL", PLUGIN_NAME);
247     }
248   }
249 
250   return 0;
251 }
252 
253 static int
transform_bypass(TSCont contp,TransformData * data)254 transform_bypass(TSCont contp, TransformData *data)
255 {
256   data->state = STATE_BYPASS;
257 
258   if (data->server_vc) {
259     TSVConnAbort(data->server_vc, 1);
260     data->server_vc  = NULL;
261     data->server_vio = NULL;
262   }
263 
264   if (data->output_buf) {
265     TSIOBufferDestroy(data->output_buf);
266     data->output_buf    = NULL;
267     data->output_reader = NULL;
268   }
269 
270   TSIOBufferReaderConsume(data->input_reader, sizeof(int));
271   data->output_vc = TSTransformOutputVConnGet((TSVConn)contp);
272   if (data->output_vc == NULL) {
273     TSError("[%s] TSTransformOutputVConnGet returns NULL", PLUGIN_NAME);
274   } else {
275     data->output_vio = TSVConnWrite(data->output_vc, contp, data->input_reader, TSIOBufferReaderAvail(data->input_reader));
276     if (data->output_vio == NULL) {
277       TSError("[%s] TSVConnWrite returns NULL", PLUGIN_NAME);
278     }
279   }
280   return 1;
281 }
282 
283 static int
transform_buffer_event(TSCont contp,TransformData * data,TSEvent event ATS_UNUSED,void * edata ATS_UNUSED)284 transform_buffer_event(TSCont contp, TransformData *data, TSEvent event ATS_UNUSED, void *edata ATS_UNUSED)
285 {
286   TSVIO write_vio;
287   int64_t towrite;
288 
289   if (!data->input_buf) {
290     data->input_buf    = TSIOBufferCreate();
291     data->input_reader = TSIOBufferReaderAlloc(data->input_buf);
292   }
293 
294   /* Get the write VIO for the write operation that was performed on
295      ourself. This VIO contains the buffer that we are to read from
296      as well as the continuation we are to call when the buffer is
297      empty. */
298   write_vio = TSVConnWriteVIOGet(contp);
299 
300   /* We also check to see if the write VIO's buffer is non-NULL. A
301      NULL buffer indicates that the write operation has been
302      shutdown and that the continuation does not want us to send any
303      more WRITE_READY or WRITE_COMPLETE events. For this buffered
304      transformation that means we're done buffering data. */
305   if (!TSVIOBufferGet(write_vio)) {
306     return transform_connect(contp, data);
307   }
308 
309   /* Determine how much data we have left to read. For this server
310      transform plugin this is also the amount of data we have left
311      to write to the output connection. */
312   towrite = TSVIONTodoGet(write_vio);
313   if (towrite > 0) {
314     /* The amount of data left to read needs to be truncated by
315        the amount of data actually in the read buffer. */
316     int64_t avail = TSIOBufferReaderAvail(TSVIOReaderGet(write_vio));
317     if (towrite > avail) {
318       towrite = avail;
319     }
320 
321     if (towrite > 0) {
322       /* Copy the data from the read buffer to the input buffer. */
323       TSIOBufferCopy(data->input_buf, TSVIOReaderGet(write_vio), towrite, 0);
324 
325       /* Tell the read buffer that we have read the data and are no
326          longer interested in it. */
327       TSIOBufferReaderConsume(TSVIOReaderGet(write_vio), towrite);
328 
329       /* Modify the write VIO to reflect how much data we've
330          completed. */
331       TSVIONDoneSet(write_vio, TSVIONDoneGet(write_vio) + towrite);
332     }
333   }
334 
335   /* Now we check the write VIO to see if there is data left to
336      read. */
337   if (TSVIONTodoGet(write_vio) > 0) {
338     /* Call back the write VIO continuation to let it know that we
339        are ready for more data. */
340     TSContCall(TSVIOContGet(write_vio), TS_EVENT_VCONN_WRITE_READY, write_vio);
341   } else {
342     /* Call back the write VIO continuation to let it know that we
343        have completed the write operation. */
344     TSContCall(TSVIOContGet(write_vio), TS_EVENT_VCONN_WRITE_COMPLETE, write_vio);
345 
346     /* start compression... */
347     return transform_connect(contp, data);
348   }
349 
350   return 0;
351 }
352 
353 static int
transform_connect_event(TSCont contp,TransformData * data,TSEvent event,void * edata)354 transform_connect_event(TSCont contp, TransformData *data, TSEvent event, void *edata)
355 {
356   switch (event) {
357   case TS_EVENT_NET_CONNECT:
358     TSDebug(PLUGIN_NAME, "connected");
359 
360     data->pending_action = NULL;
361     data->server_vc      = (TSVConn)edata;
362     return transform_write(contp, data);
363   case TS_EVENT_NET_CONNECT_FAILED:
364     TSDebug(PLUGIN_NAME, "connect failed");
365     data->pending_action = NULL;
366     return transform_bypass(contp, data);
367   default:
368     break;
369   }
370 
371   return 0;
372 }
373 
374 static int
transform_write_event(TSCont contp,TransformData * data,TSEvent event,void * edata ATS_UNUSED)375 transform_write_event(TSCont contp, TransformData *data, TSEvent event, void *edata ATS_UNUSED)
376 {
377   switch (event) {
378   case TS_EVENT_VCONN_WRITE_READY:
379     TSVIOReenable(data->server_vio);
380     break;
381   case TS_EVENT_VCONN_WRITE_COMPLETE:
382     return transform_read_status(contp, data);
383   case TS_EVENT_ERROR:
384     return transform_bypass(contp, data);
385   case TS_EVENT_IMMEDIATE:
386     TSVIOReenable(data->server_vio);
387     break;
388   default:
389     /* An error occurred while writing to the server. Close down
390        the connection to the server and bypass. */
391     return transform_bypass(contp, data);
392   }
393 
394   return 0;
395 }
396 
397 static int
transform_read_status_event(TSCont contp,TransformData * data,TSEvent event,void * edata ATS_UNUSED)398 transform_read_status_event(TSCont contp, TransformData *data, TSEvent event, void *edata ATS_UNUSED)
399 {
400   switch (event) {
401   case TS_EVENT_ERROR:
402   case TS_EVENT_VCONN_EOS:
403     return transform_bypass(contp, data);
404   case TS_EVENT_VCONN_READ_COMPLETE:
405     if (TSIOBufferReaderAvail(data->output_reader) == sizeof(int)) {
406       void *buf_ptr;
407       int64_t avail;
408       int64_t read_nbytes = sizeof(int);
409 
410       buf_ptr = &data->content_length;
411       while (read_nbytes > 0) {
412         TSIOBufferBlock blk = TSIOBufferReaderStart(data->output_reader);
413         char *buf           = (char *)TSIOBufferBlockReadStart(blk, data->output_reader, &avail);
414         int64_t read_ndone  = (avail >= read_nbytes) ? read_nbytes : avail;
415 
416         memcpy(buf_ptr, buf, read_ndone);
417         if (read_ndone > 0) {
418           TSIOBufferReaderConsume(data->output_reader, read_ndone);
419           read_nbytes -= read_ndone;
420           /* move ptr frwd by read_ndone bytes */
421           buf_ptr = (char *)buf_ptr + read_ndone;
422         }
423       }
424       // data->content_length = ntohl(data->content_length);
425       return transform_read(contp, data);
426     }
427     return transform_bypass(contp, data);
428   default:
429     break;
430   }
431 
432   return 0;
433 }
434 
435 static int
transform_read_event(TSCont contp ATS_UNUSED,TransformData * data,TSEvent event,void * edata ATS_UNUSED)436 transform_read_event(TSCont contp ATS_UNUSED, TransformData *data, TSEvent event, void *edata ATS_UNUSED)
437 {
438   switch (event) {
439   case TS_EVENT_ERROR:
440     TSVConnAbort(data->server_vc, 1);
441     data->server_vc  = NULL;
442     data->server_vio = NULL;
443 
444     TSVConnAbort(data->output_vc, 1);
445     data->output_vc  = NULL;
446     data->output_vio = NULL;
447     break;
448   case TS_EVENT_VCONN_EOS:
449     TSVConnAbort(data->server_vc, 1);
450     data->server_vc  = NULL;
451     data->server_vio = NULL;
452 
453     TSVConnAbort(data->output_vc, 1);
454     data->output_vc  = NULL;
455     data->output_vio = NULL;
456     break;
457   case TS_EVENT_VCONN_READ_COMPLETE:
458     TSVConnClose(data->server_vc);
459     data->server_vc  = NULL;
460     data->server_vio = NULL;
461 
462     TSVIOReenable(data->output_vio);
463     break;
464   case TS_EVENT_VCONN_READ_READY:
465     TSVIOReenable(data->output_vio);
466     break;
467   case TS_EVENT_VCONN_WRITE_COMPLETE:
468     TSVConnShutdown(data->output_vc, 0, 1);
469     break;
470   case TS_EVENT_VCONN_WRITE_READY:
471     TSVIOReenable(data->server_vio);
472     break;
473   default:
474     break;
475   }
476 
477   return 0;
478 }
479 
480 static int
transform_bypass_event(TSCont contp ATS_UNUSED,TransformData * data,TSEvent event,void * edata ATS_UNUSED)481 transform_bypass_event(TSCont contp ATS_UNUSED, TransformData *data, TSEvent event, void *edata ATS_UNUSED)
482 {
483   switch (event) {
484   case TS_EVENT_VCONN_WRITE_COMPLETE:
485     TSVConnShutdown(data->output_vc, 0, 1);
486     break;
487   case TS_EVENT_VCONN_WRITE_READY:
488   default:
489     TSVIOReenable(data->output_vio);
490     break;
491   }
492 
493   return 0;
494 }
495 
496 static int
transform_handler(TSCont contp,TSEvent event,void * edata)497 transform_handler(TSCont contp, TSEvent event, void *edata)
498 {
499   /* Check to see if the transformation has been closed by a call to
500      TSVConnClose. */
501   if (TSVConnClosedGet(contp)) {
502     TSDebug(PLUGIN_NAME, "transformation closed");
503     transform_destroy(contp);
504     return 0;
505   } else {
506     TransformData *data;
507     int val = 0;
508 
509     data = (TransformData *)TSContDataGet(contp);
510     if (data == NULL) {
511       TSError("[%s] Didn't get Continuation's Data, ignoring event", PLUGIN_NAME);
512       return 0;
513     }
514     TSDebug(PLUGIN_NAME, "transform handler event [%d], data->state = [%d]", event, data->state);
515 
516     do {
517       switch (data->state) {
518       case STATE_BUFFER:
519         val = transform_buffer_event(contp, data, event, edata);
520         break;
521       case STATE_CONNECT:
522         val = transform_connect_event(contp, data, event, edata);
523         break;
524       case STATE_WRITE:
525         val = transform_write_event(contp, data, event, edata);
526         break;
527       case STATE_READ_STATUS:
528         val = transform_read_status_event(contp, data, event, edata);
529         break;
530       case STATE_READ:
531         val = transform_read_event(contp, data, event, edata);
532         break;
533       case STATE_BYPASS:
534         val = transform_bypass_event(contp, data, event, edata);
535         break;
536       }
537     } while (val);
538   }
539 
540   return 0;
541 }
542 
543 static int
request_ok(TSHttpTxn txnp ATS_UNUSED)544 request_ok(TSHttpTxn txnp ATS_UNUSED)
545 {
546   /* Is the initial client request OK for transformation. This is a
547      good place to check accept headers to see if the client can
548      accept a transformed document. */
549   return 1;
550 }
551 
552 static int
cache_response_ok(TSHttpTxn txnp ATS_UNUSED)553 cache_response_ok(TSHttpTxn txnp ATS_UNUSED)
554 {
555   /* Is the response we're reading from cache OK for
556    * transformation. This is a good place to check the cached
557    * response to see if it is transformable. The default
558    * behavior is to cache transformed content; therefore
559    * to avoid transforming twice we will not transform
560    * content served from the cache.
561    */
562   return 0;
563 }
564 
565 static int
server_response_ok(TSHttpTxn txnp)566 server_response_ok(TSHttpTxn txnp)
567 {
568   /* Is the response the server sent OK for transformation. This is
569    * a good place to check the server's response to see if it is
570    * transformable. In this example, we will transform only "200 OK"
571    * responses.
572    */
573 
574   TSMBuffer bufp;
575   TSMLoc hdr_loc;
576   TSHttpStatus resp_status;
577 
578   if (TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
579     TSError("[%s] Unable to get handle to Server Response", PLUGIN_NAME);
580     return 0;
581   }
582 
583   resp_status = TSHttpHdrStatusGet(bufp, hdr_loc);
584   if (TS_HTTP_STATUS_OK == resp_status) {
585     if (TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc) != TS_SUCCESS) {
586       TSError("[%s] Unable to release handle to server request", PLUGIN_NAME);
587     }
588     return 1;
589   } else {
590     if (TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc) != TS_SUCCESS) {
591       TSError("[%s] Unable to release handle to server request", PLUGIN_NAME);
592     }
593     return 0;
594   }
595 }
596 
597 static int
transform_plugin(TSCont contp,TSEvent event,void * edata)598 transform_plugin(TSCont contp, TSEvent event, void *edata)
599 {
600   TSHttpTxn txnp = (TSHttpTxn)edata;
601 
602   switch (event) {
603   case TS_EVENT_HTTP_READ_REQUEST_HDR:
604     if (request_ok(txnp)) {
605       TSHttpTxnHookAdd(txnp, TS_HTTP_READ_CACHE_HDR_HOOK, contp);
606       TSHttpTxnHookAdd(txnp, TS_HTTP_READ_RESPONSE_HDR_HOOK, contp);
607     }
608     TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
609     break;
610   case TS_EVENT_HTTP_READ_CACHE_HDR:
611     if (cache_response_ok(txnp)) {
612       TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, transform_create(txnp));
613     }
614     TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
615     break;
616   case TS_EVENT_HTTP_READ_RESPONSE_HDR:
617     if (server_response_ok(txnp)) {
618       TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, transform_create(txnp));
619     }
620     TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
621     break;
622   default:
623     break;
624   }
625   return 0;
626 }
627 
628 void
TSPluginInit(int argc ATS_UNUSED,const char * argv[]ATS_UNUSED)629 TSPluginInit(int argc ATS_UNUSED, const char *argv[] ATS_UNUSED)
630 {
631   TSPluginRegistrationInfo info;
632   TSCont cont;
633 
634   info.plugin_name   = PLUGIN_NAME;
635   info.vendor_name   = "Apache Software Foundation";
636   info.support_email = "dev@trafficserver.apache.org";
637 
638   if (TSPluginRegister(&info) != TS_SUCCESS) {
639     TSError("[%s] Plugin registration failed", PLUGIN_NAME);
640   }
641 
642   /* connect to the echo port on localhost */
643   server_ip   = (127 << 24) | (0 << 16) | (0 << 8) | (1);
644   server_ip   = htonl(server_ip);
645   server_port = 7;
646 
647   cont = TSContCreate(transform_plugin, NULL);
648   TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, cont);
649 }
650