1 /*
2   Licensed to the Apache Software Foundation (ASF) under one
3   or more contributor license agreements.  See the NOTICE file
4   distributed with this work for additional information
5   regarding copyright ownership.  The ASF licenses this file
6   to you under the Apache License, Version 2.0 (the
7   "License"); you may not use this file except in compliance
8   with the License.  You may obtain a copy of the License at
9 
10   http://www.apache.org/licenses/LICENSE-2.0
11 
12   Unless required by applicable law or agreed to in writing, software
13   distributed under the License is distributed on an "AS IS" BASIS,
14   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   See the License for the specific language governing permissions and
16   limitations under the License.
17 */
18 
19 #include "mp4_common.h"
20 
21 static char *ts_arg(const char *param, size_t param_len, const char *key, size_t key_len, size_t *val_len);
22 static int mp4_handler(TSCont contp, TSEvent event, void *edata);
23 static void mp4_cache_lookup_complete(Mp4Context *mc, TSHttpTxn txnp);
24 static void mp4_read_response(Mp4Context *mc, TSHttpTxn txnp);
25 static void mp4_add_transform(Mp4Context *mc, TSHttpTxn txnp);
26 static int mp4_transform_entry(TSCont contp, TSEvent event, void *edata);
27 static int mp4_transform_handler(TSCont contp, Mp4Context *mc);
28 static int mp4_parse_meta(Mp4TransformContext *mtc, bool body_complete);
29 
30 TSReturnCode
TSRemapInit(TSRemapInterface * api_info,char * errbuf,int errbuf_size)31 TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size)
32 {
33   if (!api_info) {
34     snprintf(errbuf, errbuf_size, "[TSRemapInit] - Invalid TSRemapInterface argument");
35     return TS_ERROR;
36   }
37 
38   if (api_info->size < sizeof(TSRemapInterface)) {
39     snprintf(errbuf, errbuf_size, "[TSRemapInit] - Incorrect size of TSRemapInterface structure");
40     return TS_ERROR;
41   }
42 
43   return TS_SUCCESS;
44 }
45 
46 TSReturnCode
TSRemapNewInstance(int argc,char **,void ** ih,char * errbuf,int errbuf_size)47 TSRemapNewInstance(int argc, char ** /* argv ATS_UNUSED */, void **ih, char *errbuf, int errbuf_size)
48 {
49   if (argc > 2) {
50     snprintf(errbuf, errbuf_size, "[TSRemapNewInstance] - Argument should be removed");
51   }
52 
53   *ih = nullptr;
54   return TS_SUCCESS;
55 }
56 
57 void
TSRemapDeleteInstance(void *)58 TSRemapDeleteInstance(void * /* ih ATS_UNUSED */)
59 {
60   return;
61 }
62 
63 TSRemapStatus
TSRemapDoRemap(void *,TSHttpTxn rh,TSRemapRequestInfo * rri)64 TSRemapDoRemap(void * /* ih ATS_UNUSED */, TSHttpTxn rh, TSRemapRequestInfo *rri)
65 {
66   const char *method, *query, *path;
67   int method_len, query_len, path_len;
68   size_t val_len;
69   const char *val;
70   int ret;
71   float start;
72   char buf[1024];
73   int buf_len;
74   int left, right;
75   TSMLoc ae_field, range_field;
76   TSCont contp;
77   Mp4Context *mc;
78 
79   method = TSHttpHdrMethodGet(rri->requestBufp, rri->requestHdrp, &method_len);
80   if (method != TS_HTTP_METHOD_GET) {
81     return TSREMAP_NO_REMAP;
82   }
83 
84   // check suffix
85   path = TSUrlPathGet(rri->requestBufp, rri->requestUrl, &path_len);
86 
87   if (path == nullptr || path_len <= 4) {
88     return TSREMAP_NO_REMAP;
89 
90   } else if (strncasecmp(path + path_len - 4, ".mp4", 4) != 0) {
91     return TSREMAP_NO_REMAP;
92   }
93 
94   start = 0;
95   query = TSUrlHttpQueryGet(rri->requestBufp, rri->requestUrl, &query_len);
96 
97   val = ts_arg(query, query_len, "start", sizeof("start") - 1, &val_len);
98   if (val != nullptr) {
99     ret = sscanf(val, "%f", &start);
100     if (ret != 1) {
101       start = 0;
102     }
103   }
104 
105   if (start == 0) {
106     return TSREMAP_NO_REMAP;
107 
108   } else if (start < 0) {
109     TSHttpTxnStatusSet(rh, TS_HTTP_STATUS_BAD_REQUEST);
110     TSHttpTxnErrorBodySet(rh, TSstrdup("Invalid request."), sizeof("Invalid request.") - 1, nullptr);
111   }
112 
113   // reset args
114   left  = val - sizeof("start") - query;
115   right = query + query_len - val - val_len;
116 
117   if (left > 0) {
118     left--;
119   }
120 
121   if (left == 0 && right > 0) {
122     right--;
123   }
124 
125   buf_len = sprintf(buf, "%.*s%.*s", left, query, right, query + query_len - right);
126   TSUrlHttpQuerySet(rri->requestBufp, rri->requestUrl, buf, buf_len);
127 
128   // remove Accept-Encoding
129   ae_field = TSMimeHdrFieldFind(rri->requestBufp, rri->requestHdrp, TS_MIME_FIELD_ACCEPT_ENCODING, TS_MIME_LEN_ACCEPT_ENCODING);
130   if (ae_field) {
131     TSMimeHdrFieldDestroy(rri->requestBufp, rri->requestHdrp, ae_field);
132     TSHandleMLocRelease(rri->requestBufp, rri->requestHdrp, ae_field);
133   }
134 
135   // remove Range
136   range_field = TSMimeHdrFieldFind(rri->requestBufp, rri->requestHdrp, TS_MIME_FIELD_RANGE, TS_MIME_LEN_RANGE);
137   if (range_field) {
138     TSMimeHdrFieldDestroy(rri->requestBufp, rri->requestHdrp, range_field);
139     TSHandleMLocRelease(rri->requestBufp, rri->requestHdrp, range_field);
140   }
141 
142   mc    = new Mp4Context(start);
143   contp = TSContCreate(mp4_handler, nullptr);
144   TSContDataSet(contp, mc);
145 
146   TSHttpTxnHookAdd(rh, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, contp);
147   TSHttpTxnHookAdd(rh, TS_HTTP_READ_RESPONSE_HDR_HOOK, contp);
148   TSHttpTxnHookAdd(rh, TS_HTTP_TXN_CLOSE_HOOK, contp);
149   return TSREMAP_NO_REMAP;
150 }
151 
152 static int
mp4_handler(TSCont contp,TSEvent event,void * edata)153 mp4_handler(TSCont contp, TSEvent event, void *edata)
154 {
155   TSHttpTxn txnp;
156   Mp4Context *mc;
157 
158   txnp = static_cast<TSHttpTxn>(edata);
159   mc   = static_cast<Mp4Context *>(TSContDataGet(contp));
160 
161   switch (event) {
162   case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE:
163     mp4_cache_lookup_complete(mc, txnp);
164     break;
165 
166   case TS_EVENT_HTTP_READ_RESPONSE_HDR:
167     mp4_read_response(mc, txnp);
168     break;
169 
170   case TS_EVENT_HTTP_TXN_CLOSE:
171     delete mc;
172     TSContDestroy(contp);
173     break;
174 
175   default:
176     break;
177   }
178 
179   TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
180   return 0;
181 }
182 
183 static void
mp4_cache_lookup_complete(Mp4Context * mc,TSHttpTxn txnp)184 mp4_cache_lookup_complete(Mp4Context *mc, TSHttpTxn txnp)
185 {
186   TSMBuffer bufp;
187   TSMLoc hdrp;
188   TSMLoc cl_field;
189   TSHttpStatus code;
190   int obj_status;
191   int64_t n;
192 
193   if (TSHttpTxnCacheLookupStatusGet(txnp, &obj_status) == TS_ERROR) {
194     TSError("[%s] Couldn't get cache status of object", __FUNCTION__);
195     return;
196   }
197 
198   if (obj_status != TS_CACHE_LOOKUP_HIT_STALE && obj_status != TS_CACHE_LOOKUP_HIT_FRESH) {
199     return;
200   }
201 
202   if (TSHttpTxnCachedRespGet(txnp, &bufp, &hdrp) != TS_SUCCESS) {
203     TSError("[%s] Couldn't get cache resp", __FUNCTION__);
204     return;
205   }
206 
207   code = TSHttpHdrStatusGet(bufp, hdrp);
208   if (code != TS_HTTP_STATUS_OK) {
209     goto release;
210   }
211 
212   n = 0;
213 
214   cl_field = TSMimeHdrFieldFind(bufp, hdrp, TS_MIME_FIELD_CONTENT_LENGTH, TS_MIME_LEN_CONTENT_LENGTH);
215   if (cl_field) {
216     n = TSMimeHdrFieldValueInt64Get(bufp, hdrp, cl_field, -1);
217     TSHandleMLocRelease(bufp, hdrp, cl_field);
218   }
219 
220   if (n <= 0) {
221     goto release;
222   }
223 
224   mc->cl = n;
225   mp4_add_transform(mc, txnp);
226 
227 release:
228 
229   TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdrp);
230 }
231 
232 static void
mp4_read_response(Mp4Context * mc,TSHttpTxn txnp)233 mp4_read_response(Mp4Context *mc, TSHttpTxn txnp)
234 {
235   TSMBuffer bufp;
236   TSMLoc hdrp;
237   TSMLoc cl_field;
238   TSHttpStatus status;
239   int64_t n;
240 
241   if (TSHttpTxnServerRespGet(txnp, &bufp, &hdrp) != TS_SUCCESS) {
242     TSError("[%s] could not get request os data", __FUNCTION__);
243     return;
244   }
245 
246   status = TSHttpHdrStatusGet(bufp, hdrp);
247   if (status != TS_HTTP_STATUS_OK) {
248     goto release;
249   }
250 
251   n        = 0;
252   cl_field = TSMimeHdrFieldFind(bufp, hdrp, TS_MIME_FIELD_CONTENT_LENGTH, TS_MIME_LEN_CONTENT_LENGTH);
253   if (cl_field) {
254     n = TSMimeHdrFieldValueInt64Get(bufp, hdrp, cl_field, -1);
255     TSHandleMLocRelease(bufp, hdrp, cl_field);
256   }
257 
258   if (n <= 0) {
259     goto release;
260   }
261 
262   mc->cl = n;
263   mp4_add_transform(mc, txnp);
264 
265 release:
266 
267   TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdrp);
268 }
269 
270 static void
mp4_add_transform(Mp4Context * mc,TSHttpTxn txnp)271 mp4_add_transform(Mp4Context *mc, TSHttpTxn txnp)
272 {
273   TSVConn connp;
274 
275   if (mc->transform_added) {
276     return;
277   }
278 
279   mc->mtc = new Mp4TransformContext(mc->start, mc->cl);
280 
281   TSHttpTxnUntransformedRespCache(txnp, 1);
282   TSHttpTxnTransformedRespCache(txnp, 0);
283 
284   connp = TSTransformCreate(mp4_transform_entry, txnp);
285   TSContDataSet(connp, mc);
286   TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, connp);
287 
288   mc->transform_added = true;
289 }
290 
291 static int
mp4_transform_entry(TSCont contp,TSEvent event,void *)292 mp4_transform_entry(TSCont contp, TSEvent event, void * /* edata ATS_UNUSED */)
293 {
294   TSVIO input_vio;
295   Mp4Context *mc = static_cast<Mp4Context *>(TSContDataGet(contp));
296 
297   if (TSVConnClosedGet(contp)) {
298     TSContDestroy(contp);
299     return 0;
300   }
301 
302   switch (event) {
303   case TS_EVENT_ERROR:
304     input_vio = TSVConnWriteVIOGet(contp);
305     TSContCall(TSVIOContGet(input_vio), TS_EVENT_ERROR, input_vio);
306     break;
307 
308   case TS_EVENT_VCONN_WRITE_COMPLETE:
309     TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1);
310     break;
311 
312   case TS_EVENT_VCONN_WRITE_READY:
313   default:
314     mp4_transform_handler(contp, mc);
315     break;
316   }
317 
318   return 0;
319 }
320 
321 static int
mp4_transform_handler(TSCont contp,Mp4Context * mc)322 mp4_transform_handler(TSCont contp, Mp4Context *mc)
323 {
324   TSVConn output_conn;
325   TSVIO input_vio;
326   TSIOBufferReader input_reader;
327   int64_t avail, toread, need, upstream_done;
328   int ret;
329   bool write_down;
330   Mp4TransformContext *mtc;
331 
332   mtc = mc->mtc;
333 
334   output_conn  = TSTransformOutputVConnGet(contp);
335   input_vio    = TSVConnWriteVIOGet(contp);
336   input_reader = TSVIOReaderGet(input_vio);
337 
338   if (!TSVIOBufferGet(input_vio)) {
339     if (mtc->output.buffer) {
340       TSVIONBytesSet(mtc->output.vio, mtc->total);
341       TSVIOReenable(mtc->output.vio);
342     }
343     return 1;
344   }
345 
346   avail         = TSIOBufferReaderAvail(input_reader);
347   upstream_done = TSVIONDoneGet(input_vio);
348 
349   TSIOBufferCopy(mtc->res_buffer, input_reader, avail, 0);
350   TSIOBufferReaderConsume(input_reader, avail);
351   TSVIONDoneSet(input_vio, upstream_done + avail);
352 
353   toread     = TSVIONTodoGet(input_vio);
354   write_down = false;
355 
356   if (!mtc->parse_over) {
357     ret = mp4_parse_meta(mtc, toread <= 0);
358     if (ret == 0) {
359       goto trans;
360     }
361 
362     mtc->parse_over    = true;
363     mtc->output.buffer = TSIOBufferCreate();
364     mtc->output.reader = TSIOBufferReaderAlloc(mtc->output.buffer);
365 
366     if (ret < 0) {
367       mtc->output.vio    = TSVConnWrite(output_conn, contp, mtc->output.reader, mc->cl);
368       mtc->raw_transform = true;
369 
370     } else {
371       mtc->output.vio = TSVConnWrite(output_conn, contp, mtc->output.reader, mtc->content_length);
372     }
373   }
374 
375   avail = TSIOBufferReaderAvail(mtc->res_reader);
376 
377   if (mtc->raw_transform) {
378     if (avail > 0) {
379       TSIOBufferCopy(mtc->output.buffer, mtc->res_reader, avail, 0);
380       TSIOBufferReaderConsume(mtc->res_reader, avail);
381       mtc->total += avail;
382       write_down = true;
383     }
384 
385   } else {
386     // copy the new meta data
387     if (mtc->total < mtc->meta_length) {
388       TSIOBufferCopy(mtc->output.buffer, mtc->mm.out_handle.reader, mtc->meta_length, 0);
389       mtc->total += mtc->meta_length;
390       write_down = true;
391     }
392 
393     // ignore useless part
394     if (mtc->pos < mtc->tail) {
395       avail = TSIOBufferReaderAvail(mtc->res_reader);
396       need  = mtc->tail - mtc->pos;
397       if (need > avail) {
398         need = avail;
399       }
400 
401       if (need > 0) {
402         TSIOBufferReaderConsume(mtc->res_reader, need);
403         mtc->pos += need;
404       }
405     }
406 
407     // copy the video & audio data
408     if (mtc->pos >= mtc->tail) {
409       avail = TSIOBufferReaderAvail(mtc->res_reader);
410 
411       if (avail > 0) {
412         TSIOBufferCopy(mtc->output.buffer, mtc->res_reader, avail, 0);
413         TSIOBufferReaderConsume(mtc->res_reader, avail);
414 
415         mtc->pos += avail;
416         mtc->total += avail;
417         write_down = true;
418       }
419     }
420   }
421 
422 trans:
423 
424   if (write_down) {
425     TSVIOReenable(mtc->output.vio);
426   }
427 
428   if (toread > 0) {
429     TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_READY, input_vio);
430 
431   } else {
432     TSVIONBytesSet(mtc->output.vio, mtc->total);
433     TSContCall(TSVIOContGet(input_vio), TS_EVENT_VCONN_WRITE_COMPLETE, input_vio);
434   }
435 
436   return 1;
437 }
438 
439 static int
mp4_parse_meta(Mp4TransformContext * mtc,bool body_complete)440 mp4_parse_meta(Mp4TransformContext *mtc, bool body_complete)
441 {
442   int ret;
443   int64_t avail, bytes;
444   TSIOBufferBlock blk;
445   const char *data;
446   Mp4Meta *mm;
447 
448   mm = &mtc->mm;
449 
450   avail = TSIOBufferReaderAvail(mtc->dup_reader);
451   blk   = TSIOBufferReaderStart(mtc->dup_reader);
452 
453   while (blk != nullptr) {
454     data = TSIOBufferBlockReadStart(blk, mtc->dup_reader, &bytes);
455     if (bytes > 0) {
456       TSIOBufferWrite(mm->meta_buffer, data, bytes);
457     }
458 
459     blk = TSIOBufferBlockNext(blk);
460   }
461 
462   TSIOBufferReaderConsume(mtc->dup_reader, avail);
463 
464   ret = mm->parse_meta(body_complete);
465 
466   if (ret > 0) { // meta success
467     mtc->tail           = mm->start_pos;
468     mtc->content_length = mm->content_length;
469     mtc->meta_length    = TSIOBufferReaderAvail(mm->out_handle.reader);
470   }
471 
472   if (ret != 0) {
473     TSIOBufferReaderFree(mtc->dup_reader);
474     mtc->dup_reader = nullptr;
475   }
476 
477   return ret;
478 }
479 
480 static char *
ts_arg(const char * param,size_t param_len,const char * key,size_t key_len,size_t * val_len)481 ts_arg(const char *param, size_t param_len, const char *key, size_t key_len, size_t *val_len)
482 {
483   const char *p, *last;
484   const char *val;
485 
486   *val_len = 0;
487 
488   if (!param || !param_len) {
489     return nullptr;
490   }
491 
492   p    = param;
493   last = p + param_len;
494 
495   for (; p < last; p++) {
496     p = static_cast<char *>(memmem(p, last - p, key, key_len));
497 
498     if (p == nullptr) {
499       return nullptr;
500     }
501 
502     if ((p == param || *(p - 1) == '&') && *(p + key_len) == '=') {
503       val = p + key_len + 1;
504 
505       p = (char *)memchr(p, '&', last - p);
506 
507       if (p == nullptr) {
508         p = param + param_len;
509       }
510 
511       *val_len = p - val;
512 
513       return const_cast<char *>(val);
514     }
515   }
516 
517   return nullptr;
518 }
519