1 /*****************************************************************************
2  * file.c: HTTP read-only file
3  *****************************************************************************
4  * Copyright (C) 2015 Rémi Denis-Courmont
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20 
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24 
25 #include <assert.h>
26 #include <errno.h>
27 #include <stdbool.h>
28 #include <stdint.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 
33 #include <vlc_common.h>
34 #include <vlc_block.h>
35 #include <vlc_strings.h>
36 #include "message.h"
37 #include "resource.h"
38 #include "file.h"
39 
40 #pragma GCC visibility push(default)
41 
42 struct vlc_http_file
43 {
44     struct vlc_http_resource resource;
45     uintmax_t offset;
46 };
47 
vlc_http_file_req(const struct vlc_http_resource * res,struct vlc_http_msg * req,void * opaque)48 static int vlc_http_file_req(const struct vlc_http_resource *res,
49                              struct vlc_http_msg *req, void *opaque)
50 {
51     struct vlc_http_file *file = (struct vlc_http_file *)res;
52     const uintmax_t *offset = opaque;
53 
54     if (file->resource.response != NULL)
55     {
56         const char *str = vlc_http_msg_get_header(file->resource.response,
57                                                   "ETag");
58         if (str != NULL)
59         {
60             if (!memcmp(str, "W/", 2))
61                 str += 2; /* skip weak mark */
62             vlc_http_msg_add_header(req, "If-Match", "%s", str);
63         }
64         else
65         {
66             time_t mtime = vlc_http_msg_get_mtime(file->resource.response);
67             if (mtime != -1)
68                 vlc_http_msg_add_time(req, "If-Unmodified-Since", &mtime);
69         }
70     }
71 
72     if (vlc_http_msg_add_header(req, "Range", "bytes=%ju-", *offset)
73      && *offset != 0)
74         return -1;
75     return 0;
76 }
77 
vlc_http_file_resp(const struct vlc_http_resource * res,const struct vlc_http_msg * resp,void * opaque)78 static int vlc_http_file_resp(const struct vlc_http_resource *res,
79                               const struct vlc_http_msg *resp, void *opaque)
80 {
81     const uintmax_t *offset = opaque;
82 
83     if (vlc_http_msg_get_status(resp) == 206)
84     {
85         const char *str = vlc_http_msg_get_header(resp, "Content-Range");
86         if (str == NULL)
87             /* A multipart/byteranges response. This is not what we asked for
88              * and we do not support it. */
89             goto fail;
90 
91         uintmax_t start, end;
92         if (sscanf(str, "bytes %ju-%ju", &start, &end) != 2
93          || start != *offset || start > end)
94             /* A single range response is what we asked for, but not at that
95              * start offset. */
96             goto fail;
97     }
98 
99     (void) res;
100     return 0;
101 
102 fail:
103     errno = EIO;
104     return -1;
105 }
106 
107 static const struct vlc_http_resource_cbs vlc_http_file_callbacks =
108 {
109     vlc_http_file_req,
110     vlc_http_file_resp,
111 };
112 
vlc_http_file_create(struct vlc_http_mgr * mgr,const char * uri,const char * ua,const char * ref)113 struct vlc_http_resource *vlc_http_file_create(struct vlc_http_mgr *mgr,
114                                                const char *uri, const char *ua,
115                                                const char *ref)
116 {
117     struct vlc_http_file *file = malloc(sizeof (*file));
118     if (unlikely(file == NULL))
119         return NULL;
120 
121     if (vlc_http_res_init(&file->resource, &vlc_http_file_callbacks, mgr,
122                           uri, ua, ref))
123     {
124         free(file);
125         return NULL;
126     }
127 
128     file->offset = 0;
129     return &file->resource;
130 }
131 
vlc_http_msg_get_file_size(const struct vlc_http_msg * resp)132 static uintmax_t vlc_http_msg_get_file_size(const struct vlc_http_msg *resp)
133 {
134     int status = vlc_http_msg_get_status(resp);
135     const char *range = vlc_http_msg_get_header(resp, "Content-Range");
136 
137     if (status == 206 /* Partial Content */)
138     {   /* IETF RFC7233 §4.1 */
139         assert(range != NULL); /* checked by vlc_http_file_resp() */
140 
141         uintmax_t end, total;
142 
143         switch (sscanf(range, "bytes %*u-%ju/%ju", &end, &total))
144         {
145             case 1:
146                 if (unlikely(end == UINTMAX_MAX))
147                     return -1; /* avoid wrapping to zero */
148                 return end + 1;
149             case 2:
150                 return total;
151         }
152         vlc_assert_unreachable(); /* checked by vlc_http_file_resp() */
153     }
154 
155     if (status == 416 /* Range Not Satisfiable */)
156     {   /* IETF RFC7233 §4.4 */
157         uintmax_t total;
158 
159         if (range == NULL)
160             return -1; /* valid but helpless response */
161 
162         if (sscanf(range, "bytes */%ju", &total) == 1)
163             return total; /* this occurs when seeking beyond EOF */
164     }
165 
166     return -1;
167 }
168 
vlc_http_msg_can_seek(const struct vlc_http_msg * resp)169 static bool vlc_http_msg_can_seek(const struct vlc_http_msg *resp)
170 {
171     int status = vlc_http_msg_get_status(resp);
172     if (status == 206 || status == 416)
173         return true; /* Partial Content */
174 
175     return vlc_http_msg_get_token(resp, "Accept-Ranges", "bytes") != NULL;
176 }
177 
vlc_http_file_get_size(struct vlc_http_resource * res)178 uintmax_t vlc_http_file_get_size(struct vlc_http_resource *res)
179 {
180     int status = vlc_http_res_get_status(res);
181     if (status < 0)
182         return -1;
183 
184     uintmax_t ret = vlc_http_msg_get_file_size(res->response);
185     if (ret != (uintmax_t)-1)
186         return ret;
187 
188     if (status >= 300 || status == 201)
189         return -1; /* Error or redirection, size is unknown/irrelevant. */
190 
191     /* Content-Range is meaningless here (see RFC7233 B), so check if the size
192      * of the response entity body is known. */
193     return vlc_http_msg_get_size(res->response);
194 }
195 
vlc_http_file_can_seek(struct vlc_http_resource * res)196 bool vlc_http_file_can_seek(struct vlc_http_resource *res)
197 {   /* See IETF RFC7233 */
198     int status = vlc_http_res_get_status(res);
199     if (status < 0)
200         return false;
201     return vlc_http_msg_can_seek(res->response);
202 }
203 
vlc_http_file_seek(struct vlc_http_resource * res,uintmax_t offset)204 int vlc_http_file_seek(struct vlc_http_resource *res, uintmax_t offset)
205 {
206     struct vlc_http_msg *resp = vlc_http_res_open(res, &offset);
207     if (resp == NULL)
208         return -1;
209 
210     struct vlc_http_file *file = (struct vlc_http_file *)res;
211 
212     int status = vlc_http_msg_get_status(resp);
213     if (res->response != NULL)
214     {   /* Accept the new and ditch the old one if:
215          * - requested succeeded and range was accepted (206),
216          * - requested failed due to out-of-range (416),
217          * - request succeeded and seek offset is zero (2xx).
218          */
219         if (status != 206 && status != 416 && (offset != 0 || status >= 300))
220         {
221             vlc_http_msg_destroy(resp);
222             return -1;
223         }
224         vlc_http_msg_destroy(res->response);
225     }
226 
227     res->response = resp;
228     file->offset = offset;
229     return 0;
230 }
231 
vlc_http_file_read(struct vlc_http_resource * res)232 block_t *vlc_http_file_read(struct vlc_http_resource *res)
233 {
234     struct vlc_http_file *file = (struct vlc_http_file *)res;
235     block_t *block = vlc_http_res_read(res);
236 
237     if (block == vlc_http_error)
238     {   /* Automatically reconnect on error if server supports seek */
239         if (res->response != NULL
240          && vlc_http_msg_can_seek(res->response)
241          && file->offset < vlc_http_msg_get_file_size(res->response)
242          && vlc_http_file_seek(res, file->offset) == 0)
243             block = vlc_http_res_read(res);
244 
245         if (block == vlc_http_error)
246             return NULL;
247     }
248 
249     if (block == NULL)
250         return NULL; /* End of stream */
251 
252     file->offset += block->i_buffer;
253     return block;
254 }
255