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