Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * unbuffered I/O | |
3 | * Copyright (c) 2001 Fabrice Bellard | |
4 | * | |
5 | * This file is part of FFmpeg. | |
6 | * | |
7 | * FFmpeg is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU Lesser General Public | |
9 | * License as published by the Free Software Foundation; either | |
10 | * version 2.1 of the License, or (at your option) any later version. | |
11 | * | |
12 | * FFmpeg is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | * Lesser General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU Lesser General Public | |
18 | * License along with FFmpeg; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
20 | */ | |
21 | ||
22 | #include "libavutil/avstring.h" | |
23 | #include "libavutil/dict.h" | |
24 | #include "libavutil/opt.h" | |
25 | #include "libavutil/time.h" | |
26 | #include "os_support.h" | |
27 | #include "avformat.h" | |
28 | #if CONFIG_NETWORK | |
29 | #include "network.h" | |
30 | #endif | |
31 | #include "url.h" | |
32 | ||
33 | static URLProtocol *first_protocol = NULL; | |
34 | ||
35 | URLProtocol *ffurl_protocol_next(const URLProtocol *prev) | |
36 | { | |
37 | return prev ? prev->next : first_protocol; | |
38 | } | |
39 | ||
40 | /** @name Logging context. */ | |
41 | /*@{*/ | |
42 | static const char *urlcontext_to_name(void *ptr) | |
43 | { | |
44 | URLContext *h = (URLContext *)ptr; | |
45 | if (h->prot) | |
46 | return h->prot->name; | |
47 | else | |
48 | return "NULL"; | |
49 | } | |
50 | ||
51 | static void *urlcontext_child_next(void *obj, void *prev) | |
52 | { | |
53 | URLContext *h = obj; | |
54 | if (!prev && h->priv_data && h->prot->priv_data_class) | |
55 | return h->priv_data; | |
56 | return NULL; | |
57 | } | |
58 | ||
59 | static const AVClass *urlcontext_child_class_next(const AVClass *prev) | |
60 | { | |
61 | URLProtocol *p = NULL; | |
62 | ||
63 | /* find the protocol that corresponds to prev */ | |
64 | while (prev && (p = ffurl_protocol_next(p))) | |
65 | if (p->priv_data_class == prev) | |
66 | break; | |
67 | ||
68 | /* find next protocol with priv options */ | |
69 | while (p = ffurl_protocol_next(p)) | |
70 | if (p->priv_data_class) | |
71 | return p->priv_data_class; | |
72 | return NULL; | |
73 | } | |
74 | ||
75 | static const AVOption options[] = { { NULL } }; | |
76 | const AVClass ffurl_context_class = { | |
77 | .class_name = "URLContext", | |
78 | .item_name = urlcontext_to_name, | |
79 | .option = options, | |
80 | .version = LIBAVUTIL_VERSION_INT, | |
81 | .child_next = urlcontext_child_next, | |
82 | .child_class_next = urlcontext_child_class_next, | |
83 | }; | |
84 | /*@}*/ | |
85 | ||
86 | const char *avio_enum_protocols(void **opaque, int output) | |
87 | { | |
88 | URLProtocol *p; | |
89 | *opaque = ffurl_protocol_next(*opaque); | |
90 | if (!(p = *opaque)) | |
91 | return NULL; | |
92 | if ((output && p->url_write) || (!output && p->url_read)) | |
93 | return p->name; | |
94 | return avio_enum_protocols(opaque, output); | |
95 | } | |
96 | ||
97 | int ffurl_register_protocol(URLProtocol *protocol) | |
98 | { | |
99 | URLProtocol **p; | |
100 | p = &first_protocol; | |
101 | while (*p) | |
102 | p = &(*p)->next; | |
103 | *p = protocol; | |
104 | protocol->next = NULL; | |
105 | return 0; | |
106 | } | |
107 | ||
108 | static int url_alloc_for_protocol(URLContext **puc, struct URLProtocol *up, | |
109 | const char *filename, int flags, | |
110 | const AVIOInterruptCB *int_cb) | |
111 | { | |
112 | URLContext *uc; | |
113 | int err; | |
114 | ||
115 | #if CONFIG_NETWORK | |
116 | if (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init()) | |
117 | return AVERROR(EIO); | |
118 | #endif | |
119 | if ((flags & AVIO_FLAG_READ) && !up->url_read) { | |
120 | av_log(NULL, AV_LOG_ERROR, | |
121 | "Impossible to open the '%s' protocol for reading\n", up->name); | |
122 | return AVERROR(EIO); | |
123 | } | |
124 | if ((flags & AVIO_FLAG_WRITE) && !up->url_write) { | |
125 | av_log(NULL, AV_LOG_ERROR, | |
126 | "Impossible to open the '%s' protocol for writing\n", up->name); | |
127 | return AVERROR(EIO); | |
128 | } | |
129 | uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1); | |
130 | if (!uc) { | |
131 | err = AVERROR(ENOMEM); | |
132 | goto fail; | |
133 | } | |
134 | uc->av_class = &ffurl_context_class; | |
135 | uc->filename = (char *)&uc[1]; | |
136 | strcpy(uc->filename, filename); | |
137 | uc->prot = up; | |
138 | uc->flags = flags; | |
139 | uc->is_streamed = 0; /* default = not streamed */ | |
140 | uc->max_packet_size = 0; /* default: stream file */ | |
141 | if (up->priv_data_size) { | |
142 | uc->priv_data = av_mallocz(up->priv_data_size); | |
143 | if (!uc->priv_data) { | |
144 | err = AVERROR(ENOMEM); | |
145 | goto fail; | |
146 | } | |
147 | if (up->priv_data_class) { | |
148 | int proto_len= strlen(up->name); | |
149 | char *start = strchr(uc->filename, ','); | |
150 | *(const AVClass **)uc->priv_data = up->priv_data_class; | |
151 | av_opt_set_defaults(uc->priv_data); | |
152 | if(!strncmp(up->name, uc->filename, proto_len) && uc->filename + proto_len == start){ | |
153 | int ret= 0; | |
154 | char *p= start; | |
155 | char sep= *++p; | |
156 | char *key, *val; | |
157 | p++; | |
158 | while(ret >= 0 && (key= strchr(p, sep)) && p<key && (val = strchr(key+1, sep))){ | |
159 | *val= *key= 0; | |
160 | ret= av_opt_set(uc->priv_data, p, key+1, 0); | |
161 | if (ret == AVERROR_OPTION_NOT_FOUND) | |
162 | av_log(uc, AV_LOG_ERROR, "Key '%s' not found.\n", p); | |
163 | *val= *key= sep; | |
164 | p= val+1; | |
165 | } | |
166 | if(ret<0 || p!=key){ | |
167 | av_log(uc, AV_LOG_ERROR, "Error parsing options string %s\n", start); | |
168 | av_freep(&uc->priv_data); | |
169 | av_freep(&uc); | |
170 | err = AVERROR(EINVAL); | |
171 | goto fail; | |
172 | } | |
173 | memmove(start, key+1, strlen(key)); | |
174 | } | |
175 | } | |
176 | } | |
177 | if (int_cb) | |
178 | uc->interrupt_callback = *int_cb; | |
179 | ||
180 | *puc = uc; | |
181 | return 0; | |
182 | fail: | |
183 | *puc = NULL; | |
184 | if (uc) | |
185 | av_freep(&uc->priv_data); | |
186 | av_freep(&uc); | |
187 | #if CONFIG_NETWORK | |
188 | if (up->flags & URL_PROTOCOL_FLAG_NETWORK) | |
189 | ff_network_close(); | |
190 | #endif | |
191 | return err; | |
192 | } | |
193 | ||
194 | int ffurl_connect(URLContext *uc, AVDictionary **options) | |
195 | { | |
196 | int err = | |
197 | uc->prot->url_open2 ? uc->prot->url_open2(uc, | |
198 | uc->filename, | |
199 | uc->flags, | |
200 | options) : | |
201 | uc->prot->url_open(uc, uc->filename, uc->flags); | |
202 | if (err) | |
203 | return err; | |
204 | uc->is_connected = 1; | |
205 | /* We must be careful here as ffurl_seek() could be slow, | |
206 | * for example for http */ | |
207 | if ((uc->flags & AVIO_FLAG_WRITE) || !strcmp(uc->prot->name, "file")) | |
208 | if (!uc->is_streamed && ffurl_seek(uc, 0, SEEK_SET) < 0) | |
209 | uc->is_streamed = 1; | |
210 | return 0; | |
211 | } | |
212 | ||
213 | #define URL_SCHEME_CHARS \ | |
214 | "abcdefghijklmnopqrstuvwxyz" \ | |
215 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ | |
216 | "0123456789+-." | |
217 | ||
218 | static struct URLProtocol *url_find_protocol(const char *filename) | |
219 | { | |
220 | URLProtocol *up = NULL; | |
221 | char proto_str[128], proto_nested[128], *ptr; | |
222 | size_t proto_len = strspn(filename, URL_SCHEME_CHARS); | |
223 | ||
224 | if (filename[proto_len] != ':' && | |
225 | (filename[proto_len] != ',' || !strchr(filename + proto_len + 1, ':')) || | |
226 | is_dos_path(filename)) | |
227 | strcpy(proto_str, "file"); | |
228 | else | |
229 | av_strlcpy(proto_str, filename, | |
230 | FFMIN(proto_len + 1, sizeof(proto_str))); | |
231 | ||
232 | if ((ptr = strchr(proto_str, ','))) | |
233 | *ptr = '\0'; | |
234 | av_strlcpy(proto_nested, proto_str, sizeof(proto_nested)); | |
235 | if ((ptr = strchr(proto_nested, '+'))) | |
236 | *ptr = '\0'; | |
237 | ||
238 | while (up = ffurl_protocol_next(up)) { | |
239 | if (!strcmp(proto_str, up->name)) | |
240 | break; | |
241 | if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME && | |
242 | !strcmp(proto_nested, up->name)) | |
243 | break; | |
244 | } | |
245 | ||
246 | return up; | |
247 | } | |
248 | ||
249 | int ffurl_alloc(URLContext **puc, const char *filename, int flags, | |
250 | const AVIOInterruptCB *int_cb) | |
251 | { | |
252 | URLProtocol *p = NULL; | |
253 | ||
254 | if (!first_protocol) { | |
255 | av_log(NULL, AV_LOG_WARNING, "No URL Protocols are registered. " | |
256 | "Missing call to av_register_all()?\n"); | |
257 | } | |
258 | ||
259 | p = url_find_protocol(filename); | |
260 | if (p) | |
261 | return url_alloc_for_protocol(puc, p, filename, flags, int_cb); | |
262 | ||
263 | *puc = NULL; | |
264 | if (av_strstart(filename, "https:", NULL)) | |
265 | av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile with openssl or gnutls enabled.\n"); | |
266 | return AVERROR_PROTOCOL_NOT_FOUND; | |
267 | } | |
268 | ||
269 | int ffurl_open(URLContext **puc, const char *filename, int flags, | |
270 | const AVIOInterruptCB *int_cb, AVDictionary **options) | |
271 | { | |
272 | int ret = ffurl_alloc(puc, filename, flags, int_cb); | |
273 | if (ret < 0) | |
274 | return ret; | |
275 | if (options && (*puc)->prot->priv_data_class && | |
276 | (ret = av_opt_set_dict((*puc)->priv_data, options)) < 0) | |
277 | goto fail; | |
278 | if ((ret = av_opt_set_dict(*puc, options)) < 0) | |
279 | goto fail; | |
280 | ret = ffurl_connect(*puc, options); | |
281 | if (!ret) | |
282 | return 0; | |
283 | fail: | |
284 | ffurl_close(*puc); | |
285 | *puc = NULL; | |
286 | return ret; | |
287 | } | |
288 | ||
289 | static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf, | |
290 | int size, int size_min, | |
291 | int (*transfer_func)(URLContext *h, | |
292 | uint8_t *buf, | |
293 | int size)) | |
294 | { | |
295 | int ret, len; | |
296 | int fast_retries = 5; | |
297 | int64_t wait_since = 0; | |
298 | ||
299 | len = 0; | |
300 | while (len < size_min) { | |
301 | if (ff_check_interrupt(&h->interrupt_callback)) | |
302 | return AVERROR_EXIT; | |
303 | ret = transfer_func(h, buf + len, size - len); | |
304 | if (ret == AVERROR(EINTR)) | |
305 | continue; | |
306 | if (h->flags & AVIO_FLAG_NONBLOCK) | |
307 | return ret; | |
308 | if (ret == AVERROR(EAGAIN)) { | |
309 | ret = 0; | |
310 | if (fast_retries) { | |
311 | fast_retries--; | |
312 | } else { | |
313 | if (h->rw_timeout) { | |
314 | if (!wait_since) | |
315 | wait_since = av_gettime_relative(); | |
316 | else if (av_gettime_relative() > wait_since + h->rw_timeout) | |
317 | return AVERROR(EIO); | |
318 | } | |
319 | av_usleep(1000); | |
320 | } | |
321 | } else if (ret < 1) | |
322 | return (ret < 0 && ret != AVERROR_EOF) ? ret : len; | |
323 | if (ret) | |
324 | fast_retries = FFMAX(fast_retries, 2); | |
325 | len += ret; | |
326 | } | |
327 | return len; | |
328 | } | |
329 | ||
330 | int ffurl_read(URLContext *h, unsigned char *buf, int size) | |
331 | { | |
332 | if (!(h->flags & AVIO_FLAG_READ)) | |
333 | return AVERROR(EIO); | |
334 | return retry_transfer_wrapper(h, buf, size, 1, h->prot->url_read); | |
335 | } | |
336 | ||
337 | int ffurl_read_complete(URLContext *h, unsigned char *buf, int size) | |
338 | { | |
339 | if (!(h->flags & AVIO_FLAG_READ)) | |
340 | return AVERROR(EIO); | |
341 | return retry_transfer_wrapper(h, buf, size, size, h->prot->url_read); | |
342 | } | |
343 | ||
344 | int ffurl_write(URLContext *h, const unsigned char *buf, int size) | |
345 | { | |
346 | if (!(h->flags & AVIO_FLAG_WRITE)) | |
347 | return AVERROR(EIO); | |
348 | /* avoid sending too big packets */ | |
349 | if (h->max_packet_size && size > h->max_packet_size) | |
350 | return AVERROR(EIO); | |
351 | ||
352 | return retry_transfer_wrapper(h, (unsigned char *)buf, size, size, (void*)h->prot->url_write); | |
353 | } | |
354 | ||
355 | int64_t ffurl_seek(URLContext *h, int64_t pos, int whence) | |
356 | { | |
357 | int64_t ret; | |
358 | ||
359 | if (!h->prot->url_seek) | |
360 | return AVERROR(ENOSYS); | |
361 | ret = h->prot->url_seek(h, pos, whence & ~AVSEEK_FORCE); | |
362 | return ret; | |
363 | } | |
364 | ||
365 | int ffurl_closep(URLContext **hh) | |
366 | { | |
367 | URLContext *h= *hh; | |
368 | int ret = 0; | |
369 | if (!h) | |
370 | return 0; /* can happen when ffurl_open fails */ | |
371 | ||
372 | if (h->is_connected && h->prot->url_close) | |
373 | ret = h->prot->url_close(h); | |
374 | #if CONFIG_NETWORK | |
375 | if (h->prot->flags & URL_PROTOCOL_FLAG_NETWORK) | |
376 | ff_network_close(); | |
377 | #endif | |
378 | if (h->prot->priv_data_size) { | |
379 | if (h->prot->priv_data_class) | |
380 | av_opt_free(h->priv_data); | |
381 | av_freep(&h->priv_data); | |
382 | } | |
383 | av_freep(hh); | |
384 | return ret; | |
385 | } | |
386 | ||
387 | int ffurl_close(URLContext *h) | |
388 | { | |
389 | return ffurl_closep(&h); | |
390 | } | |
391 | ||
392 | ||
393 | const char *avio_find_protocol_name(const char *url) | |
394 | { | |
395 | URLProtocol *p = url_find_protocol(url); | |
396 | ||
397 | return p ? p->name : NULL; | |
398 | } | |
399 | ||
400 | int avio_check(const char *url, int flags) | |
401 | { | |
402 | URLContext *h; | |
403 | int ret = ffurl_alloc(&h, url, flags, NULL); | |
404 | if (ret < 0) | |
405 | return ret; | |
406 | ||
407 | if (h->prot->url_check) { | |
408 | ret = h->prot->url_check(h, flags); | |
409 | } else { | |
410 | ret = ffurl_connect(h, NULL); | |
411 | if (ret >= 0) | |
412 | ret = flags; | |
413 | } | |
414 | ||
415 | ffurl_close(h); | |
416 | return ret; | |
417 | } | |
418 | ||
419 | int64_t ffurl_size(URLContext *h) | |
420 | { | |
421 | int64_t pos, size; | |
422 | ||
423 | size = ffurl_seek(h, 0, AVSEEK_SIZE); | |
424 | if (size < 0) { | |
425 | pos = ffurl_seek(h, 0, SEEK_CUR); | |
426 | if ((size = ffurl_seek(h, -1, SEEK_END)) < 0) | |
427 | return size; | |
428 | size++; | |
429 | ffurl_seek(h, pos, SEEK_SET); | |
430 | } | |
431 | return size; | |
432 | } | |
433 | ||
434 | int ffurl_get_file_handle(URLContext *h) | |
435 | { | |
436 | if (!h->prot->url_get_file_handle) | |
437 | return -1; | |
438 | return h->prot->url_get_file_handle(h); | |
439 | } | |
440 | ||
441 | int ffurl_get_multi_file_handle(URLContext *h, int **handles, int *numhandles) | |
442 | { | |
443 | if (!h->prot->url_get_multi_file_handle) { | |
444 | if (!h->prot->url_get_file_handle) | |
445 | return AVERROR(ENOSYS); | |
446 | *handles = av_malloc(sizeof(**handles)); | |
447 | if (!*handles) | |
448 | return AVERROR(ENOMEM); | |
449 | *numhandles = 1; | |
450 | *handles[0] = h->prot->url_get_file_handle(h); | |
451 | return 0; | |
452 | } | |
453 | return h->prot->url_get_multi_file_handle(h, handles, numhandles); | |
454 | } | |
455 | ||
456 | int ffurl_shutdown(URLContext *h, int flags) | |
457 | { | |
458 | if (!h->prot->url_shutdown) | |
459 | return AVERROR(EINVAL); | |
460 | return h->prot->url_shutdown(h, flags); | |
461 | } | |
462 | ||
463 | int ff_check_interrupt(AVIOInterruptCB *cb) | |
464 | { | |
465 | int ret; | |
466 | if (cb && cb->callback && (ret = cb->callback(cb->opaque))) | |
467 | return ret; | |
468 | return 0; | |
469 | } |