move ff_url_split() and ff_url_join() declarations to internal.h
[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/base64.h"
23 #include "libavutil/avstring.h"
24 #include "avformat.h"
25 #include <unistd.h>
26 #include <strings.h>
27 #include "internal.h"
28 #include "network.h"
29 #include "os_support.h"
30
31 /* XXX: POST protocol is not completely implemented because ffmpeg uses
32    only a subset of it. */
33
34 /* used for protocol handling */
35 #define BUFFER_SIZE 1024
36 #define URL_SIZE    4096
37 #define MAX_REDIRECTS 8
38
39 typedef struct {
40     URLContext *hd;
41     unsigned char buffer[BUFFER_SIZE], *buf_ptr, *buf_end;
42     int line_count;
43     int http_code;
44     int64_t chunksize;      /**< Used if "Transfer-Encoding: chunked" otherwise -1. */
45     int64_t off, filesize;
46     char location[URL_SIZE];
47 } HTTPContext;
48
49 static int http_connect(URLContext *h, const char *path, const char *hoststr,
50                         const char *auth, int *new_location);
51 static int http_write(URLContext *h, uint8_t *buf, int size);
52
53
54 /* return non zero if error */
55 static int http_open_cnx(URLContext *h)
56 {
57     const char *path, *proxy_path;
58     char hostname[1024], hoststr[1024];
59     char auth[1024];
60     char path1[1024];
61     char buf[1024];
62     int port, use_proxy, err, location_changed = 0, redirects = 0;
63     HTTPContext *s = h->priv_data;
64     URLContext *hd = NULL;
65
66     proxy_path = getenv("http_proxy");
67     use_proxy = (proxy_path != NULL) && !getenv("no_proxy") &&
68         av_strstart(proxy_path, "http://", NULL);
69
70     /* fill the dest addr */
71  redo:
72     /* needed in any case to build the host string */
73     ff_url_split(NULL, 0, auth, sizeof(auth), hostname, sizeof(hostname), &port,
74                  path1, sizeof(path1), s->location);
75     ff_url_join(hoststr, sizeof(hoststr), NULL, NULL, hostname, port, NULL);
76
77     if (use_proxy) {
78         ff_url_split(NULL, 0, auth, sizeof(auth), hostname, sizeof(hostname), &port,
79                      NULL, 0, proxy_path);
80         path = s->location;
81     } else {
82         if (path1[0] == '\0')
83             path = "/";
84         else
85             path = path1;
86     }
87     if (port < 0)
88         port = 80;
89
90     ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL);
91     err = url_open(&hd, buf, URL_RDWR);
92     if (err < 0)
93         goto fail;
94
95     s->hd = hd;
96     if (http_connect(h, path, hoststr, auth, &location_changed) < 0)
97         goto fail;
98     if ((s->http_code == 302 || s->http_code == 303) && location_changed == 1) {
99         /* url moved, get next */
100         url_close(hd);
101         if (redirects++ >= MAX_REDIRECTS)
102             return AVERROR(EIO);
103         location_changed = 0;
104         goto redo;
105     }
106     return 0;
107  fail:
108     if (hd)
109         url_close(hd);
110     return AVERROR(EIO);
111 }
112
113 static int http_open(URLContext *h, const char *uri, int flags)
114 {
115     HTTPContext *s;
116     int ret;
117
118     h->is_streamed = 1;
119
120     s = av_malloc(sizeof(HTTPContext));
121     if (!s) {
122         return AVERROR(ENOMEM);
123     }
124     h->priv_data = s;
125     s->filesize = -1;
126     s->chunksize = -1;
127     s->off = 0;
128     av_strlcpy(s->location, uri, URL_SIZE);
129
130     ret = http_open_cnx(h);
131     if (ret != 0)
132         av_free (s);
133     return ret;
134 }
135 static int http_getc(HTTPContext *s)
136 {
137     int len;
138     if (s->buf_ptr >= s->buf_end) {
139         len = url_read(s->hd, s->buffer, BUFFER_SIZE);
140         if (len < 0) {
141             return AVERROR(EIO);
142         } else if (len == 0) {
143             return -1;
144         } else {
145             s->buf_ptr = s->buffer;
146             s->buf_end = s->buffer + len;
147         }
148     }
149     return *s->buf_ptr++;
150 }
151
152 static int http_get_line(HTTPContext *s, char *line, int line_size)
153 {
154     int ch;
155     char *q;
156
157     q = line;
158     for(;;) {
159         ch = http_getc(s);
160         if (ch < 0)
161             return AVERROR(EIO);
162         if (ch == '\n') {
163             /* process line */
164             if (q > line && q[-1] == '\r')
165                 q--;
166             *q = '\0';
167
168             return 0;
169         } else {
170             if ((q - line) < line_size - 1)
171                 *q++ = ch;
172         }
173     }
174 }
175
176 static int process_line(URLContext *h, char *line, int line_count,
177                         int *new_location)
178 {
179     HTTPContext *s = h->priv_data;
180     char *tag, *p;
181
182     /* end of header */
183     if (line[0] == '\0')
184         return 0;
185
186     p = line;
187     if (line_count == 0) {
188         while (!isspace(*p) && *p != '\0')
189             p++;
190         while (isspace(*p))
191             p++;
192         s->http_code = strtol(p, NULL, 10);
193
194         dprintf(NULL, "http_code=%d\n", s->http_code);
195
196         /* error codes are 4xx and 5xx */
197         if (s->http_code >= 400 && s->http_code < 600)
198             return -1;
199     } else {
200         while (*p != '\0' && *p != ':')
201             p++;
202         if (*p != ':')
203             return 1;
204
205         *p = '\0';
206         tag = line;
207         p++;
208         while (isspace(*p))
209             p++;
210         if (!strcmp(tag, "Location")) {
211             strcpy(s->location, p);
212             *new_location = 1;
213         } else if (!strcmp (tag, "Content-Length") && s->filesize == -1) {
214             s->filesize = atoll(p);
215         } else if (!strcmp (tag, "Content-Range")) {
216             /* "bytes $from-$to/$document_size" */
217             const char *slash;
218             if (!strncmp (p, "bytes ", 6)) {
219                 p += 6;
220                 s->off = atoll(p);
221                 if ((slash = strchr(p, '/')) && strlen(slash) > 0)
222                     s->filesize = atoll(slash+1);
223             }
224             h->is_streamed = 0; /* we _can_ in fact seek */
225         } else if (!strcmp (tag, "Transfer-Encoding") && !strncasecmp(p, "chunked", 7)) {
226             s->filesize = -1;
227             s->chunksize = 0;
228         }
229     }
230     return 1;
231 }
232
233 static int http_connect(URLContext *h, const char *path, const char *hoststr,
234                         const char *auth, int *new_location)
235 {
236     HTTPContext *s = h->priv_data;
237     int post, err;
238     char line[1024];
239     char *auth_b64;
240     int auth_b64_len = (strlen(auth) + 2) / 3 * 4 + 1;
241     int64_t off = s->off;
242
243
244     /* send http header */
245     post = h->flags & URL_WRONLY;
246     auth_b64 = av_malloc(auth_b64_len);
247     av_base64_encode(auth_b64, auth_b64_len, auth, strlen(auth));
248     snprintf(s->buffer, sizeof(s->buffer),
249              "%s %s HTTP/1.1\r\n"
250              "User-Agent: %s\r\n"
251              "Accept: */*\r\n"
252              "Range: bytes=%"PRId64"-\r\n"
253              "Host: %s\r\n"
254              "Authorization: Basic %s\r\n"
255              "Connection: close\r\n"
256              "%s"
257              "\r\n",
258              post ? "POST" : "GET",
259              path,
260              LIBAVFORMAT_IDENT,
261              s->off,
262              hoststr,
263              auth_b64,
264              post ? "Transfer-Encoding: chunked\r\n" : "");
265
266     av_freep(&auth_b64);
267     if (http_write(h, s->buffer, strlen(s->buffer)) < 0)
268         return AVERROR(EIO);
269
270     /* init input buffer */
271     s->buf_ptr = s->buffer;
272     s->buf_end = s->buffer;
273     s->line_count = 0;
274     s->off = 0;
275     s->filesize = -1;
276     if (post) {
277         /* always use chunked encoding for upload data */
278         s->chunksize = 0;
279         return 0;
280     }
281
282     /* wait for header */
283     for(;;) {
284         if (http_get_line(s, line, sizeof(line)) < 0)
285             return AVERROR(EIO);
286
287         dprintf(NULL, "header='%s'\n", line);
288
289         err = process_line(h, line, s->line_count, new_location);
290         if (err < 0)
291             return err;
292         if (err == 0)
293             break;
294         s->line_count++;
295     }
296
297     return (off == s->off) ? 0 : -1;
298 }
299
300
301 static int http_read(URLContext *h, uint8_t *buf, int size)
302 {
303     HTTPContext *s = h->priv_data;
304     int len;
305
306     if (s->chunksize >= 0) {
307         if (!s->chunksize) {
308             char line[32];
309
310             for(;;) {
311                 do {
312                     if (http_get_line(s, line, sizeof(line)) < 0)
313                         return AVERROR(EIO);
314                 } while (!*line);    /* skip CR LF from last chunk */
315
316                 s->chunksize = strtoll(line, NULL, 16);
317
318                 dprintf(NULL, "Chunked encoding data size: %"PRId64"'\n", s->chunksize);
319
320                 if (!s->chunksize)
321                     return 0;
322                 break;
323             }
324         }
325         size = FFMIN(size, s->chunksize);
326     }
327     /* read bytes from input buffer first */
328     len = s->buf_end - s->buf_ptr;
329     if (len > 0) {
330         if (len > size)
331             len = size;
332         memcpy(buf, s->buf_ptr, len);
333         s->buf_ptr += len;
334     } else {
335         len = url_read(s->hd, buf, size);
336     }
337     if (len > 0) {
338         s->off += len;
339         if (s->chunksize > 0)
340             s->chunksize -= len;
341     }
342     return len;
343 }
344
345 /* used only when posting data */
346 static int http_write(URLContext *h, uint8_t *buf, int size)
347 {
348     char temp[11];  /* 32-bit hex + CRLF + nul */
349     int ret;
350     char crlf[] = "\r\n";
351     HTTPContext *s = h->priv_data;
352
353     if (s->chunksize == -1) {
354         /* headers are sent without any special encoding */
355         return url_write(s->hd, buf, size);
356     }
357
358     /* silently ignore zero-size data since chunk encoding that would
359      * signal EOF */
360     if (size > 0) {
361         /* upload data using chunked encoding */
362         snprintf(temp, sizeof(temp), "%x\r\n", size);
363
364         if ((ret = url_write(s->hd, temp, strlen(temp))) < 0 ||
365             (ret = url_write(s->hd, buf, size)) < 0 ||
366             (ret = url_write(s->hd, crlf, sizeof(crlf) - 1)) < 0)
367             return ret;
368     }
369     return size;
370 }
371
372 static int http_close(URLContext *h)
373 {
374     int ret = 0;
375     char footer[] = "0\r\n\r\n";
376     HTTPContext *s = h->priv_data;
377
378     /* signal end of chunked encoding if used */
379     if ((h->flags & URL_WRONLY) && s->chunksize != -1) {
380         ret = url_write(s->hd, footer, sizeof(footer) - 1);
381         ret = ret > 0 ? 0 : ret;
382     }
383
384     url_close(s->hd);
385     av_free(s);
386     return ret;
387 }
388
389 static int64_t http_seek(URLContext *h, int64_t off, int whence)
390 {
391     HTTPContext *s = h->priv_data;
392     URLContext *old_hd = s->hd;
393     int64_t old_off = s->off;
394     uint8_t old_buf[BUFFER_SIZE];
395     int old_buf_size;
396
397     if (whence == AVSEEK_SIZE)
398         return s->filesize;
399     else if ((s->filesize == -1 && whence == SEEK_END) || h->is_streamed)
400         return -1;
401
402     /* we save the old context in case the seek fails */
403     old_buf_size = s->buf_end - s->buf_ptr;
404     memcpy(old_buf, s->buf_ptr, old_buf_size);
405     s->hd = NULL;
406     if (whence == SEEK_CUR)
407         off += s->off;
408     else if (whence == SEEK_END)
409         off += s->filesize;
410     s->off = off;
411
412     /* if it fails, continue on old connection */
413     if (http_open_cnx(h) < 0) {
414         memcpy(s->buffer, old_buf, old_buf_size);
415         s->buf_ptr = s->buffer;
416         s->buf_end = s->buffer + old_buf_size;
417         s->hd = old_hd;
418         s->off = old_off;
419         return -1;
420     }
421     url_close(old_hd);
422     return off;
423 }
424
425 static int
426 http_get_file_handle(URLContext *h)
427 {
428     HTTPContext *s = h->priv_data;
429     return url_get_file_handle(s->hd);
430 }
431
432 URLProtocol http_protocol = {
433     "http",
434     http_open,
435     http_read,
436     http_write,
437     http_seek,
438     http_close,
439     .url_get_file_handle = http_get_file_handle,
440 };