1 /** @file
2  *
3  * Try not to download the same file twice.  Improve cache efficiency
4  * and speed up downloads.
5  *
6  * @section license License
7  *
8  * Licensed to the Apache Software Foundation (ASF) under one or more
9  * contributor license agreements.  See the NOTICE file distributed
10  * with this work for additional information regarding copyright
11  * ownership.  The ASF licenses this file to you under the Apache
12  * License, Version 2.0 (the "License"); you may not use this file
13  * except in compliance with the License.  You may obtain a copy of
14  * the License at
15  *
16  *    http://www.apache.org/licenses/LICENSE-2.0
17  *
18  * Unless required by applicable law or agreed to in writing, software
19  * distributed under the License is distributed on an "AS IS" BASIS,
20  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
21  * implied.  See the License for the specific language governing
22  * permissions and limitations under the License. */
23 
24 #include <strings.h>
25 
26 #include <openssl/sha.h>
27 
28 #include "ts/ts.h"
29 
30 /* Implement TS_HTTP_READ_RESPONSE_HDR_HOOK to implement a null
31  * transformation.  Compute the SHA-256 digest of the content, write
32  * it to the cache and store the request URL at that key.
33  *
34  * Implement TS_HTTP_SEND_RESPONSE_HDR_HOOK to check the Location and
35  * Digest headers.  Use TSCacheRead() to check if the URL in the
36  * Location header is already cached.  If not, potentially rewrite
37  * that header.  Do this after responses are cached because the cache
38  * will change.
39  *
40  * More details are on the [wiki page] in the Traffic Server wiki.
41  *
42  *    [wiki page]   https://cwiki.apache.org/confluence/display/TS/Metalink */
43 
44 /* TSCacheWrite() and TSVConnWrite() data: Write the digest to the
45  * cache and store the request URL at that key */
46 
47 typedef struct {
48   TSHttpTxn txnp;
49 
50   TSCacheKey key;
51 
52   TSVConn connp;
53   TSIOBuffer cache_bufp;
54 
55 } WriteData;
56 
57 /* TSTransformCreate() data: Compute the SHA-256 digest of the content */
58 
59 typedef struct {
60   TSHttpTxn txnp;
61 
62   /* Null transformation */
63   TSIOBuffer output_bufp;
64   TSVIO output_viop;
65 
66   /* Message digest handle */
67   SHA256_CTX c;
68 
69 } TransformData;
70 
71 /* TSCacheRead() and TSVConnRead() data: Check the Location and Digest
72  * headers */
73 
74 typedef struct {
75   TSHttpTxn txnp;
76 
77   TSMBuffer resp_bufp;
78   TSMLoc hdr_loc;
79 
80   /* Location header */
81   TSMLoc location_loc;
82 
83   /* Cache key */
84   TSMLoc url_loc;
85   TSCacheKey key;
86 
87   /* Digest header */
88   TSMLoc digest_loc;
89 
90   /* Digest header field value index */
91   int idx;
92 
93   TSVConn connp;
94   TSIOBuffer cache_bufp;
95 
96   const char *value;
97   int64_t length;
98 
99 } SendData;
100 
101 /* Implement TS_HTTP_READ_RESPONSE_HDR_HOOK to implement a null
102  * transformation */
103 
104 /* Write the digest to the cache and store the request URL at that key */
105 
106 static int
cache_open_write(TSCont contp,void * edata)107 cache_open_write(TSCont contp, void *edata)
108 {
109   TSMBuffer req_bufp;
110 
111   TSMLoc hdr_loc;
112   TSMLoc url_loc;
113 
114   char *value;
115   int length;
116 
117   WriteData *data = static_cast<WriteData *>(TSContDataGet(contp));
118   data->connp     = static_cast<TSVConn>(edata);
119 
120   TSCacheKeyDestroy(data->key);
121 
122   if (TSHttpTxnClientReqGet(data->txnp, &req_bufp, &hdr_loc) != TS_SUCCESS) {
123     TSError("[metalink] Couldn't retrieve client request header");
124 
125     TSContDestroy(contp);
126 
127     TSfree(data);
128 
129     return 0;
130   }
131 
132   if (TSHttpHdrUrlGet(req_bufp, hdr_loc, &url_loc) != TS_SUCCESS) {
133     TSContDestroy(contp);
134 
135     TSfree(data);
136 
137     TSHandleMLocRelease(req_bufp, TS_NULL_MLOC, hdr_loc);
138 
139     return 0;
140   }
141 
142   /* Allocation!  Must free! */
143   value = TSUrlStringGet(req_bufp, url_loc, &length);
144   if (!value) {
145     TSContDestroy(contp);
146 
147     TSfree(data);
148 
149     TSHandleMLocRelease(req_bufp, hdr_loc, url_loc);
150     TSHandleMLocRelease(req_bufp, TS_NULL_MLOC, hdr_loc);
151 
152     return 0;
153   }
154 
155   TSHandleMLocRelease(req_bufp, hdr_loc, url_loc);
156   TSHandleMLocRelease(req_bufp, TS_NULL_MLOC, hdr_loc);
157 
158   /* Store the request URL */
159 
160   data->cache_bufp         = TSIOBufferCreate();
161   TSIOBufferReader readerp = TSIOBufferReaderAlloc(data->cache_bufp);
162 
163   int nbytes = TSIOBufferWrite(data->cache_bufp, value, length);
164 
165   TSfree(value);
166 
167   /* Reentrant!  Reuse the TSCacheWrite() continuation. */
168   TSVConnWrite(data->connp, contp, readerp, nbytes);
169 
170   return 0;
171 }
172 
173 /* Do nothing */
174 
175 static int
cache_open_write_failed(TSCont contp,void *)176 cache_open_write_failed(TSCont contp, void * /* edata ATS_UNUSED */)
177 {
178   WriteData *data = static_cast<WriteData *>(TSContDataGet(contp));
179   TSContDestroy(contp);
180 
181   TSCacheKeyDestroy(data->key);
182   TSfree(data);
183 
184   return 0;
185 }
186 
187 static int
write_vconn_write_complete(TSCont contp,void *)188 write_vconn_write_complete(TSCont contp, void * /* edata ATS_UNUSED */)
189 {
190   WriteData *data = static_cast<WriteData *>(TSContDataGet(contp));
191   TSContDestroy(contp);
192 
193   /* The object is not committed to the cache until the VConnection is
194    * closed.  When all the data has been transferred, the user (contp)
195    * must do a TSVConnClose() */
196   TSVConnClose(data->connp);
197 
198   TSIOBufferDestroy(data->cache_bufp);
199   TSfree(data);
200 
201   return 0;
202 }
203 
204 /* TSCacheWrite() and TSVConnWrite() handler: Write the digest to the
205  * cache and store the request URL at that key */
206 
207 static int
write_handler(TSCont contp,TSEvent event,void * edata)208 write_handler(TSCont contp, TSEvent event, void *edata)
209 {
210   switch (event) {
211   case TS_EVENT_CACHE_OPEN_WRITE:
212     return cache_open_write(contp, edata);
213 
214   case TS_EVENT_CACHE_OPEN_WRITE_FAILED:
215     return cache_open_write_failed(contp, edata);
216 
217   case TS_EVENT_VCONN_WRITE_COMPLETE:
218     return write_vconn_write_complete(contp, edata);
219 
220   default:
221     TSAssert(!"Unexpected event");
222   }
223 
224   return 0;
225 }
226 
227 /* Copy content from the input buffer to the output buffer without
228  * modification and feed it through the message digest at the same
229  * time.
230  *
231  *    1.  Check if we are "closed" before doing anything else to avoid
232  *        errors.
233  *
234  *    2.  Then deal with any input that's available now.
235  *
236  *    3.  Check if the input is complete after dealing with any
237  *        available input in case it was the last of it.  If it is
238  *        complete, tell downstream, thank upstream, and finish
239  *        computing the digest.  Otherwise either wait for more input
240  *        or abort if upstream is "closed".
241  *
242  * The handler is guaranteed to get called at least once, even if the
243  * response is 304 Not Modified, so we are guaranteed an opportunity
244  * to clean up e.g. data that we allocated when we called
245  * TSTransformCreate().
246  *
247  * TS_EVENT_VCONN_WRITE_READY and TS_EVENT_VCONN_WRITE_COMPLETE events
248  * are sent from downstream, e.g. by
249  * TransformTerminus::handle_event().  TS_EVENT_IMMEDIATE events are
250  * sent by INKVConnInternal::do_io_write(),
251  * INKVConnInternal::do_io_close(), and INKVConnInternal::reenable()
252  * which are called from upstream, e.g. by
253  * TransformVConnection::do_io_write(),
254  * TransformVConnection::do_io_close(), and
255  * HttpTunnel::producer_handler().
256  *
257  * Clean up the output buffer on TS_EVENT_VCONN_WRITE_COMPLETE and not
258  * before.  We are guaranteed a TS_EVENT_VCONN_WRITE_COMPLETE event
259  * *unless* we are "closed".  In that case we instead get a
260  * TS_EVENT_IMMEDIATE event where TSVConnClosedGet() is one.  We'll
261  * only ever get one event where TSVConnClosedGet() is one and it will
262  * be our last, so we *must* check for this case and clean up then
263  * too.  Because we'll only ever get one such event and it will be our
264  * last, there's no risk of double freeing.
265  *
266  * The response headers get sent when TSVConnWrite() gets called and
267  * not before.  (We could potentially edit them until then.)
268  *
269  * The events say nothing about the state of the input.  Gather this
270  * instead from TSVConnClosedGet(), TSVIOReaderGet(), and
271  * TSVIONTodoGet() and handle the end of the input independently from
272  * the TS_EVENT_VCONN_WRITE_COMPLETE event from downstream.
273  *
274  * When TSVConnClosedGet() is one, *we* are "closed".  We *must* check
275  * for this case, if only to clean up allocated data.  Some state is
276  * already inconsistent.  (This happens when the response is 304 Not
277  * Modified or when the client or origin disconnect before the message
278  * is complete.)
279  *
280  * When TSVIOReaderGet() is NULL, upstream is "closed".  In that case
281  * it's clearly an error to call TSIOBufferReaderAvail(), it's also an
282  * error at that point to send any events upstream with TSContCall().
283  * (This happens when the content length is zero or when we get the
284  * final chunk of a chunked response.)
285  *
286  * The input is complete only when TSVIONTodoGet() is zero.  (Don't
287  * update the downstream nbytes otherwise!)  Update the downstream
288  * nbytes when the input is complete in case the response is chunked
289  * (in which case nbytes is unknown until then).  Downstream will
290  * (normally) send the TS_EVENT_VCONN_WRITE_COMPLETE event (and the
291  * final chunk if the response is chunked) when ndone equals nbytes
292  * and not before.
293  *
294  * Send our own TS_EVENT_VCONN_WRITE_COMPLETE event upstream when the
295  * input is complete otherwise HttpSM::update_stats() won't get called
296  * and the transaction won't get logged.  (If there are upstream
297  * transformations they won't get a chance to clean up otherwise!)
298  *
299  * Summary of the cases each event can fall into:
300  *
301  *    Closed        *We* are "closed".  Clean up allocated data.
302  *     │
303  *     ├ Start      First (and last) time the handler was called.
304  *     │            (This happens when the response is 304 Not
305  *     │            Modified.)
306  *     │
307  *     └ Not start  (This happens when the client or origin disconnect
308  *                  before the message is complete.)
309  *
310  *    Start         First time the handler was called.  Initialize
311  *     │            data here because we can't call TSVConnWrite()
312  *     │            before TS_HTTP_RESPONSE_TRANSFORM_HOOK.
313  *     │
314  *     ├ Content length
315  *     │
316  *     └ Chunked response
317  *
318  *    Upstream closed
319  *                  (This happens when the content length is zero or
320  *                  when we get the final chunk of a chunked
321  *                  response.)
322  *
323  *    Available input
324  *
325  *    Input complete
326  *     │
327  *     ├ Deja vu    There might be multiple TS_EVENT_IMMEDIATE events
328  *     │            between the end of the input and the
329  *     │            TS_EVENT_VCONN_WRITE_COMPLETE event from
330  *     │            downstream.
331  *     │
332  *     └ Not deja vu
333  *                  Tell downstream and thank upstream.
334  *
335  *    Downstream complete
336  *                  Clean up the output buffer. */
337 
338 static int
vconn_write_ready(TSCont contp,void *)339 vconn_write_ready(TSCont contp, void * /* edata ATS_UNUSED */)
340 {
341   const char *value;
342   int64_t length;
343 
344   char digest[32]; /* SHA-256 */
345 
346   TransformData *transform_data = static_cast<TransformData *>(TSContDataGet(contp));
347 
348   /* Check if we are "closed" before doing anything else to avoid
349    * errors.  We *must* check for this case, if only to clean up
350    * allocated data.  Some state is already inconsistent.  (This
351    * happens if the response is 304 Not Modified or if the client or
352    * origin disconnect before the message is complete.) */
353   int closed = TSVConnClosedGet(contp);
354   if (closed) {
355     TSContDestroy(contp);
356 
357     /* Avoid failed assert "sdk_sanity_check_iocore_structure(bufp) ==
358      * TS_SUCCESS" in TSIOBufferDestroy() if the response is 304 Not
359      * Modified */
360     if (transform_data->output_bufp) {
361       TSIOBufferDestroy(transform_data->output_bufp);
362     }
363 
364     TSfree(transform_data);
365 
366     return 0;
367   }
368 
369   TSVIO input_viop = TSVConnWriteVIOGet(contp);
370 
371   /* Initialize data here because we can't call TSVConnWrite() before
372    * TS_HTTP_RESPONSE_TRANSFORM_HOOK */
373   if (!transform_data->output_bufp) {
374     TSVConn output_connp = TSTransformOutputVConnGet(contp);
375 
376     transform_data->output_bufp = TSIOBufferCreate();
377     TSIOBufferReader readerp    = TSIOBufferReaderAlloc(transform_data->output_bufp);
378 
379     /* Determines the Content-Length header (or a chunked response) */
380 
381     /* Reentrant!  Avoid failed assert "nbytes >= 0" if the response
382      * is chunked. */
383     int nbytes                  = TSVIONBytesGet(input_viop);
384     transform_data->output_viop = TSVConnWrite(output_connp, contp, readerp, nbytes < 0 ? INT64_MAX : nbytes);
385 
386     SHA256_Init(&transform_data->c);
387   }
388 
389   /* Then deal with any input that's available now.  Avoid failed
390    * assert "sdk_sanity_check_iocore_structure(readerp) == TS_SUCCESS"
391    * in TSIOBufferReaderAvail() if the content length is zero or when
392    * we get the final chunk of a chunked response. */
393   TSIOBufferReader readerp = TSVIOReaderGet(input_viop);
394   if (readerp) {
395     int avail = TSIOBufferReaderAvail(readerp);
396     if (avail) {
397       TSIOBufferCopy(transform_data->output_bufp, readerp, avail, 0);
398 
399       /* Feed content to the message digest */
400       TSIOBufferBlock blockp = TSIOBufferReaderStart(readerp);
401       while (blockp) {
402         /* No allocation? */
403         value = TSIOBufferBlockReadStart(blockp, readerp, &length);
404         SHA256_Update(&transform_data->c, value, length);
405 
406         blockp = TSIOBufferBlockNext(blockp);
407       }
408 
409       TSIOBufferReaderConsume(readerp, avail);
410 
411       /* Call TSVIONDoneSet() for TSVIONTodoGet() condition */
412       int ndone = TSVIONDoneGet(input_viop);
413       TSVIONDoneSet(input_viop, ndone + avail);
414     }
415   }
416 
417   /* Check if the input is complete after dealing with any available
418    * input in case it was the last of it */
419   int ntodo = TSVIONTodoGet(input_viop);
420   if (ntodo) {
421     TSVIOReenable(transform_data->output_viop);
422 
423     TSContCall(TSVIOContGet(input_viop), TS_EVENT_VCONN_WRITE_READY, input_viop);
424 
425     /* Don't finish computing the digest (and tell downstream and thank
426      * upstream) more than once!  There might be multiple
427      * TS_EVENT_IMMEDIATE events between the end of the input and the
428      * TS_EVENT_VCONN_WRITE_COMPLETE event from downstream, e.g.
429      * INKVConnInternal::reenable() is called by
430      * HttpTunnel::producer_handler() when more input is available and
431      * TransformVConnection::do_io_shutdown() is called by
432      * HttpSM::tunnel_handler_transform_write() when we send our own
433      * TS_EVENT_VCONN_WRITE_COMPLETE event upstream. */
434   } else if (transform_data->txnp) {
435     int ndone = TSVIONDoneGet(input_viop);
436     TSVIONBytesSet(transform_data->output_viop, ndone);
437 
438     TSVIOReenable(transform_data->output_viop);
439 
440     /* Avoid failed assert "c->alive == true" in TSContCall() if the
441      * content length is zero or when we get the final chunk of a
442      * chunked response */
443     if (readerp) {
444       TSContCall(TSVIOContGet(input_viop), TS_EVENT_VCONN_WRITE_COMPLETE, input_viop);
445     }
446 
447     /* Write the digest to the cache */
448 
449     SHA256_Final(reinterpret_cast<unsigned char *>(digest), &transform_data->c);
450 
451     WriteData *write_data = static_cast<WriteData *>(TSmalloc(sizeof(WriteData)));
452     write_data->txnp      = transform_data->txnp;
453 
454     /* Don't finish computing the digest more than once! */
455     transform_data->txnp = nullptr;
456 
457     write_data->key = TSCacheKeyCreate();
458     if (TSCacheKeyDigestSet(write_data->key, digest, sizeof(digest)) != TS_SUCCESS) {
459       TSCacheKeyDestroy(write_data->key);
460       TSfree(write_data);
461 
462       return 0;
463     }
464 
465     /* Can't reuse the TSTransformCreate() continuation because we
466      * don't know whether to destroy it in
467      * cache_open_write()/cache_open_write_failed() */
468     contp = TSContCreate(write_handler, nullptr);
469     TSContDataSet(contp, write_data);
470 
471     /* Reentrant! */
472     TSCacheWrite(contp, write_data->key);
473   }
474 
475   return 0;
476 }
477 
478 /* TSTransformCreate() handler: Compute the SHA-256 digest of the
479  * content */
480 
481 static int
transform_handler(TSCont contp,TSEvent event,void * edata)482 transform_handler(TSCont contp, TSEvent event, void *edata)
483 {
484   switch (event) {
485   case TS_EVENT_IMMEDIATE:
486   case TS_EVENT_VCONN_WRITE_READY:
487     return vconn_write_ready(contp, edata);
488 
489   case TS_EVENT_VCONN_WRITE_COMPLETE:
490     TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1);
491     break;
492   default:
493     TSAssert(!"Unexpected event");
494   }
495 
496   return 0;
497 }
498 
499 /* Compute the SHA-256 digest of the content, write it to the cache
500  * and store the request URL at that key */
501 
502 static int
http_read_response_hdr(TSCont,void * edata)503 http_read_response_hdr(TSCont /* contp ATS_UNUSED */, void *edata)
504 {
505   TransformData *data = static_cast<TransformData *>(TSmalloc(sizeof(TransformData)));
506   data->txnp          = static_cast<TSHttpTxn>(edata);
507 
508   /* Can't initialize data here because we can't call TSVConnWrite()
509    * before TS_HTTP_RESPONSE_TRANSFORM_HOOK */
510   data->output_bufp = nullptr;
511 
512   TSVConn connp = TSTransformCreate(transform_handler, data->txnp);
513   TSContDataSet(connp, data);
514 
515   TSHttpTxnHookAdd(data->txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, connp);
516 
517   TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
518 
519   return 0;
520 }
521 
522 /* Implement TS_HTTP_SEND_RESPONSE_HDR_HOOK to check the Location and
523  * Digest headers */
524 
525 /* Read the URL stored at the digest */
526 
527 static int
cache_open_read(TSCont contp,void * edata)528 cache_open_read(TSCont contp, void *edata)
529 {
530   SendData *data = static_cast<SendData *>(TSContDataGet(contp));
531   data->connp    = static_cast<TSVConn>(edata);
532 
533   data->cache_bufp = TSIOBufferCreate();
534 
535   /* Reentrant!  Reuse the TSCacheRead() continuation. */
536   TSVConnRead(data->connp, contp, data->cache_bufp, INT64_MAX);
537 
538   return 0;
539 }
540 
541 /* Do nothing, just reenable the response */
542 
543 static int
cache_open_read_failed(TSCont contp,void *)544 cache_open_read_failed(TSCont contp, void * /* edata ATS_UNUSED */)
545 {
546   SendData *data = static_cast<SendData *>(TSContDataGet(contp));
547   TSContDestroy(contp);
548 
549   TSCacheKeyDestroy(data->key);
550 
551   TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->url_loc);
552   TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->location_loc);
553   TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
554 
555   TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
556   TSfree(data);
557 
558   return 0;
559 }
560 
561 /* TSCacheRead() handler: Check if the URL stored at the digest is
562  * cached */
563 
564 static int
rewrite_handler(TSCont contp,TSEvent event,void *)565 rewrite_handler(TSCont contp, TSEvent event, void * /* edata ATS_UNUSED */)
566 {
567   SendData *data = static_cast<SendData *>(TSContDataGet(contp));
568   TSContDestroy(contp);
569 
570   TSCacheKeyDestroy(data->key);
571 
572   switch (event) {
573   /* Yes: Rewrite the Location header and reenable the response */
574   case TS_EVENT_CACHE_OPEN_READ:
575 
576     TSMimeHdrFieldValuesClear(data->resp_bufp, data->hdr_loc, data->location_loc);
577     TSMimeHdrFieldValueStringInsert(data->resp_bufp, data->hdr_loc, data->location_loc, -1, data->value, data->length);
578 
579     break;
580 
581   /* No: Do nothing, just reenable the response */
582   case TS_EVENT_CACHE_OPEN_READ_FAILED:
583     break;
584 
585   default:
586     TSAssert(!"Unexpected event");
587   }
588 
589   TSIOBufferDestroy(data->cache_bufp);
590 
591   TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->location_loc);
592   TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
593 
594   TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
595   TSfree(data);
596 
597   return 0;
598 }
599 
600 /* Read the URL stored at the digest */
601 
602 static int
vconn_read_ready(TSCont contp,void *)603 vconn_read_ready(TSCont contp, void * /* edata ATS_UNUSED */)
604 {
605   SendData *data = static_cast<SendData *>(TSContDataGet(contp));
606   TSContDestroy(contp);
607 
608   TSVConnClose(data->connp);
609 
610   TSIOBufferReader readerp = TSIOBufferReaderAlloc(data->cache_bufp);
611 
612   TSIOBufferBlock blockp = TSIOBufferReaderStart(readerp);
613 
614   /* No allocation, freed with data->cache_bufp? */
615   const char *value = data->value = TSIOBufferBlockReadStart(blockp, readerp, &data->length);
616 
617   /* The start pointer is both an input and an output parameter.
618    * After a successful parse the start pointer equals the end
619    * pointer. */
620   if (TSUrlParse(data->resp_bufp, data->url_loc, &value, value + data->length) != TS_PARSE_DONE) {
621     TSIOBufferDestroy(data->cache_bufp);
622 
623     TSCacheKeyDestroy(data->key);
624 
625     TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->url_loc);
626     TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->location_loc);
627     TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
628 
629     TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
630     TSfree(data);
631 
632     return 0;
633   }
634 
635   if (TSCacheKeyDigestFromUrlSet(data->key, data->url_loc) != TS_SUCCESS) {
636     TSIOBufferDestroy(data->cache_bufp);
637 
638     TSCacheKeyDestroy(data->key);
639 
640     TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->url_loc);
641     TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->location_loc);
642     TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
643 
644     TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
645     TSfree(data);
646 
647     return 0;
648   }
649 
650   TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->url_loc);
651 
652   /* Check if the URL stored at the digest is cached */
653 
654   contp = TSContCreate(rewrite_handler, nullptr);
655   TSContDataSet(contp, data);
656 
657   /* Reentrant!  (Particularly in case of a cache miss.)
658    * rewrite_handler() will clean up the TSVConnRead() buffer so be
659    * sure to close this virtual connection or CacheVC::openReadMain()
660    * will continue operating on it! */
661   TSCacheRead(contp, data->key);
662 
663   return 0;
664 }
665 
666 /* TSCacheRead() and TSVConnRead() handler: Check if the digest
667  * already exists in the cache */
668 
669 static int
digest_handler(TSCont contp,TSEvent event,void * edata)670 digest_handler(TSCont contp, TSEvent event, void *edata)
671 {
672   switch (event) {
673   /* Yes: Read the URL stored at that key */
674   case TS_EVENT_CACHE_OPEN_READ:
675     return cache_open_read(contp, edata);
676 
677   /* No: Do nothing, just reenable the response */
678   case TS_EVENT_CACHE_OPEN_READ_FAILED:
679     return cache_open_read_failed(contp, edata);
680 
681   case TS_EVENT_VCONN_READ_READY:
682     return vconn_read_ready(contp, edata);
683 
684   default:
685     TSAssert(!"Unexpected event");
686   }
687 
688   return 0;
689 }
690 
691 /* TSCacheRead() handler: Check if the Location URL is already cached */
692 
693 static int
location_handler(TSCont contp,TSEvent event,void *)694 location_handler(TSCont contp, TSEvent event, void * /* edata ATS_UNUSED */)
695 {
696   const char *value;
697   int length;
698 
699   char digest[33]; /* ATS_BASE64_DECODE_DSTLEN() */
700 
701   SendData *data = static_cast<SendData *>(TSContDataGet(contp));
702   TSContDestroy(contp);
703 
704   switch (event) {
705   /* Yes: Do nothing, just reenable the response */
706   case TS_EVENT_CACHE_OPEN_READ:
707     break;
708 
709   /* No: Check if the digest already exists in the cache */
710   case TS_EVENT_CACHE_OPEN_READ_FAILED:
711 
712     /* No allocation, freed with data->resp_bufp? */
713     value = TSMimeHdrFieldValueStringGet(data->resp_bufp, data->hdr_loc, data->digest_loc, data->idx, &length);
714     if (TSBase64Decode(value + 8, length - 8, reinterpret_cast<unsigned char *>(digest), sizeof(digest), nullptr) != TS_SUCCESS ||
715         TSCacheKeyDigestSet(data->key, digest, 32 /* SHA-256 */) != TS_SUCCESS) {
716       break;
717     }
718 
719     TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->digest_loc);
720 
721     /* Check if the digest already exists in the cache */
722 
723     contp = TSContCreate(digest_handler, nullptr);
724     TSContDataSet(contp, data);
725 
726     /* Reentrant! */
727     TSCacheRead(contp, data->key);
728 
729     return 0;
730 
731   default:
732     TSAssert(!"Unexpected event");
733   }
734 
735   TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->digest_loc);
736 
737   TSCacheKeyDestroy(data->key);
738 
739   TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->url_loc);
740   TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->location_loc);
741   TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
742 
743   TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
744   TSfree(data);
745 
746   return 0;
747 }
748 
749 /* Use TSCacheRead() to check if the URL in the Location header is
750  * already cached.  If not, potentially rewrite that header.  Do this
751  * after responses are cached because the cache will change. */
752 
753 static int
http_send_response_hdr(TSCont contp,void * edata)754 http_send_response_hdr(TSCont contp, void *edata)
755 {
756   const char *value;
757   int length;
758 
759   SendData *data = static_cast<SendData *>(TSmalloc(sizeof(SendData)));
760   data->txnp     = static_cast<TSHttpTxn>(edata);
761 
762   if (TSHttpTxnClientRespGet(data->txnp, &data->resp_bufp, &data->hdr_loc) != TS_SUCCESS) {
763     TSError("[metalink] Couldn't retrieve client response header");
764 
765     TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
766     TSfree(data);
767 
768     return 0;
769   }
770 
771   /* If Instance Digests are not provided by the Metalink servers, the
772    * Link header fields pertaining to this specification MUST be
773    * ignored */
774 
775   /* Metalinks contain whole file hashes as described in Section 6,
776    * and MUST include SHA-256, as specified in [FIPS-180-3] */
777 
778   /* Assumption: We want to minimize cache reads, so check first that
779    *
780    *    1.  the response has a Location header and
781    *
782    *    2.  the response has a Digest header.
783    *
784    * Then scan if the URL or digest already exist in the cache. */
785 
786   /* If the response has a Location header */
787   data->location_loc = TSMimeHdrFieldFind(data->resp_bufp, data->hdr_loc, TS_MIME_FIELD_LOCATION, TS_MIME_LEN_LOCATION);
788   if (!data->location_loc) {
789     TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
790 
791     TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
792     TSfree(data);
793 
794     return 0;
795   }
796 
797   TSUrlCreate(data->resp_bufp, &data->url_loc);
798 
799   /* If we can't parse or lookup the Location URL, should we still
800    * check if the response has a Digest header?  No: Can't parse or
801    * lookup the URL in the Location header is an error. */
802 
803   /* No allocation, freed with data->resp_bufp? */
804   value = TSMimeHdrFieldValueStringGet(data->resp_bufp, data->hdr_loc, data->location_loc, -1, &length);
805   if (TSUrlParse(data->resp_bufp, data->url_loc, &value, value + length) != TS_PARSE_DONE) {
806     TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->url_loc);
807     TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->location_loc);
808     TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
809 
810     TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
811     TSfree(data);
812 
813     return 0;
814   }
815 
816   data->key = TSCacheKeyCreate();
817   if (TSCacheKeyDigestFromUrlSet(data->key, data->url_loc) != TS_SUCCESS) {
818     TSCacheKeyDestroy(data->key);
819 
820     TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->url_loc);
821     TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->location_loc);
822     TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
823 
824     TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
825     TSfree(data);
826 
827     return 0;
828   }
829 
830   /* ... and a Digest header */
831   data->digest_loc = TSMimeHdrFieldFind(data->resp_bufp, data->hdr_loc, "Digest", 6);
832   while (data->digest_loc) {
833     int count = TSMimeHdrFieldValuesCount(data->resp_bufp, data->hdr_loc, data->digest_loc);
834     for (data->idx = 0; data->idx < count; data->idx += 1) {
835       /* No allocation, freed with data->resp_bufp? */
836       value = TSMimeHdrFieldValueStringGet(data->resp_bufp, data->hdr_loc, data->digest_loc, data->idx, &length);
837       if (length < 8 + 44 /* 32 bytes, Base64 */ || strncasecmp(value, "SHA-256=", 8)) {
838         continue;
839       }
840 
841       /* Check if the Location URL is already cached */
842 
843       contp = TSContCreate(location_handler, nullptr);
844       TSContDataSet(contp, data);
845 
846       /* Reentrant! */
847       TSCacheRead(contp, data->key);
848 
849       return 0;
850     }
851 
852     TSMLoc next_loc = TSMimeHdrFieldNextDup(data->resp_bufp, data->hdr_loc, data->digest_loc);
853 
854     TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->digest_loc);
855 
856     data->digest_loc = next_loc;
857   }
858 
859   /* Didn't find a Digest header, just reenable the response */
860 
861   TSCacheKeyDestroy(data->key);
862 
863   TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->url_loc);
864   TSHandleMLocRelease(data->resp_bufp, data->hdr_loc, data->location_loc);
865   TSHandleMLocRelease(data->resp_bufp, TS_NULL_MLOC, data->hdr_loc);
866 
867   TSHttpTxnReenable(data->txnp, TS_EVENT_HTTP_CONTINUE);
868   TSfree(data);
869 
870   return 0;
871 }
872 
873 static int
handler(TSCont contp,TSEvent event,void * edata)874 handler(TSCont contp, TSEvent event, void *edata)
875 {
876   switch (event) {
877   case TS_EVENT_HTTP_READ_RESPONSE_HDR:
878     return http_read_response_hdr(contp, edata);
879 
880   case TS_EVENT_HTTP_SEND_RESPONSE_HDR:
881     return http_send_response_hdr(contp, edata);
882 
883   default:
884     TSAssert(!"Unexpected event");
885   }
886 
887   return 0;
888 }
889 
890 void
TSPluginInit(int,const char * [])891 TSPluginInit(int /* argc ATS_UNUSED */, const char * /* argv ATS_UNUSED */[])
892 {
893   TSPluginRegistrationInfo info;
894 
895   info.plugin_name   = (char *)"metalink";
896   info.vendor_name   = (char *)"Apache Software Foundation";
897   info.support_email = (char *)"dev@trafficserver.apache.org";
898 
899   if (TSPluginRegister(&info) != TS_SUCCESS) {
900     TSError("[metalink] Plugin registration failed");
901   }
902 
903   TSCont contp = TSContCreate(handler, nullptr);
904 
905   TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK, contp);
906   TSHttpHookAdd(TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp);
907 }
908