add support for HTTP seeking
[libav.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 #include "avformat.h"
22 #include <unistd.h>
23 #include <sys/types.h>
24 #include <sys/socket.h>
25 #include <netinet/in.h>
26 #ifndef __BEOS__
27 # include <arpa/inet.h>
28 #else
29 # include "barpainet.h"
30 #endif
31 #include <netdb.h>
32
33 #include "base64.h"
34
35 /* XXX: POST protocol is not completly implemented because ffmpeg use
36 only a subset of it */
37
38 //#define DEBUG
39
40 /* used for protocol handling */
41 #define BUFFER_SIZE 1024
42 #define URL_SIZE 4096
43 #define MAX_REDIRECTS 8
44
45 typedef struct {
46 URLContext *hd;
47 unsigned char buffer[BUFFER_SIZE], *buf_ptr, *buf_end;
48 int line_count;
49 int http_code;
50 offset_t off, filesize;
51 char location[URL_SIZE];
52 } HTTPContext;
53
54 static int http_connect(URLContext *h, const char *path, const char *hoststr,
55 const char *auth, int *new_location);
56 static int http_write(URLContext *h, uint8_t *buf, int size);
57
58
59 /* return non zero if error */
60 static int http_open_cnx(URLContext *h)
61 {
62 const char *path, *proxy_path;
63 char hostname[1024], hoststr[1024];
64 char auth[1024];
65 char path1[1024];
66 char buf[1024];
67 int port, use_proxy, err, location_changed = 0, redirects = 0;
68 HTTPContext *s = h->priv_data;
69 URLContext *hd = NULL;
70
71 proxy_path = getenv("http_proxy");
72 use_proxy = (proxy_path != NULL) && !getenv("no_proxy") &&
73 strstart(proxy_path, "http://", NULL);
74
75 /* fill the dest addr */
76 redo:
77 /* needed in any case to build the host string */
78 url_split(NULL, 0, auth, sizeof(auth), hostname, sizeof(hostname), &port,
79 path1, sizeof(path1), s->location);
80 if (port > 0) {
81 snprintf(hoststr, sizeof(hoststr), "%s:%d", hostname, port);
82 } else {
83 pstrcpy(hoststr, sizeof(hoststr), hostname);
84 }
85
86 if (use_proxy) {
87 url_split(NULL, 0, auth, sizeof(auth), hostname, sizeof(hostname), &port,
88 NULL, 0, proxy_path);
89 path = s->location;
90 } else {
91 if (path1[0] == '\0')
92 path = "/";
93 else
94 path = path1;
95 }
96 if (port < 0)
97 port = 80;
98
99 snprintf(buf, sizeof(buf), "tcp://%s:%d", hostname, port);
100 err = url_open(&hd, buf, URL_RDWR);
101 if (err < 0)
102 goto fail;
103
104 s->hd = hd;
105 if (http_connect(h, path, hoststr, auth, &location_changed) < 0)
106 goto fail;
107 if (s->http_code == 303 && location_changed == 1) {
108 /* url moved, get next */
109 url_close(hd);
110 if (redirects++ >= MAX_REDIRECTS)
111 return AVERROR_IO;
112 location_changed = 0;
113 goto redo;
114 }
115 return 0;
116 fail:
117 if (hd)
118 url_close(hd);
119 return AVERROR_IO;
120 }
121
122 static int http_open(URLContext *h, const char *uri, int flags)
123 {
124 HTTPContext *s;
125 int ret;
126
127 h->is_streamed = 1;
128
129 s = av_malloc(sizeof(HTTPContext));
130 if (!s) {
131 return -ENOMEM;
132 }
133 h->priv_data = s;
134 s->filesize = -1;
135 s->off = 0;
136 pstrcpy (s->location, URL_SIZE, uri);
137
138 ret = http_open_cnx(h);
139 if (ret != 0)
140 av_free (s);
141 return ret;
142 }
143 static int http_getc(HTTPContext *s)
144 {
145 int len;
146 if (s->buf_ptr >= s->buf_end) {
147 len = url_read(s->hd, s->buffer, BUFFER_SIZE);
148 if (len < 0) {
149 return AVERROR_IO;
150 } else if (len == 0) {
151 return -1;
152 } else {
153 s->buf_ptr = s->buffer;
154 s->buf_end = s->buffer + len;
155 }
156 }
157 return *s->buf_ptr++;
158 }
159
160 static int process_line(URLContext *h, char *line, int line_count,
161 int *new_location)
162 {
163 HTTPContext *s = h->priv_data;
164 char *tag, *p;
165
166 /* end of header */
167 if (line[0] == '\0')
168 return 0;
169
170 p = line;
171 if (line_count == 0) {
172 while (!isspace(*p) && *p != '\0')
173 p++;
174 while (isspace(*p))
175 p++;
176 s->http_code = strtol(p, NULL, 10);
177 #ifdef DEBUG
178 printf("http_code=%d\n", s->http_code);
179 #endif
180 } else {
181 while (*p != '\0' && *p != ':')
182 p++;
183 if (*p != ':')
184 return 1;
185
186 *p = '\0';
187 tag = line;
188 p++;
189 while (isspace(*p))
190 p++;
191 if (!strcmp(tag, "Location")) {
192 strcpy(s->location, p);
193 *new_location = 1;
194 } else if (!strcmp (tag, "Content-Length") && s->filesize == -1) {
195 s->filesize = atoll(p);
196 } else if (!strcmp (tag, "Content-Range")) {
197 /* "bytes $from-$to/$document_size" */
198 const char *slash;
199 if (!strncmp (p, "bytes ", 6)) {
200 p += 6;
201 s->off = atoll(p);
202 if ((slash = strchr(p, '/')) && strlen(slash) > 0)
203 s->filesize = atoll(slash+1);
204 }
205 h->is_streamed = 0; /* we _can_ in fact seek */
206 }
207 }
208 return 1;
209 }
210
211 static int http_connect(URLContext *h, const char *path, const char *hoststr,
212 const char *auth, int *new_location)
213 {
214 HTTPContext *s = h->priv_data;
215 int post, err, ch;
216 char line[1024], *q;
217 char *auth_b64;
218 offset_t off = s->off;
219
220
221 /* send http header */
222 post = h->flags & URL_WRONLY;
223
224 auth_b64 = av_base64_encode((uint8_t *)auth, strlen(auth));
225 snprintf(s->buffer, sizeof(s->buffer),
226 "%s %s HTTP/1.1\r\n"
227 "User-Agent: %s\r\n"
228 "Accept: */*\r\n"
229 "Range: bytes=%llu-\r\n"
230 "Host: %s\r\n"
231 "Authorization: Basic %s\r\n"
232 "\r\n",
233 post ? "POST" : "GET",
234 path,
235 LIBAVFORMAT_IDENT,
236 s->off,
237 hoststr,
238 auth_b64);
239
240 av_freep(&auth_b64);
241 if (http_write(h, s->buffer, strlen(s->buffer)) < 0)
242 return AVERROR_IO;
243
244 /* init input buffer */
245 s->buf_ptr = s->buffer;
246 s->buf_end = s->buffer;
247 s->line_count = 0;
248 s->off = 0;
249 if (post) {
250 sleep(1);
251 return 0;
252 }
253
254 /* wait for header */
255 q = line;
256 for(;;) {
257 ch = http_getc(s);
258 if (ch < 0)
259 return AVERROR_IO;
260 if (ch == '\n') {
261 /* process line */
262 if (q > line && q[-1] == '\r')
263 q--;
264 *q = '\0';
265 #ifdef DEBUG
266 printf("header='%s'\n", line);
267 #endif
268 err = process_line(h, line, s->line_count, new_location);
269 if (err < 0)
270 return err;
271 if (err == 0)
272 break;
273 s->line_count++;
274 q = line;
275 } else {
276 if ((q - line) < sizeof(line) - 1)
277 *q++ = ch;
278 }
279 }
280
281 return (off == s->off) ? 0 : -1;
282 }
283
284
285 static int http_read(URLContext *h, uint8_t *buf, int size)
286 {
287 HTTPContext *s = h->priv_data;
288 int len;
289
290 /* read bytes from input buffer first */
291 len = s->buf_end - s->buf_ptr;
292 if (len > 0) {
293 if (len > size)
294 len = size;
295 memcpy(buf, s->buf_ptr, len);
296 s->buf_ptr += len;
297 } else {
298 len = url_read(s->hd, buf, size);
299 }
300 if (len > 0)
301 s->off += len;
302 return len;
303 }
304
305 /* used only when posting data */
306 static int http_write(URLContext *h, uint8_t *buf, int size)
307 {
308 HTTPContext *s = h->priv_data;
309 return url_write(s->hd, buf, size);
310 }
311
312 static int http_close(URLContext *h)
313 {
314 HTTPContext *s = h->priv_data;
315 url_close(s->hd);
316 av_free(s);
317 return 0;
318 }
319
320 static offset_t http_seek(URLContext *h, offset_t off, int whence)
321 {
322 HTTPContext *s = h->priv_data;
323 URLContext *old_hd = s->hd;
324 offset_t old_off = s->off;
325
326 if (whence == AVSEEK_SIZE)
327 return s->filesize;
328 else if ((s->filesize == -1 && whence == SEEK_END) || h->is_streamed)
329 return -1;
330
331 /* we save the old context in case the seek fails */
332 s->hd = NULL;
333 if (whence == SEEK_CUR)
334 off += s->off;
335 else if (whence == SEEK_END)
336 off += s->filesize;
337 s->off = off;
338
339 /* if it fails, continue on old connection */
340 if (http_open_cnx(h) < 0) {
341 s->hd = old_hd;
342 s->off = old_off;
343 return -1;
344 }
345 url_close(old_hd);
346 return off;
347 }
348
349 URLProtocol http_protocol = {
350 "http",
351 http_open,
352 http_read,
353 http_write,
354 http_seek,
355 http_close,
356 };