Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * HTTP protocol for ffmpeg client | |
3 | * Copyright (c) 2000, 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 "config.h" | |
23 | ||
24 | #if CONFIG_ZLIB | |
25 | #include <zlib.h> | |
26 | #endif /* CONFIG_ZLIB */ | |
27 | ||
28 | #include "libavutil/avstring.h" | |
29 | #include "libavutil/opt.h" | |
30 | ||
31 | #include "avformat.h" | |
32 | #include "http.h" | |
33 | #include "httpauth.h" | |
34 | #include "internal.h" | |
35 | #include "network.h" | |
36 | #include "os_support.h" | |
37 | #include "url.h" | |
38 | ||
39 | /* XXX: POST protocol is not completely implemented because ffmpeg uses | |
40 | * only a subset of it. */ | |
41 | ||
42 | /* The IO buffer size is unrelated to the max URL size in itself, but needs | |
43 | * to be large enough to fit the full request headers (including long | |
44 | * path names). */ | |
45 | #define BUFFER_SIZE MAX_URL_SIZE | |
46 | #define MAX_REDIRECTS 8 | |
47 | ||
48 | typedef struct { | |
49 | const AVClass *class; | |
50 | URLContext *hd; | |
51 | unsigned char buffer[BUFFER_SIZE], *buf_ptr, *buf_end; | |
52 | int line_count; | |
53 | int http_code; | |
54 | /* Used if "Transfer-Encoding: chunked" otherwise -1. */ | |
55 | int64_t chunksize; | |
56 | int64_t off, end_off, filesize; | |
57 | char *location; | |
58 | HTTPAuthState auth_state; | |
59 | HTTPAuthState proxy_auth_state; | |
60 | char *headers; | |
61 | char *mime_type; | |
62 | char *user_agent; | |
63 | char *content_type; | |
64 | /* Set if the server correctly handles Connection: close and will close | |
65 | * the connection after feeding us the content. */ | |
66 | int willclose; | |
67 | int seekable; /**< Control seekability, 0 = disable, 1 = enable, -1 = probe. */ | |
68 | int chunked_post; | |
69 | /* A flag which indicates if the end of chunked encoding has been sent. */ | |
70 | int end_chunked_post; | |
71 | /* A flag which indicates we have finished to read POST reply. */ | |
72 | int end_header; | |
73 | /* A flag which indicates if we use persistent connections. */ | |
74 | int multiple_requests; | |
75 | uint8_t *post_data; | |
76 | int post_datalen; | |
77 | int is_akamai; | |
78 | int is_mediagateway; | |
79 | char *cookies; ///< holds newline (\n) delimited Set-Cookie header field values (without the "Set-Cookie: " field name) | |
80 | int icy; | |
81 | /* how much data was read since the last ICY metadata packet */ | |
82 | int icy_data_read; | |
83 | /* after how many bytes of read data a new metadata packet will be found */ | |
84 | int icy_metaint; | |
85 | char *icy_metadata_headers; | |
86 | char *icy_metadata_packet; | |
87 | AVDictionary *metadata; | |
88 | #if CONFIG_ZLIB | |
89 | int compressed; | |
90 | z_stream inflate_stream; | |
91 | uint8_t *inflate_buffer; | |
92 | #endif /* CONFIG_ZLIB */ | |
93 | AVDictionary *chained_options; | |
94 | int send_expect_100; | |
95 | char *method; | |
96 | } HTTPContext; | |
97 | ||
98 | #define OFFSET(x) offsetof(HTTPContext, x) | |
99 | #define D AV_OPT_FLAG_DECODING_PARAM | |
100 | #define E AV_OPT_FLAG_ENCODING_PARAM | |
101 | #define DEFAULT_USER_AGENT "Lavf/" AV_STRINGIFY(LIBAVFORMAT_VERSION) | |
102 | ||
103 | static const AVOption options[] = { | |
104 | { "seekable", "control seekability of connection", OFFSET(seekable), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 1, D }, | |
105 | { "chunked_post", "use chunked transfer-encoding for posts", OFFSET(chunked_post), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, E }, | |
106 | { "headers", "set custom HTTP headers, can override built in default headers", OFFSET(headers), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D | E }, | |
107 | { "content_type", "set a specific content type for the POST messages", OFFSET(content_type), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D | E }, | |
108 | { "user_agent", "override User-Agent header", OFFSET(user_agent), AV_OPT_TYPE_STRING, { .str = DEFAULT_USER_AGENT }, 0, 0, D }, | |
109 | { "user-agent", "override User-Agent header", OFFSET(user_agent), AV_OPT_TYPE_STRING, { .str = DEFAULT_USER_AGENT }, 0, 0, D }, | |
110 | { "multiple_requests", "use persistent connections", OFFSET(multiple_requests), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, D | E }, | |
111 | { "post_data", "set custom HTTP post data", OFFSET(post_data), AV_OPT_TYPE_BINARY, .flags = D | E }, | |
112 | { "mime_type", "export the MIME type", OFFSET(mime_type), AV_OPT_TYPE_STRING, { 0 }, 0, 0, AV_OPT_FLAG_EXPORT | AV_OPT_FLAG_READONLY }, | |
113 | { "cookies", "set cookies to be sent in applicable future requests, use newline delimited Set-Cookie HTTP field value syntax", OFFSET(cookies), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D }, | |
114 | { "icy", "request ICY metadata", OFFSET(icy), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, D }, | |
115 | { "icy_metadata_headers", "return ICY metadata headers", OFFSET(icy_metadata_headers), AV_OPT_TYPE_STRING, { 0 }, 0, 0, AV_OPT_FLAG_EXPORT }, | |
116 | { "icy_metadata_packet", "return current ICY metadata packet", OFFSET(icy_metadata_packet), AV_OPT_TYPE_STRING, { 0 }, 0, 0, AV_OPT_FLAG_EXPORT }, | |
117 | { "metadata", "metadata read from the bitstream", OFFSET(metadata), AV_OPT_TYPE_DICT, {0}, 0, 0, AV_OPT_FLAG_EXPORT }, | |
118 | { "auth_type", "HTTP authentication type", OFFSET(auth_state.auth_type), AV_OPT_TYPE_INT, { .i64 = HTTP_AUTH_NONE }, HTTP_AUTH_NONE, HTTP_AUTH_BASIC, D | E, "auth_type"}, | |
119 | { "none", "No auth method set, autodetect", 0, AV_OPT_TYPE_CONST, { .i64 = HTTP_AUTH_NONE }, 0, 0, D | E, "auth_type"}, | |
120 | { "basic", "HTTP basic authentication", 0, AV_OPT_TYPE_CONST, { .i64 = HTTP_AUTH_BASIC }, 0, 0, D | E, "auth_type"}, | |
121 | { "send_expect_100", "Force sending an Expect: 100-continue header for POST", OFFSET(send_expect_100), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, E }, | |
122 | { "location", "The actual location of the data received", OFFSET(location), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D | E }, | |
123 | { "offset", "initial byte offset", OFFSET(off), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D }, | |
124 | { "end_offset", "try to limit the request to bytes preceding this offset", OFFSET(end_off), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D }, | |
125 | { "method", "Override the HTTP method", OFFSET(method), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E }, | |
126 | { NULL } | |
127 | }; | |
128 | ||
129 | static int http_connect(URLContext *h, const char *path, const char *local_path, | |
130 | const char *hoststr, const char *auth, | |
131 | const char *proxyauth, int *new_location); | |
132 | ||
133 | void ff_http_init_auth_state(URLContext *dest, const URLContext *src) | |
134 | { | |
135 | memcpy(&((HTTPContext *)dest->priv_data)->auth_state, | |
136 | &((HTTPContext *)src->priv_data)->auth_state, | |
137 | sizeof(HTTPAuthState)); | |
138 | memcpy(&((HTTPContext *)dest->priv_data)->proxy_auth_state, | |
139 | &((HTTPContext *)src->priv_data)->proxy_auth_state, | |
140 | sizeof(HTTPAuthState)); | |
141 | } | |
142 | ||
143 | static int http_open_cnx_internal(URLContext *h, AVDictionary **options) | |
144 | { | |
145 | const char *path, *proxy_path, *lower_proto = "tcp", *local_path; | |
146 | char hostname[1024], hoststr[1024], proto[10]; | |
147 | char auth[1024], proxyauth[1024] = ""; | |
148 | char path1[MAX_URL_SIZE]; | |
149 | char buf[1024], urlbuf[MAX_URL_SIZE]; | |
150 | int port, use_proxy, err, location_changed = 0; | |
151 | HTTPContext *s = h->priv_data; | |
152 | ||
153 | av_url_split(proto, sizeof(proto), auth, sizeof(auth), | |
154 | hostname, sizeof(hostname), &port, | |
155 | path1, sizeof(path1), s->location); | |
156 | ff_url_join(hoststr, sizeof(hoststr), NULL, NULL, hostname, port, NULL); | |
157 | ||
158 | proxy_path = getenv("http_proxy"); | |
159 | use_proxy = !ff_http_match_no_proxy(getenv("no_proxy"), hostname) && | |
160 | proxy_path && av_strstart(proxy_path, "http://", NULL); | |
161 | ||
162 | if (!strcmp(proto, "https")) { | |
163 | lower_proto = "tls"; | |
164 | use_proxy = 0; | |
165 | if (port < 0) | |
166 | port = 443; | |
167 | } | |
168 | if (port < 0) | |
169 | port = 80; | |
170 | ||
171 | if (path1[0] == '\0') | |
172 | path = "/"; | |
173 | else | |
174 | path = path1; | |
175 | local_path = path; | |
176 | if (use_proxy) { | |
177 | /* Reassemble the request URL without auth string - we don't | |
178 | * want to leak the auth to the proxy. */ | |
179 | ff_url_join(urlbuf, sizeof(urlbuf), proto, NULL, hostname, port, "%s", | |
180 | path1); | |
181 | path = urlbuf; | |
182 | av_url_split(NULL, 0, proxyauth, sizeof(proxyauth), | |
183 | hostname, sizeof(hostname), &port, NULL, 0, proxy_path); | |
184 | } | |
185 | ||
186 | ff_url_join(buf, sizeof(buf), lower_proto, NULL, hostname, port, NULL); | |
187 | ||
188 | if (!s->hd) { | |
189 | err = ffurl_open(&s->hd, buf, AVIO_FLAG_READ_WRITE, | |
190 | &h->interrupt_callback, options); | |
191 | if (err < 0) | |
192 | return err; | |
193 | } | |
194 | ||
195 | err = http_connect(h, path, local_path, hoststr, | |
196 | auth, proxyauth, &location_changed); | |
197 | if (err < 0) | |
198 | return err; | |
199 | ||
200 | return location_changed; | |
201 | } | |
202 | ||
203 | /* return non zero if error */ | |
204 | static int http_open_cnx(URLContext *h, AVDictionary **options) | |
205 | { | |
206 | HTTPAuthType cur_auth_type, cur_proxy_auth_type; | |
207 | HTTPContext *s = h->priv_data; | |
208 | int location_changed, attempts = 0, redirects = 0; | |
209 | redo: | |
210 | cur_auth_type = s->auth_state.auth_type; | |
211 | cur_proxy_auth_type = s->auth_state.auth_type; | |
212 | ||
213 | location_changed = http_open_cnx_internal(h, options); | |
214 | if (location_changed < 0) | |
215 | goto fail; | |
216 | ||
217 | attempts++; | |
218 | if (s->http_code == 401) { | |
219 | if ((cur_auth_type == HTTP_AUTH_NONE || s->auth_state.stale) && | |
220 | s->auth_state.auth_type != HTTP_AUTH_NONE && attempts < 4) { | |
221 | ffurl_closep(&s->hd); | |
222 | goto redo; | |
223 | } else | |
224 | goto fail; | |
225 | } | |
226 | if (s->http_code == 407) { | |
227 | if ((cur_proxy_auth_type == HTTP_AUTH_NONE || s->proxy_auth_state.stale) && | |
228 | s->proxy_auth_state.auth_type != HTTP_AUTH_NONE && attempts < 4) { | |
229 | ffurl_closep(&s->hd); | |
230 | goto redo; | |
231 | } else | |
232 | goto fail; | |
233 | } | |
234 | if ((s->http_code == 301 || s->http_code == 302 || | |
235 | s->http_code == 303 || s->http_code == 307) && | |
236 | location_changed == 1) { | |
237 | /* url moved, get next */ | |
238 | ffurl_closep(&s->hd); | |
239 | if (redirects++ >= MAX_REDIRECTS) | |
240 | return AVERROR(EIO); | |
241 | /* Restart the authentication process with the new target, which | |
242 | * might use a different auth mechanism. */ | |
243 | memset(&s->auth_state, 0, sizeof(s->auth_state)); | |
244 | attempts = 0; | |
245 | location_changed = 0; | |
246 | goto redo; | |
247 | } | |
248 | return 0; | |
249 | ||
250 | fail: | |
251 | if (s->hd) | |
252 | ffurl_closep(&s->hd); | |
253 | return AVERROR(EIO); | |
254 | } | |
255 | ||
256 | int ff_http_do_new_request(URLContext *h, const char *uri) | |
257 | { | |
258 | HTTPContext *s = h->priv_data; | |
259 | AVDictionary *options = NULL; | |
260 | int ret; | |
261 | ||
262 | s->off = 0; | |
263 | s->icy_data_read = 0; | |
264 | av_free(s->location); | |
265 | s->location = av_strdup(uri); | |
266 | if (!s->location) | |
267 | return AVERROR(ENOMEM); | |
268 | ||
269 | av_dict_copy(&options, s->chained_options, 0); | |
270 | ret = http_open_cnx(h, &options); | |
271 | av_dict_free(&options); | |
272 | return ret; | |
273 | } | |
274 | ||
275 | static int http_open(URLContext *h, const char *uri, int flags, | |
276 | AVDictionary **options) | |
277 | { | |
278 | HTTPContext *s = h->priv_data; | |
279 | int ret; | |
280 | ||
281 | if( s->seekable == 1 ) | |
282 | h->is_streamed = 0; | |
283 | else | |
284 | h->is_streamed = 1; | |
285 | ||
286 | s->filesize = -1; | |
287 | s->location = av_strdup(uri); | |
288 | if (!s->location) | |
289 | return AVERROR(ENOMEM); | |
290 | if (options) | |
291 | av_dict_copy(&s->chained_options, *options, 0); | |
292 | ||
293 | if (s->headers) { | |
294 | int len = strlen(s->headers); | |
295 | if (len < 2 || strcmp("\r\n", s->headers + len - 2)) | |
296 | av_log(h, AV_LOG_WARNING, | |
297 | "No trailing CRLF found in HTTP header.\n"); | |
298 | } | |
299 | ||
300 | ret = http_open_cnx(h, options); | |
301 | if (ret < 0) | |
302 | av_dict_free(&s->chained_options); | |
303 | return ret; | |
304 | } | |
305 | ||
306 | static int http_getc(HTTPContext *s) | |
307 | { | |
308 | int len; | |
309 | if (s->buf_ptr >= s->buf_end) { | |
310 | len = ffurl_read(s->hd, s->buffer, BUFFER_SIZE); | |
311 | if (len < 0) { | |
312 | return len; | |
313 | } else if (len == 0) { | |
314 | return AVERROR_EOF; | |
315 | } else { | |
316 | s->buf_ptr = s->buffer; | |
317 | s->buf_end = s->buffer + len; | |
318 | } | |
319 | } | |
320 | return *s->buf_ptr++; | |
321 | } | |
322 | ||
323 | static int http_get_line(HTTPContext *s, char *line, int line_size) | |
324 | { | |
325 | int ch; | |
326 | char *q; | |
327 | ||
328 | q = line; | |
329 | for (;;) { | |
330 | ch = http_getc(s); | |
331 | if (ch < 0) | |
332 | return ch; | |
333 | if (ch == '\n') { | |
334 | /* process line */ | |
335 | if (q > line && q[-1] == '\r') | |
336 | q--; | |
337 | *q = '\0'; | |
338 | ||
339 | return 0; | |
340 | } else { | |
341 | if ((q - line) < line_size - 1) | |
342 | *q++ = ch; | |
343 | } | |
344 | } | |
345 | } | |
346 | ||
347 | static int check_http_code(URLContext *h, int http_code, const char *end) | |
348 | { | |
349 | HTTPContext *s = h->priv_data; | |
350 | /* error codes are 4xx and 5xx, but regard 401 as a success, so we | |
351 | * don't abort until all headers have been parsed. */ | |
352 | if (http_code >= 400 && http_code < 600 && | |
353 | (http_code != 401 || s->auth_state.auth_type != HTTP_AUTH_NONE) && | |
354 | (http_code != 407 || s->proxy_auth_state.auth_type != HTTP_AUTH_NONE)) { | |
355 | end += strspn(end, SPACE_CHARS); | |
356 | av_log(h, AV_LOG_WARNING, "HTTP error %d %s\n", http_code, end); | |
357 | return AVERROR(EIO); | |
358 | } | |
359 | return 0; | |
360 | } | |
361 | ||
362 | static int parse_location(HTTPContext *s, const char *p) | |
363 | { | |
364 | char redirected_location[MAX_URL_SIZE], *new_loc; | |
365 | ff_make_absolute_url(redirected_location, sizeof(redirected_location), | |
366 | s->location, p); | |
367 | new_loc = av_strdup(redirected_location); | |
368 | if (!new_loc) | |
369 | return AVERROR(ENOMEM); | |
370 | av_free(s->location); | |
371 | s->location = new_loc; | |
372 | return 0; | |
373 | } | |
374 | ||
375 | /* "bytes $from-$to/$document_size" */ | |
376 | static void parse_content_range(URLContext *h, const char *p) | |
377 | { | |
378 | HTTPContext *s = h->priv_data; | |
379 | const char *slash; | |
380 | ||
381 | if (!strncmp(p, "bytes ", 6)) { | |
382 | p += 6; | |
383 | s->off = strtoll(p, NULL, 10); | |
384 | if ((slash = strchr(p, '/')) && strlen(slash) > 0) | |
385 | s->filesize = strtoll(slash + 1, NULL, 10); | |
386 | } | |
387 | if (s->seekable == -1 && (!s->is_akamai || s->filesize != 2147483647)) | |
388 | h->is_streamed = 0; /* we _can_ in fact seek */ | |
389 | } | |
390 | ||
391 | static int parse_content_encoding(URLContext *h, const char *p) | |
392 | { | |
393 | if (!av_strncasecmp(p, "gzip", 4) || | |
394 | !av_strncasecmp(p, "deflate", 7)) { | |
395 | #if CONFIG_ZLIB | |
396 | HTTPContext *s = h->priv_data; | |
397 | ||
398 | s->compressed = 1; | |
399 | inflateEnd(&s->inflate_stream); | |
400 | if (inflateInit2(&s->inflate_stream, 32 + 15) != Z_OK) { | |
401 | av_log(h, AV_LOG_WARNING, "Error during zlib initialisation: %s\n", | |
402 | s->inflate_stream.msg); | |
403 | return AVERROR(ENOSYS); | |
404 | } | |
405 | if (zlibCompileFlags() & (1 << 17)) { | |
406 | av_log(h, AV_LOG_WARNING, | |
407 | "Your zlib was compiled without gzip support.\n"); | |
408 | return AVERROR(ENOSYS); | |
409 | } | |
410 | #else | |
411 | av_log(h, AV_LOG_WARNING, | |
412 | "Compressed (%s) content, need zlib with gzip support\n", p); | |
413 | return AVERROR(ENOSYS); | |
414 | #endif /* CONFIG_ZLIB */ | |
415 | } else if (!av_strncasecmp(p, "identity", 8)) { | |
416 | // The normal, no-encoding case (although servers shouldn't include | |
417 | // the header at all if this is the case). | |
418 | } else { | |
419 | av_log(h, AV_LOG_WARNING, "Unknown content coding: %s\n", p); | |
420 | } | |
421 | return 0; | |
422 | } | |
423 | ||
424 | // Concat all Icy- header lines | |
425 | static int parse_icy(HTTPContext *s, const char *tag, const char *p) | |
426 | { | |
427 | int len = 4 + strlen(p) + strlen(tag); | |
428 | int is_first = !s->icy_metadata_headers; | |
429 | int ret; | |
430 | ||
431 | av_dict_set(&s->metadata, tag, p, 0); | |
432 | ||
433 | if (s->icy_metadata_headers) | |
434 | len += strlen(s->icy_metadata_headers); | |
435 | ||
436 | if ((ret = av_reallocp(&s->icy_metadata_headers, len)) < 0) | |
437 | return ret; | |
438 | ||
439 | if (is_first) | |
440 | *s->icy_metadata_headers = '\0'; | |
441 | ||
442 | av_strlcatf(s->icy_metadata_headers, len, "%s: %s\n", tag, p); | |
443 | ||
444 | return 0; | |
445 | } | |
446 | ||
447 | static int process_line(URLContext *h, char *line, int line_count, | |
448 | int *new_location) | |
449 | { | |
450 | HTTPContext *s = h->priv_data; | |
451 | char *tag, *p, *end; | |
452 | int ret; | |
453 | ||
454 | /* end of header */ | |
455 | if (line[0] == '\0') { | |
456 | s->end_header = 1; | |
457 | return 0; | |
458 | } | |
459 | ||
460 | p = line; | |
461 | if (line_count == 0) { | |
462 | while (!av_isspace(*p) && *p != '\0') | |
463 | p++; | |
464 | while (av_isspace(*p)) | |
465 | p++; | |
466 | s->http_code = strtol(p, &end, 10); | |
467 | ||
468 | av_log(h, AV_LOG_DEBUG, "http_code=%d\n", s->http_code); | |
469 | ||
470 | if ((ret = check_http_code(h, s->http_code, end)) < 0) | |
471 | return ret; | |
472 | } else { | |
473 | while (*p != '\0' && *p != ':') | |
474 | p++; | |
475 | if (*p != ':') | |
476 | return 1; | |
477 | ||
478 | *p = '\0'; | |
479 | tag = line; | |
480 | p++; | |
481 | while (av_isspace(*p)) | |
482 | p++; | |
483 | if (!av_strcasecmp(tag, "Location")) { | |
484 | if ((ret = parse_location(s, p)) < 0) | |
485 | return ret; | |
486 | *new_location = 1; | |
487 | } else if (!av_strcasecmp(tag, "Content-Length") && s->filesize == -1) { | |
488 | s->filesize = strtoll(p, NULL, 10); | |
489 | } else if (!av_strcasecmp(tag, "Content-Range")) { | |
490 | parse_content_range(h, p); | |
491 | } else if (!av_strcasecmp(tag, "Accept-Ranges") && | |
492 | !strncmp(p, "bytes", 5) && | |
493 | s->seekable == -1) { | |
494 | h->is_streamed = 0; | |
495 | } else if (!av_strcasecmp(tag, "Transfer-Encoding") && | |
496 | !av_strncasecmp(p, "chunked", 7)) { | |
497 | s->filesize = -1; | |
498 | s->chunksize = 0; | |
499 | } else if (!av_strcasecmp(tag, "WWW-Authenticate")) { | |
500 | ff_http_auth_handle_header(&s->auth_state, tag, p); | |
501 | } else if (!av_strcasecmp(tag, "Authentication-Info")) { | |
502 | ff_http_auth_handle_header(&s->auth_state, tag, p); | |
503 | } else if (!av_strcasecmp(tag, "Proxy-Authenticate")) { | |
504 | ff_http_auth_handle_header(&s->proxy_auth_state, tag, p); | |
505 | } else if (!av_strcasecmp(tag, "Connection")) { | |
506 | if (!strcmp(p, "close")) | |
507 | s->willclose = 1; | |
508 | } else if (!av_strcasecmp(tag, "Server")) { | |
509 | if (!av_strcasecmp(p, "AkamaiGHost")) { | |
510 | s->is_akamai = 1; | |
511 | } else if (!av_strncasecmp(p, "MediaGateway", 12)) { | |
512 | s->is_mediagateway = 1; | |
513 | } | |
514 | } else if (!av_strcasecmp(tag, "Content-Type")) { | |
515 | av_free(s->mime_type); | |
516 | s->mime_type = av_strdup(p); | |
517 | } else if (!av_strcasecmp(tag, "Set-Cookie")) { | |
518 | if (!s->cookies) { | |
519 | if (!(s->cookies = av_strdup(p))) | |
520 | return AVERROR(ENOMEM); | |
521 | } else { | |
522 | char *tmp = s->cookies; | |
523 | size_t str_size = strlen(tmp) + strlen(p) + 2; | |
524 | if (!(s->cookies = av_malloc(str_size))) { | |
525 | s->cookies = tmp; | |
526 | return AVERROR(ENOMEM); | |
527 | } | |
528 | snprintf(s->cookies, str_size, "%s\n%s", tmp, p); | |
529 | av_free(tmp); | |
530 | } | |
531 | } else if (!av_strcasecmp(tag, "Icy-MetaInt")) { | |
532 | s->icy_metaint = strtoll(p, NULL, 10); | |
533 | } else if (!av_strncasecmp(tag, "Icy-", 4)) { | |
534 | if ((ret = parse_icy(s, tag, p)) < 0) | |
535 | return ret; | |
536 | } else if (!av_strcasecmp(tag, "Content-Encoding")) { | |
537 | if ((ret = parse_content_encoding(h, p)) < 0) | |
538 | return ret; | |
539 | } | |
540 | } | |
541 | return 1; | |
542 | } | |
543 | ||
544 | /** | |
545 | * Create a string containing cookie values for use as a HTTP cookie header | |
546 | * field value for a particular path and domain from the cookie values stored in | |
547 | * the HTTP protocol context. The cookie string is stored in *cookies. | |
548 | * | |
549 | * @return a negative value if an error condition occurred, 0 otherwise | |
550 | */ | |
551 | static int get_cookies(HTTPContext *s, char **cookies, const char *path, | |
552 | const char *domain) | |
553 | { | |
554 | // cookie strings will look like Set-Cookie header field values. Multiple | |
555 | // Set-Cookie fields will result in multiple values delimited by a newline | |
556 | int ret = 0; | |
557 | char *next, *cookie, *set_cookies = av_strdup(s->cookies), *cset_cookies = set_cookies; | |
558 | ||
559 | if (!set_cookies) return AVERROR(EINVAL); | |
560 | ||
561 | *cookies = NULL; | |
562 | while ((cookie = av_strtok(set_cookies, "\n", &next))) { | |
563 | int domain_offset = 0; | |
564 | char *param, *next_param, *cdomain = NULL, *cpath = NULL, *cvalue = NULL; | |
565 | set_cookies = NULL; | |
566 | ||
567 | while ((param = av_strtok(cookie, "; ", &next_param))) { | |
568 | if (cookie) { | |
569 | // first key-value pair is the actual cookie value | |
570 | cvalue = av_strdup(param); | |
571 | cookie = NULL; | |
572 | } else if (!av_strncasecmp("path=", param, 5)) { | |
573 | av_free(cpath); | |
574 | cpath = av_strdup(¶m[5]); | |
575 | } else if (!av_strncasecmp("domain=", param, 7)) { | |
576 | // if the cookie specifies a sub-domain, skip the leading dot thereby | |
577 | // supporting URLs that point to sub-domains and the master domain | |
578 | int leading_dot = (param[7] == '.'); | |
579 | av_free(cdomain); | |
580 | cdomain = av_strdup(¶m[7+leading_dot]); | |
581 | } else { | |
582 | // ignore unknown attributes | |
583 | } | |
584 | } | |
585 | if (!cdomain) | |
586 | cdomain = av_strdup(domain); | |
587 | ||
588 | // ensure all of the necessary values are valid | |
589 | if (!cdomain || !cpath || !cvalue) { | |
590 | av_log(s, AV_LOG_WARNING, | |
591 | "Invalid cookie found, no value, path or domain specified\n"); | |
592 | goto done_cookie; | |
593 | } | |
594 | ||
595 | // check if the request path matches the cookie path | |
596 | if (av_strncasecmp(path, cpath, strlen(cpath))) | |
597 | goto done_cookie; | |
598 | ||
599 | // the domain should be at least the size of our cookie domain | |
600 | domain_offset = strlen(domain) - strlen(cdomain); | |
601 | if (domain_offset < 0) | |
602 | goto done_cookie; | |
603 | ||
604 | // match the cookie domain | |
605 | if (av_strcasecmp(&domain[domain_offset], cdomain)) | |
606 | goto done_cookie; | |
607 | ||
608 | // cookie parameters match, so copy the value | |
609 | if (!*cookies) { | |
610 | if (!(*cookies = av_strdup(cvalue))) { | |
611 | ret = AVERROR(ENOMEM); | |
612 | goto done_cookie; | |
613 | } | |
614 | } else { | |
615 | char *tmp = *cookies; | |
616 | size_t str_size = strlen(cvalue) + strlen(*cookies) + 3; | |
617 | if (!(*cookies = av_malloc(str_size))) { | |
618 | ret = AVERROR(ENOMEM); | |
619 | goto done_cookie; | |
620 | } | |
621 | snprintf(*cookies, str_size, "%s; %s", tmp, cvalue); | |
622 | av_free(tmp); | |
623 | } | |
624 | ||
625 | done_cookie: | |
626 | av_free(cdomain); | |
627 | av_free(cpath); | |
628 | av_free(cvalue); | |
629 | if (ret < 0) { | |
630 | if (*cookies) av_freep(cookies); | |
631 | av_free(cset_cookies); | |
632 | return ret; | |
633 | } | |
634 | } | |
635 | ||
636 | av_free(cset_cookies); | |
637 | ||
638 | return 0; | |
639 | } | |
640 | ||
641 | static inline int has_header(const char *str, const char *header) | |
642 | { | |
643 | /* header + 2 to skip over CRLF prefix. (make sure you have one!) */ | |
644 | if (!str) | |
645 | return 0; | |
646 | return av_stristart(str, header + 2, NULL) || av_stristr(str, header); | |
647 | } | |
648 | ||
649 | static int http_read_header(URLContext *h, int *new_location) | |
650 | { | |
651 | HTTPContext *s = h->priv_data; | |
652 | char line[MAX_URL_SIZE]; | |
653 | int err = 0; | |
654 | ||
655 | s->chunksize = -1; | |
656 | ||
657 | for (;;) { | |
658 | if ((err = http_get_line(s, line, sizeof(line))) < 0) | |
659 | return err; | |
660 | ||
661 | av_log(h, AV_LOG_DEBUG, "header='%s'\n", line); | |
662 | ||
663 | err = process_line(h, line, s->line_count, new_location); | |
664 | if (err < 0) | |
665 | return err; | |
666 | if (err == 0) | |
667 | break; | |
668 | s->line_count++; | |
669 | } | |
670 | ||
671 | if (s->seekable == -1 && s->is_mediagateway && s->filesize == 2000000000) | |
672 | h->is_streamed = 1; /* we can in fact _not_ seek */ | |
673 | ||
674 | return err; | |
675 | } | |
676 | ||
677 | static int http_connect(URLContext *h, const char *path, const char *local_path, | |
678 | const char *hoststr, const char *auth, | |
679 | const char *proxyauth, int *new_location) | |
680 | { | |
681 | HTTPContext *s = h->priv_data; | |
682 | int post, err; | |
683 | char headers[HTTP_HEADERS_SIZE] = ""; | |
684 | char *authstr = NULL, *proxyauthstr = NULL; | |
685 | int64_t off = s->off; | |
686 | int len = 0; | |
687 | const char *method; | |
688 | int send_expect_100 = 0; | |
689 | ||
690 | /* send http header */ | |
691 | post = h->flags & AVIO_FLAG_WRITE; | |
692 | ||
693 | if (s->post_data) { | |
694 | /* force POST method and disable chunked encoding when | |
695 | * custom HTTP post data is set */ | |
696 | post = 1; | |
697 | s->chunked_post = 0; | |
698 | } | |
699 | ||
700 | if (s->method) | |
701 | method = s->method; | |
702 | else | |
703 | method = post ? "POST" : "GET"; | |
704 | ||
705 | authstr = ff_http_auth_create_response(&s->auth_state, auth, | |
706 | local_path, method); | |
707 | proxyauthstr = ff_http_auth_create_response(&s->proxy_auth_state, proxyauth, | |
708 | local_path, method); | |
709 | if (post && !s->post_data) { | |
710 | send_expect_100 = s->send_expect_100; | |
711 | /* The user has supplied authentication but we don't know the auth type, | |
712 | * send Expect: 100-continue to get the 401 response including the | |
713 | * WWW-Authenticate header, or an 100 continue if no auth actually | |
714 | * is needed. */ | |
715 | if (auth && *auth && | |
716 | s->auth_state.auth_type == HTTP_AUTH_NONE && | |
717 | s->http_code != 401) | |
718 | send_expect_100 = 1; | |
719 | } | |
720 | ||
721 | /* set default headers if needed */ | |
722 | if (!has_header(s->headers, "\r\nUser-Agent: ")) | |
723 | len += av_strlcatf(headers + len, sizeof(headers) - len, | |
724 | "User-Agent: %s\r\n", s->user_agent); | |
725 | if (!has_header(s->headers, "\r\nAccept: ")) | |
726 | len += av_strlcpy(headers + len, "Accept: */*\r\n", | |
727 | sizeof(headers) - len); | |
728 | // Note: we send this on purpose even when s->off is 0 when we're probing, | |
729 | // since it allows us to detect more reliably if a (non-conforming) | |
730 | // server supports seeking by analysing the reply headers. | |
731 | if (!has_header(s->headers, "\r\nRange: ") && !post && (s->off > 0 || s->end_off || s->seekable == -1)) { | |
732 | len += av_strlcatf(headers + len, sizeof(headers) - len, | |
733 | "Range: bytes=%"PRId64"-", s->off); | |
734 | if (s->end_off) | |
735 | len += av_strlcatf(headers + len, sizeof(headers) - len, | |
736 | "%"PRId64, s->end_off - 1); | |
737 | len += av_strlcpy(headers + len, "\r\n", | |
738 | sizeof(headers) - len); | |
739 | } | |
740 | if (send_expect_100 && !has_header(s->headers, "\r\nExpect: ")) | |
741 | len += av_strlcatf(headers + len, sizeof(headers) - len, | |
742 | "Expect: 100-continue\r\n"); | |
743 | ||
744 | if (!has_header(s->headers, "\r\nConnection: ")) { | |
745 | if (s->multiple_requests) | |
746 | len += av_strlcpy(headers + len, "Connection: keep-alive\r\n", | |
747 | sizeof(headers) - len); | |
748 | else | |
749 | len += av_strlcpy(headers + len, "Connection: close\r\n", | |
750 | sizeof(headers) - len); | |
751 | } | |
752 | ||
753 | if (!has_header(s->headers, "\r\nHost: ")) | |
754 | len += av_strlcatf(headers + len, sizeof(headers) - len, | |
755 | "Host: %s\r\n", hoststr); | |
756 | if (!has_header(s->headers, "\r\nContent-Length: ") && s->post_data) | |
757 | len += av_strlcatf(headers + len, sizeof(headers) - len, | |
758 | "Content-Length: %d\r\n", s->post_datalen); | |
759 | ||
760 | if (!has_header(s->headers, "\r\nContent-Type: ") && s->content_type) | |
761 | len += av_strlcatf(headers + len, sizeof(headers) - len, | |
762 | "Content-Type: %s\r\n", s->content_type); | |
763 | if (!has_header(s->headers, "\r\nCookie: ") && s->cookies) { | |
764 | char *cookies = NULL; | |
765 | if (!get_cookies(s, &cookies, path, hoststr) && cookies) { | |
766 | len += av_strlcatf(headers + len, sizeof(headers) - len, | |
767 | "Cookie: %s\r\n", cookies); | |
768 | av_free(cookies); | |
769 | } | |
770 | } | |
771 | if (!has_header(s->headers, "\r\nIcy-MetaData: ") && s->icy) | |
772 | len += av_strlcatf(headers + len, sizeof(headers) - len, | |
773 | "Icy-MetaData: %d\r\n", 1); | |
774 | ||
775 | /* now add in custom headers */ | |
776 | if (s->headers) | |
777 | av_strlcpy(headers + len, s->headers, sizeof(headers) - len); | |
778 | ||
779 | snprintf(s->buffer, sizeof(s->buffer), | |
780 | "%s %s HTTP/1.1\r\n" | |
781 | "%s" | |
782 | "%s" | |
783 | "%s" | |
784 | "%s%s" | |
785 | "\r\n", | |
786 | method, | |
787 | path, | |
788 | post && s->chunked_post ? "Transfer-Encoding: chunked\r\n" : "", | |
789 | headers, | |
790 | authstr ? authstr : "", | |
791 | proxyauthstr ? "Proxy-" : "", proxyauthstr ? proxyauthstr : ""); | |
792 | ||
793 | av_log(h, AV_LOG_DEBUG, "request: %s\n", s->buffer); | |
794 | ||
795 | if ((err = ffurl_write(s->hd, s->buffer, strlen(s->buffer))) < 0) | |
796 | goto done; | |
797 | ||
798 | if (s->post_data) | |
799 | if ((err = ffurl_write(s->hd, s->post_data, s->post_datalen)) < 0) | |
800 | goto done; | |
801 | ||
802 | /* init input buffer */ | |
803 | s->buf_ptr = s->buffer; | |
804 | s->buf_end = s->buffer; | |
805 | s->line_count = 0; | |
806 | s->off = 0; | |
807 | s->icy_data_read = 0; | |
808 | s->filesize = -1; | |
809 | s->willclose = 0; | |
810 | s->end_chunked_post = 0; | |
811 | s->end_header = 0; | |
812 | if (post && !s->post_data && !send_expect_100) { | |
813 | /* Pretend that it did work. We didn't read any header yet, since | |
814 | * we've still to send the POST data, but the code calling this | |
815 | * function will check http_code after we return. */ | |
816 | s->http_code = 200; | |
817 | err = 0; | |
818 | goto done; | |
819 | } | |
820 | ||
821 | /* wait for header */ | |
822 | err = http_read_header(h, new_location); | |
823 | if (err < 0) | |
824 | goto done; | |
825 | ||
826 | err = (off == s->off) ? 0 : -1; | |
827 | done: | |
828 | av_freep(&authstr); | |
829 | av_freep(&proxyauthstr); | |
830 | return err; | |
831 | } | |
832 | ||
833 | static int http_buf_read(URLContext *h, uint8_t *buf, int size) | |
834 | { | |
835 | HTTPContext *s = h->priv_data; | |
836 | int len; | |
837 | /* read bytes from input buffer first */ | |
838 | len = s->buf_end - s->buf_ptr; | |
839 | if (len > 0) { | |
840 | if (len > size) | |
841 | len = size; | |
842 | memcpy(buf, s->buf_ptr, len); | |
843 | s->buf_ptr += len; | |
844 | } else { | |
845 | if ((!s->willclose || s->chunksize < 0) && | |
846 | s->filesize >= 0 && s->off >= s->filesize) | |
847 | return AVERROR_EOF; | |
848 | len = ffurl_read(s->hd, buf, size); | |
849 | } | |
850 | if (len > 0) { | |
851 | s->off += len; | |
852 | if (s->chunksize > 0) | |
853 | s->chunksize -= len; | |
854 | } | |
855 | return len; | |
856 | } | |
857 | ||
858 | #if CONFIG_ZLIB | |
859 | #define DECOMPRESS_BUF_SIZE (256 * 1024) | |
860 | static int http_buf_read_compressed(URLContext *h, uint8_t *buf, int size) | |
861 | { | |
862 | HTTPContext *s = h->priv_data; | |
863 | int ret; | |
864 | ||
865 | if (!s->inflate_buffer) { | |
866 | s->inflate_buffer = av_malloc(DECOMPRESS_BUF_SIZE); | |
867 | if (!s->inflate_buffer) | |
868 | return AVERROR(ENOMEM); | |
869 | } | |
870 | ||
871 | if (s->inflate_stream.avail_in == 0) { | |
872 | int read = http_buf_read(h, s->inflate_buffer, DECOMPRESS_BUF_SIZE); | |
873 | if (read <= 0) | |
874 | return read; | |
875 | s->inflate_stream.next_in = s->inflate_buffer; | |
876 | s->inflate_stream.avail_in = read; | |
877 | } | |
878 | ||
879 | s->inflate_stream.avail_out = size; | |
880 | s->inflate_stream.next_out = buf; | |
881 | ||
882 | ret = inflate(&s->inflate_stream, Z_SYNC_FLUSH); | |
883 | if (ret != Z_OK && ret != Z_STREAM_END) | |
884 | av_log(h, AV_LOG_WARNING, "inflate return value: %d, %s\n", | |
885 | ret, s->inflate_stream.msg); | |
886 | ||
887 | return size - s->inflate_stream.avail_out; | |
888 | } | |
889 | #endif /* CONFIG_ZLIB */ | |
890 | ||
891 | static int http_read_stream(URLContext *h, uint8_t *buf, int size) | |
892 | { | |
893 | HTTPContext *s = h->priv_data; | |
894 | int err, new_location; | |
895 | ||
896 | if (!s->hd) | |
897 | return AVERROR_EOF; | |
898 | ||
899 | if (s->end_chunked_post && !s->end_header) { | |
900 | err = http_read_header(h, &new_location); | |
901 | if (err < 0) | |
902 | return err; | |
903 | } | |
904 | ||
905 | if (s->chunksize >= 0) { | |
906 | if (!s->chunksize) { | |
907 | char line[32]; | |
908 | ||
909 | do { | |
910 | if ((err = http_get_line(s, line, sizeof(line))) < 0) | |
911 | return err; | |
912 | } while (!*line); /* skip CR LF from last chunk */ | |
913 | ||
914 | s->chunksize = strtoll(line, NULL, 16); | |
915 | ||
916 | av_dlog(NULL, "Chunked encoding data size: %"PRId64"'\n", | |
917 | s->chunksize); | |
918 | ||
919 | if (!s->chunksize) | |
920 | return 0; | |
921 | } | |
922 | size = FFMIN(size, s->chunksize); | |
923 | } | |
924 | #if CONFIG_ZLIB | |
925 | if (s->compressed) | |
926 | return http_buf_read_compressed(h, buf, size); | |
927 | #endif /* CONFIG_ZLIB */ | |
928 | return http_buf_read(h, buf, size); | |
929 | } | |
930 | ||
931 | // Like http_read_stream(), but no short reads. | |
932 | // Assumes partial reads are an error. | |
933 | static int http_read_stream_all(URLContext *h, uint8_t *buf, int size) | |
934 | { | |
935 | int pos = 0; | |
936 | while (pos < size) { | |
937 | int len = http_read_stream(h, buf + pos, size - pos); | |
938 | if (len < 0) | |
939 | return len; | |
940 | pos += len; | |
941 | } | |
942 | return pos; | |
943 | } | |
944 | ||
945 | static void update_metadata(HTTPContext *s, char *data) | |
946 | { | |
947 | char *key; | |
948 | char *val; | |
949 | char *end; | |
950 | char *next = data; | |
951 | ||
952 | while (*next) { | |
953 | key = next; | |
954 | val = strstr(key, "='"); | |
955 | if (!val) | |
956 | break; | |
957 | end = strstr(val, "';"); | |
958 | if (!end) | |
959 | break; | |
960 | ||
961 | *val = '\0'; | |
962 | *end = '\0'; | |
963 | val += 2; | |
964 | ||
965 | av_dict_set(&s->metadata, key, val, 0); | |
966 | ||
967 | next = end + 2; | |
968 | } | |
969 | } | |
970 | ||
971 | static int store_icy(URLContext *h, int size) | |
972 | { | |
973 | HTTPContext *s = h->priv_data; | |
974 | /* until next metadata packet */ | |
975 | int remaining = s->icy_metaint - s->icy_data_read; | |
976 | ||
977 | if (remaining < 0) | |
978 | return AVERROR_INVALIDDATA; | |
979 | ||
980 | if (!remaining) { | |
981 | /* The metadata packet is variable sized. It has a 1 byte header | |
982 | * which sets the length of the packet (divided by 16). If it's 0, | |
983 | * the metadata doesn't change. After the packet, icy_metaint bytes | |
984 | * of normal data follows. */ | |
985 | uint8_t ch; | |
986 | int len = http_read_stream_all(h, &ch, 1); | |
987 | if (len < 0) | |
988 | return len; | |
989 | if (ch > 0) { | |
990 | char data[255 * 16 + 1]; | |
991 | int ret; | |
992 | len = ch * 16; | |
993 | ret = http_read_stream_all(h, data, len); | |
994 | if (ret < 0) | |
995 | return ret; | |
996 | data[len + 1] = 0; | |
997 | if ((ret = av_opt_set(s, "icy_metadata_packet", data, 0)) < 0) | |
998 | return ret; | |
999 | update_metadata(s, data); | |
1000 | } | |
1001 | s->icy_data_read = 0; | |
1002 | remaining = s->icy_metaint; | |
1003 | } | |
1004 | ||
1005 | return FFMIN(size, remaining); | |
1006 | } | |
1007 | ||
1008 | static int http_read(URLContext *h, uint8_t *buf, int size) | |
1009 | { | |
1010 | HTTPContext *s = h->priv_data; | |
1011 | ||
1012 | if (s->icy_metaint > 0) { | |
1013 | size = store_icy(h, size); | |
1014 | if (size < 0) | |
1015 | return size; | |
1016 | } | |
1017 | ||
1018 | size = http_read_stream(h, buf, size); | |
1019 | if (size > 0) | |
1020 | s->icy_data_read += size; | |
1021 | return size; | |
1022 | } | |
1023 | ||
1024 | /* used only when posting data */ | |
1025 | static int http_write(URLContext *h, const uint8_t *buf, int size) | |
1026 | { | |
1027 | char temp[11] = ""; /* 32-bit hex + CRLF + nul */ | |
1028 | int ret; | |
1029 | char crlf[] = "\r\n"; | |
1030 | HTTPContext *s = h->priv_data; | |
1031 | ||
1032 | if (!s->chunked_post) { | |
1033 | /* non-chunked data is sent without any special encoding */ | |
1034 | return ffurl_write(s->hd, buf, size); | |
1035 | } | |
1036 | ||
1037 | /* silently ignore zero-size data since chunk encoding that would | |
1038 | * signal EOF */ | |
1039 | if (size > 0) { | |
1040 | /* upload data using chunked encoding */ | |
1041 | snprintf(temp, sizeof(temp), "%x\r\n", size); | |
1042 | ||
1043 | if ((ret = ffurl_write(s->hd, temp, strlen(temp))) < 0 || | |
1044 | (ret = ffurl_write(s->hd, buf, size)) < 0 || | |
1045 | (ret = ffurl_write(s->hd, crlf, sizeof(crlf) - 1)) < 0) | |
1046 | return ret; | |
1047 | } | |
1048 | return size; | |
1049 | } | |
1050 | ||
1051 | static int http_shutdown(URLContext *h, int flags) | |
1052 | { | |
1053 | int ret = 0; | |
1054 | char footer[] = "0\r\n\r\n"; | |
1055 | HTTPContext *s = h->priv_data; | |
1056 | ||
1057 | /* signal end of chunked encoding if used */ | |
1058 | if ((flags & AVIO_FLAG_WRITE) && s->chunked_post) { | |
1059 | ret = ffurl_write(s->hd, footer, sizeof(footer) - 1); | |
1060 | ret = ret > 0 ? 0 : ret; | |
1061 | s->end_chunked_post = 1; | |
1062 | } | |
1063 | ||
1064 | return ret; | |
1065 | } | |
1066 | ||
1067 | static int http_close(URLContext *h) | |
1068 | { | |
1069 | int ret = 0; | |
1070 | HTTPContext *s = h->priv_data; | |
1071 | ||
1072 | #if CONFIG_ZLIB | |
1073 | inflateEnd(&s->inflate_stream); | |
1074 | av_freep(&s->inflate_buffer); | |
1075 | #endif /* CONFIG_ZLIB */ | |
1076 | ||
1077 | if (!s->end_chunked_post) | |
1078 | /* Close the write direction by sending the end of chunked encoding. */ | |
1079 | ret = http_shutdown(h, h->flags); | |
1080 | ||
1081 | if (s->hd) | |
1082 | ffurl_closep(&s->hd); | |
1083 | av_dict_free(&s->chained_options); | |
1084 | return ret; | |
1085 | } | |
1086 | ||
1087 | static int64_t http_seek(URLContext *h, int64_t off, int whence) | |
1088 | { | |
1089 | HTTPContext *s = h->priv_data; | |
1090 | URLContext *old_hd = s->hd; | |
1091 | int64_t old_off = s->off; | |
1092 | uint8_t old_buf[BUFFER_SIZE]; | |
1093 | int old_buf_size, ret; | |
1094 | AVDictionary *options = NULL; | |
1095 | ||
1096 | if (whence == AVSEEK_SIZE) | |
1097 | return s->filesize; | |
1098 | else if ((whence == SEEK_CUR && off == 0) || | |
1099 | (whence == SEEK_SET && off == s->off)) | |
1100 | return s->off; | |
1101 | else if ((s->filesize == -1 && whence == SEEK_END) || h->is_streamed) | |
1102 | return AVERROR(ENOSYS); | |
1103 | ||
1104 | if (whence == SEEK_CUR) | |
1105 | off += s->off; | |
1106 | else if (whence == SEEK_END) | |
1107 | off += s->filesize; | |
1108 | else if (whence != SEEK_SET) | |
1109 | return AVERROR(EINVAL); | |
1110 | if (off < 0) | |
1111 | return AVERROR(EINVAL); | |
1112 | s->off = off; | |
1113 | ||
1114 | /* we save the old context in case the seek fails */ | |
1115 | old_buf_size = s->buf_end - s->buf_ptr; | |
1116 | memcpy(old_buf, s->buf_ptr, old_buf_size); | |
1117 | s->hd = NULL; | |
1118 | ||
1119 | /* if it fails, continue on old connection */ | |
1120 | av_dict_copy(&options, s->chained_options, 0); | |
1121 | if ((ret = http_open_cnx(h, &options)) < 0) { | |
1122 | av_dict_free(&options); | |
1123 | memcpy(s->buffer, old_buf, old_buf_size); | |
1124 | s->buf_ptr = s->buffer; | |
1125 | s->buf_end = s->buffer + old_buf_size; | |
1126 | s->hd = old_hd; | |
1127 | s->off = old_off; | |
1128 | return ret; | |
1129 | } | |
1130 | av_dict_free(&options); | |
1131 | ffurl_close(old_hd); | |
1132 | return off; | |
1133 | } | |
1134 | ||
1135 | static int http_get_file_handle(URLContext *h) | |
1136 | { | |
1137 | HTTPContext *s = h->priv_data; | |
1138 | return ffurl_get_file_handle(s->hd); | |
1139 | } | |
1140 | ||
1141 | #define HTTP_CLASS(flavor) \ | |
1142 | static const AVClass flavor ## _context_class = { \ | |
1143 | .class_name = # flavor, \ | |
1144 | .item_name = av_default_item_name, \ | |
1145 | .option = options, \ | |
1146 | .version = LIBAVUTIL_VERSION_INT, \ | |
1147 | } | |
1148 | ||
1149 | #if CONFIG_HTTP_PROTOCOL | |
1150 | HTTP_CLASS(http); | |
1151 | ||
1152 | URLProtocol ff_http_protocol = { | |
1153 | .name = "http", | |
1154 | .url_open2 = http_open, | |
1155 | .url_read = http_read, | |
1156 | .url_write = http_write, | |
1157 | .url_seek = http_seek, | |
1158 | .url_close = http_close, | |
1159 | .url_get_file_handle = http_get_file_handle, | |
1160 | .url_shutdown = http_shutdown, | |
1161 | .priv_data_size = sizeof(HTTPContext), | |
1162 | .priv_data_class = &http_context_class, | |
1163 | .flags = URL_PROTOCOL_FLAG_NETWORK, | |
1164 | }; | |
1165 | #endif /* CONFIG_HTTP_PROTOCOL */ | |
1166 | ||
1167 | #if CONFIG_HTTPS_PROTOCOL | |
1168 | HTTP_CLASS(https); | |
1169 | ||
1170 | URLProtocol ff_https_protocol = { | |
1171 | .name = "https", | |
1172 | .url_open2 = http_open, | |
1173 | .url_read = http_read, | |
1174 | .url_write = http_write, | |
1175 | .url_seek = http_seek, | |
1176 | .url_close = http_close, | |
1177 | .url_get_file_handle = http_get_file_handle, | |
1178 | .url_shutdown = http_shutdown, | |
1179 | .priv_data_size = sizeof(HTTPContext), | |
1180 | .priv_data_class = &https_context_class, | |
1181 | .flags = URL_PROTOCOL_FLAG_NETWORK, | |
1182 | }; | |
1183 | #endif /* CONFIG_HTTPS_PROTOCOL */ | |
1184 | ||
1185 | #if CONFIG_HTTPPROXY_PROTOCOL | |
1186 | static int http_proxy_close(URLContext *h) | |
1187 | { | |
1188 | HTTPContext *s = h->priv_data; | |
1189 | if (s->hd) | |
1190 | ffurl_closep(&s->hd); | |
1191 | return 0; | |
1192 | } | |
1193 | ||
1194 | static int http_proxy_open(URLContext *h, const char *uri, int flags) | |
1195 | { | |
1196 | HTTPContext *s = h->priv_data; | |
1197 | char hostname[1024], hoststr[1024]; | |
1198 | char auth[1024], pathbuf[1024], *path; | |
1199 | char lower_url[100]; | |
1200 | int port, ret = 0, attempts = 0; | |
1201 | HTTPAuthType cur_auth_type; | |
1202 | char *authstr; | |
1203 | int new_loc; | |
1204 | ||
1205 | if( s->seekable == 1 ) | |
1206 | h->is_streamed = 0; | |
1207 | else | |
1208 | h->is_streamed = 1; | |
1209 | ||
1210 | av_url_split(NULL, 0, auth, sizeof(auth), hostname, sizeof(hostname), &port, | |
1211 | pathbuf, sizeof(pathbuf), uri); | |
1212 | ff_url_join(hoststr, sizeof(hoststr), NULL, NULL, hostname, port, NULL); | |
1213 | path = pathbuf; | |
1214 | if (*path == '/') | |
1215 | path++; | |
1216 | ||
1217 | ff_url_join(lower_url, sizeof(lower_url), "tcp", NULL, hostname, port, | |
1218 | NULL); | |
1219 | redo: | |
1220 | ret = ffurl_open(&s->hd, lower_url, AVIO_FLAG_READ_WRITE, | |
1221 | &h->interrupt_callback, NULL); | |
1222 | if (ret < 0) | |
1223 | return ret; | |
1224 | ||
1225 | authstr = ff_http_auth_create_response(&s->proxy_auth_state, auth, | |
1226 | path, "CONNECT"); | |
1227 | snprintf(s->buffer, sizeof(s->buffer), | |
1228 | "CONNECT %s HTTP/1.1\r\n" | |
1229 | "Host: %s\r\n" | |
1230 | "Connection: close\r\n" | |
1231 | "%s%s" | |
1232 | "\r\n", | |
1233 | path, | |
1234 | hoststr, | |
1235 | authstr ? "Proxy-" : "", authstr ? authstr : ""); | |
1236 | av_freep(&authstr); | |
1237 | ||
1238 | if ((ret = ffurl_write(s->hd, s->buffer, strlen(s->buffer))) < 0) | |
1239 | goto fail; | |
1240 | ||
1241 | s->buf_ptr = s->buffer; | |
1242 | s->buf_end = s->buffer; | |
1243 | s->line_count = 0; | |
1244 | s->filesize = -1; | |
1245 | cur_auth_type = s->proxy_auth_state.auth_type; | |
1246 | ||
1247 | /* Note: This uses buffering, potentially reading more than the | |
1248 | * HTTP header. If tunneling a protocol where the server starts | |
1249 | * the conversation, we might buffer part of that here, too. | |
1250 | * Reading that requires using the proper ffurl_read() function | |
1251 | * on this URLContext, not using the fd directly (as the tls | |
1252 | * protocol does). This shouldn't be an issue for tls though, | |
1253 | * since the client starts the conversation there, so there | |
1254 | * is no extra data that we might buffer up here. | |
1255 | */ | |
1256 | ret = http_read_header(h, &new_loc); | |
1257 | if (ret < 0) | |
1258 | goto fail; | |
1259 | ||
1260 | attempts++; | |
1261 | if (s->http_code == 407 && | |
1262 | (cur_auth_type == HTTP_AUTH_NONE || s->proxy_auth_state.stale) && | |
1263 | s->proxy_auth_state.auth_type != HTTP_AUTH_NONE && attempts < 2) { | |
1264 | ffurl_closep(&s->hd); | |
1265 | goto redo; | |
1266 | } | |
1267 | ||
1268 | if (s->http_code < 400) | |
1269 | return 0; | |
1270 | ret = AVERROR(EIO); | |
1271 | ||
1272 | fail: | |
1273 | http_proxy_close(h); | |
1274 | return ret; | |
1275 | } | |
1276 | ||
1277 | static int http_proxy_write(URLContext *h, const uint8_t *buf, int size) | |
1278 | { | |
1279 | HTTPContext *s = h->priv_data; | |
1280 | return ffurl_write(s->hd, buf, size); | |
1281 | } | |
1282 | ||
1283 | URLProtocol ff_httpproxy_protocol = { | |
1284 | .name = "httpproxy", | |
1285 | .url_open = http_proxy_open, | |
1286 | .url_read = http_buf_read, | |
1287 | .url_write = http_proxy_write, | |
1288 | .url_close = http_proxy_close, | |
1289 | .url_get_file_handle = http_get_file_handle, | |
1290 | .priv_data_size = sizeof(HTTPContext), | |
1291 | .flags = URL_PROTOCOL_FLAG_NETWORK, | |
1292 | }; | |
1293 | #endif /* CONFIG_HTTPPROXY_PROTOCOL */ |