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