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