Reindent
[ffmpeg.git] / libavformat / http.c
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 "libavutil/avstring.h"
23 #include "avformat.h"
24 #include <unistd.h>
25 #include <strings.h>
26 #include "internal.h"
27 #include "network.h"
28 #include "http.h"
29 #include "os_support.h"
30 #include "httpauth.h"
31
32 /* XXX: POST protocol is not completely implemented because ffmpeg uses
33    only a subset of it. */
34
35 /* used for protocol handling */
36 #define BUFFER_SIZE 1024
37 #define URL_SIZE    4096
38 #define MAX_REDIRECTS 8
39
40 typedef struct {
41     URLContext *hd;
42     unsigned char buffer[BUFFER_SIZE], *buf_ptr, *buf_end;
43     int line_count;
44     int http_code;
45     int64_t chunksize;      /**< Used if "Transfer-Encoding: chunked" otherwise -1. */
46     int64_t off, filesize;
47     char location[URL_SIZE];
48     HTTPAuthState auth_state;
49     int init;
50     unsigned char headers[BUFFER_SIZE];
51 } HTTPContext;
52
53 static int http_connect(URLContext *h, const char *path, const char *hoststr,
54                         const char *auth, int *new_location);
55
56 void ff_http_set_headers(URLContext *h, const char *headers)
57 {
58     HTTPContext *s = h->priv_data;
59     int len = strlen(headers);
60
61     if (len && strcmp("\r\n", headers + len - 2))
62         av_log(NULL, AV_LOG_ERROR, "No trailing CRLF found in HTTP header.\n");
63
64     av_strlcpy(s->headers, headers, sizeof(s->headers));
65 }
66
67 void ff_http_set_chunked_transfer_encoding(URLContext *h, int is_chunked)
68 {
69     ((HTTPContext*)h->priv_data)->chunksize = is_chunked ? 0 : -1;
70 }
71
72 /* return non zero if error */
73 static int http_open_cnx(URLContext *h)
74 {
75     const char *path, *proxy_path;
76     char hostname[1024], hoststr[1024];
77     char auth[1024];
78     char path1[1024];
79     char buf[1024];
80     int port, use_proxy, err, location_changed = 0, redirects = 0;
81     HTTPAuthType cur_auth_type;
82     HTTPContext *s = h->priv_data;
83     URLContext *hd = NULL;
84
85     s->init = 1;
86     proxy_path = getenv("http_proxy");
87     use_proxy = (proxy_path != NULL) && !getenv("no_proxy") &&
88         av_strstart(proxy_path, "http://", NULL);
89
90     /* fill the dest addr */
91  redo:
92     /* needed in any case to build the host string */
93     ff_url_split(NULL, 0, auth, sizeof(auth), hostname, sizeof(hostname), &port,
94                  path1, sizeof(path1), s->location);
95     ff_url_join(hoststr, sizeof(hoststr), NULL, NULL, hostname, port, NULL);
96
97     if (use_proxy) {
98         ff_url_split(NULL, 0, auth, sizeof(auth), hostname, sizeof(hostname), &port,
99                      NULL, 0, proxy_path);
100         path = s->location;
101     } else {
102         if (path1[0] == '\0')
103             path = "/";
104         else
105             path = path1;
106     }
107     if (port < 0)
108         port = 80;
109
110     ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL);
111     err = url_open(&hd, buf, URL_RDWR);
112     if (err < 0)
113         goto fail;
114
115     s->hd = hd;
116     cur_auth_type = s->auth_state.auth_type;
117     if (http_connect(h, path, hoststr, auth, &location_changed) < 0)
118         goto fail;
119     if (s->http_code == 401) {
120         if (cur_auth_type == HTTP_AUTH_NONE && s->auth_state.auth_type != HTTP_AUTH_NONE) {
121             url_close(hd);
122             goto redo;
123         } else
124             goto fail;
125     }
126     if ((s->http_code == 302 || s->http_code == 303) && location_changed == 1) {
127         /* url moved, get next */
128         url_close(hd);
129         if (redirects++ >= MAX_REDIRECTS)
130             return AVERROR(EIO);
131         location_changed = 0;
132         goto redo;
133     }
134     return 0;
135  fail:
136     if (hd)
137         url_close(hd);
138     s->hd = NULL;
139     return AVERROR(EIO);
140 }
141
142 static int http_open(URLContext *h, const char *uri, int flags)
143 {
144     HTTPContext *s;
145
146     h->is_streamed = 1;
147
148     s = av_malloc(sizeof(HTTPContext));
149     if (!s) {
150         return AVERROR(ENOMEM);
151     }
152     h->priv_data = s;
153     s->filesize = -1;
154     s->chunksize = 0; /* Default to chunked POSTs */
155     s->off = 0;
156     s->init = 0;
157     s->hd = NULL;
158     *s->headers = '\0';
159     memset(&s->auth_state, 0, sizeof(s->auth_state));
160     av_strlcpy(s->location, uri, URL_SIZE);
161
162     return 0;
163 }
164 static int http_getc(HTTPContext *s)
165 {
166     int len;
167     if (s->buf_ptr >= s->buf_end) {
168         len = url_read(s->hd, s->buffer, BUFFER_SIZE);
169         if (len < 0) {
170             return AVERROR(EIO);
171         } else if (len == 0) {
172             return -1;
173         } else {
174             s->buf_ptr = s->buffer;
175             s->buf_end = s->buffer + len;
176         }
177     }
178     return *s->buf_ptr++;
179 }
180
181 static int http_get_line(HTTPContext *s, char *line, int line_size)
182 {
183     int ch;
184     char *q;
185
186     q = line;
187     for(;;) {
188         ch = http_getc(s);
189         if (ch < 0)
190             return AVERROR(EIO);
191         if (ch == '\n') {
192             /* process line */
193             if (q > line && q[-1] == '\r')
194                 q--;
195             *q = '\0';
196
197             return 0;
198         } else {
199             if ((q - line) < line_size - 1)
200                 *q++ = ch;
201         }
202     }
203 }
204
205 static int process_line(URLContext *h, char *line, int line_count,
206                         int *new_location)
207 {
208     HTTPContext *s = h->priv_data;
209     char *tag, *p;
210
211     /* end of header */
212     if (line[0] == '\0')
213         return 0;
214
215     p = line;
216     if (line_count == 0) {
217         while (!isspace(*p) && *p != '\0')
218             p++;
219         while (isspace(*p))
220             p++;
221         s->http_code = strtol(p, NULL, 10);
222
223         dprintf(NULL, "http_code=%d\n", s->http_code);
224
225         /* error codes are 4xx and 5xx, but regard 401 as a success, so we
226          * don't abort until all headers have been parsed. */
227         if (s->http_code >= 400 && s->http_code < 600 && s->http_code != 401)
228             return -1;
229     } else {
230         while (*p != '\0' && *p != ':')
231             p++;
232         if (*p != ':')
233             return 1;
234
235         *p = '\0';
236         tag = line;
237         p++;
238         while (isspace(*p))
239             p++;
240         if (!strcmp(tag, "Location")) {
241             strcpy(s->location, p);
242             *new_location = 1;
243         } else if (!strcmp (tag, "Content-Length") && s->filesize == -1) {
244             s->filesize = atoll(p);
245         } else if (!strcmp (tag, "Content-Range")) {
246             /* "bytes $from-$to/$document_size" */
247             const char *slash;
248             if (!strncmp (p, "bytes ", 6)) {
249                 p += 6;
250                 s->off = atoll(p);
251                 if ((slash = strchr(p, '/')) && strlen(slash) > 0)
252                     s->filesize = atoll(slash+1);
253             }
254             h->is_streamed = 0; /* we _can_ in fact seek */
255         } else if (!strcmp (tag, "Transfer-Encoding") && !strncasecmp(p, "chunked", 7)) {
256             s->filesize = -1;
257             s->chunksize = 0;
258         } else if (!strcmp (tag, "WWW-Authenticate")) {
259             ff_http_auth_handle_header(&s->auth_state, tag, p);
260         } else if (!strcmp (tag, "Authentication-Info")) {
261             ff_http_auth_handle_header(&s->auth_state, tag, p);
262         }
263     }
264     return 1;
265 }
266
267 static inline int has_header(const char *str, const char *header)
268 {
269     /* header + 2 to skip over CRLF prefix. (make sure you have one!) */
270     return av_stristart(str, header + 2, NULL) || av_stristr(str, header);
271 }
272
273 static int http_connect(URLContext *h, const char *path, const char *hoststr,
274                         const char *auth, int *new_location)
275 {
276     HTTPContext *s = h->priv_data;
277     int post, err;
278     char line[1024];
279     char headers[1024] = "";
280     char *authstr = NULL;
281     int64_t off = s->off;
282     int len = 0;
283
284
285     /* send http header */
286     post = h->flags & URL_WRONLY;
287     authstr = ff_http_auth_create_response(&s->auth_state, auth, path,
288                                         post ? "POST" : "GET");
289
290     /* set default headers if needed */
291     if (!has_header(s->headers, "\r\nUser-Agent: "))
292        len += av_strlcatf(headers + len, sizeof(headers) - len,
293                           "User-Agent: %s\r\n", LIBAVFORMAT_IDENT);
294     if (!has_header(s->headers, "\r\nAccept: "))
295         len += av_strlcpy(headers + len, "Accept: */*\r\n",
296                           sizeof(headers) - len);
297     if (!has_header(s->headers, "\r\nRange: "))
298         len += av_strlcatf(headers + len, sizeof(headers) - len,
299                            "Range: bytes=%"PRId64"-\r\n", s->off);
300     if (!has_header(s->headers, "\r\nConnection: "))
301         len += av_strlcpy(headers + len, "Connection: close\r\n",
302                           sizeof(headers)-len);
303     if (!has_header(s->headers, "\r\nHost: "))
304         len += av_strlcatf(headers + len, sizeof(headers) - len,
305                            "Host: %s\r\n", hoststr);
306
307     /* now add in custom headers */
308     av_strlcpy(headers+len, s->headers, sizeof(headers)-len);
309
310     snprintf(s->buffer, sizeof(s->buffer),
311              "%s %s HTTP/1.1\r\n"
312              "%s"
313              "%s"
314              "%s"
315              "\r\n",
316              post ? "POST" : "GET",
317              path,
318              post && s->chunksize >= 0 ? "Transfer-Encoding: chunked\r\n" : "",
319              headers,
320              authstr ? authstr : "");
321
322     av_freep(&authstr);
323     if (url_write(s->hd, s->buffer, strlen(s->buffer)) < 0)
324         return AVERROR(EIO);
325
326     /* init input buffer */
327     s->buf_ptr = s->buffer;
328     s->buf_end = s->buffer;
329     s->line_count = 0;
330     s->off = 0;
331     s->filesize = -1;
332     if (post) {
333         /* Pretend that it did work. We didn't read any header yet, since
334          * we've still to send the POST data, but the code calling this
335          * function will check http_code after we return. */
336         s->http_code = 200;
337         return 0;
338     }
339     s->chunksize = -1;
340
341     /* wait for header */
342     for(;;) {
343         if (http_get_line(s, line, sizeof(line)) < 0)
344             return AVERROR(EIO);
345
346         dprintf(NULL, "header='%s'\n", line);
347
348         err = process_line(h, line, s->line_count, new_location);
349         if (err < 0)
350             return err;
351         if (err == 0)
352             break;
353         s->line_count++;
354     }
355
356     return (off == s->off) ? 0 : -1;
357 }
358
359
360 static int http_read(URLContext *h, uint8_t *buf, int size)
361 {
362     HTTPContext *s = h->priv_data;
363     int len;
364
365     if (!s->init) {
366         int ret = http_open_cnx(h);
367         if (ret != 0)
368             return ret;
369     }
370     if (!s->hd)
371         return AVERROR(EIO);
372
373     /* A size of zero can be used to force
374      * initializaton of the connection. */
375     if (!size)
376         return 0;
377
378     if (s->chunksize >= 0) {
379         if (!s->chunksize) {
380             char line[32];
381
382             for(;;) {
383                 do {
384                     if (http_get_line(s, line, sizeof(line)) < 0)
385                         return AVERROR(EIO);
386                 } while (!*line);    /* skip CR LF from last chunk */
387
388                 s->chunksize = strtoll(line, NULL, 16);
389
390                 dprintf(NULL, "Chunked encoding data size: %"PRId64"'\n", s->chunksize);
391
392                 if (!s->chunksize)
393                     return 0;
394                 break;
395             }
396         }
397         size = FFMIN(size, s->chunksize);
398     }
399     /* read bytes from input buffer first */
400     len = s->buf_end - s->buf_ptr;
401     if (len > 0) {
402         if (len > size)
403             len = size;
404         memcpy(buf, s->buf_ptr, len);
405         s->buf_ptr += len;
406     } else {
407         len = url_read(s->hd, buf, size);
408     }
409     if (len > 0) {
410         s->off += len;
411         if (s->chunksize > 0)
412             s->chunksize -= len;
413     }
414     return len;
415 }
416
417 /* used only when posting data */
418 static int http_write(URLContext *h, const uint8_t *buf, int size)
419 {
420     char temp[11] = "";  /* 32-bit hex + CRLF + nul */
421     int ret;
422     char crlf[] = "\r\n";
423     HTTPContext *s = h->priv_data;
424
425     if (!s->init) {
426         int ret = http_open_cnx(h);
427         if (ret != 0)
428             return ret;
429     }
430     if (!s->hd)
431         return AVERROR(EIO);
432
433     if (s->chunksize == -1) {
434         /* non-chunked data is sent without any special encoding */
435         return url_write(s->hd, buf, size);
436     }
437
438     /* silently ignore zero-size data since chunk encoding that would
439      * signal EOF */
440     if (size > 0) {
441         /* upload data using chunked encoding */
442         snprintf(temp, sizeof(temp), "%x\r\n", size);
443
444         if ((ret = url_write(s->hd, temp, strlen(temp))) < 0 ||
445             (ret = url_write(s->hd, buf, size)) < 0 ||
446             (ret = url_write(s->hd, crlf, sizeof(crlf) - 1)) < 0)
447             return ret;
448     }
449     return size;
450 }
451
452 static int http_close(URLContext *h)
453 {
454     int ret = 0;
455     char footer[] = "0\r\n\r\n";
456     HTTPContext *s = h->priv_data;
457
458     /* signal end of chunked encoding if used */
459     if ((h->flags & URL_WRONLY) && s->chunksize != -1) {
460         ret = url_write(s->hd, footer, sizeof(footer) - 1);
461         ret = ret > 0 ? 0 : ret;
462     }
463
464     if (s->hd)
465         url_close(s->hd);
466     av_free(s);
467     return ret;
468 }
469
470 static int64_t http_seek(URLContext *h, int64_t off, int whence)
471 {
472     HTTPContext *s = h->priv_data;
473     URLContext *old_hd = s->hd;
474     int64_t old_off = s->off;
475     uint8_t old_buf[BUFFER_SIZE];
476     int old_buf_size;
477
478     if (!s->init) {
479         int ret = http_open_cnx(h);
480         if (ret != 0)
481             return ret;
482     }
483     if (!s->hd)
484         return AVERROR(EIO);
485
486     if (whence == AVSEEK_SIZE)
487         return s->filesize;
488     else if ((s->filesize == -1 && whence == SEEK_END) || h->is_streamed)
489         return -1;
490
491     /* we save the old context in case the seek fails */
492     old_buf_size = s->buf_end - s->buf_ptr;
493     memcpy(old_buf, s->buf_ptr, old_buf_size);
494     s->hd = NULL;
495     if (whence == SEEK_CUR)
496         off += s->off;
497     else if (whence == SEEK_END)
498         off += s->filesize;
499     s->off = off;
500
501     /* if it fails, continue on old connection */
502     if (http_open_cnx(h) < 0) {
503         memcpy(s->buffer, old_buf, old_buf_size);
504         s->buf_ptr = s->buffer;
505         s->buf_end = s->buffer + old_buf_size;
506         s->hd = old_hd;
507         s->off = old_off;
508         return -1;
509     }
510     url_close(old_hd);
511     return off;
512 }
513
514 static int
515 http_get_file_handle(URLContext *h)
516 {
517     HTTPContext *s = h->priv_data;
518     return url_get_file_handle(s->hd);
519 }
520
521 URLProtocol http_protocol = {
522     "http",
523     http_open,
524     http_read,
525     http_write,
526     http_seek,
527     http_close,
528     .url_get_file_handle = http_get_file_handle,
529 };