Libav 0.7.1
|
00001 /* 00002 * HTTP protocol for ffmpeg client 00003 * Copyright (c) 2000, 2001 Fabrice Bellard 00004 * 00005 * This file is part of Libav. 00006 * 00007 * Libav is free software; you can redistribute it and/or 00008 * modify it under the terms of the GNU Lesser General Public 00009 * License as published by the Free Software Foundation; either 00010 * version 2.1 of the License, or (at your option) any later version. 00011 * 00012 * Libav is distributed in the hope that it will be useful, 00013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 * Lesser General Public License for more details. 00016 * 00017 * You should have received a copy of the GNU Lesser General Public 00018 * License along with Libav; if not, write to the Free Software 00019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00020 */ 00021 00022 #include "libavutil/avstring.h" 00023 #include "avformat.h" 00024 #include <unistd.h> 00025 #include <strings.h> 00026 #include "internal.h" 00027 #include "network.h" 00028 #include "http.h" 00029 #include "os_support.h" 00030 #include "httpauth.h" 00031 #include "url.h" 00032 #include "libavutil/opt.h" 00033 00034 /* XXX: POST protocol is not completely implemented because ffmpeg uses 00035 only a subset of it. */ 00036 00037 /* used for protocol handling */ 00038 #define BUFFER_SIZE 1024 00039 #define MAX_REDIRECTS 8 00040 00041 typedef struct { 00042 const AVClass *class; 00043 URLContext *hd; 00044 unsigned char buffer[BUFFER_SIZE], *buf_ptr, *buf_end; 00045 int line_count; 00046 int http_code; 00047 int64_t chunksize; 00048 int64_t off, filesize; 00049 char location[MAX_URL_SIZE]; 00050 HTTPAuthState auth_state; 00051 unsigned char headers[BUFFER_SIZE]; 00052 int willclose; 00053 } HTTPContext; 00054 00055 #define OFFSET(x) offsetof(HTTPContext, x) 00056 static const AVOption options[] = { 00057 {"chunksize", "use chunked transfer-encoding for posts, -1 disables it, 0 enables it", OFFSET(chunksize), FF_OPT_TYPE_INT64, {.dbl = 0}, -1, 0 }, /* Default to 0, for chunked POSTs */ 00058 {NULL} 00059 }; 00060 static const AVClass httpcontext_class = { 00061 .class_name = "HTTP", 00062 .item_name = av_default_item_name, 00063 .option = options, 00064 .version = LIBAVUTIL_VERSION_INT, 00065 }; 00066 00067 static int http_connect(URLContext *h, const char *path, const char *hoststr, 00068 const char *auth, int *new_location); 00069 00070 void ff_http_set_headers(URLContext *h, const char *headers) 00071 { 00072 HTTPContext *s = h->priv_data; 00073 int len = strlen(headers); 00074 00075 if (len && strcmp("\r\n", headers + len - 2)) 00076 av_log(h, AV_LOG_ERROR, "No trailing CRLF found in HTTP header.\n"); 00077 00078 av_strlcpy(s->headers, headers, sizeof(s->headers)); 00079 } 00080 00081 void ff_http_set_chunked_transfer_encoding(URLContext *h, int is_chunked) 00082 { 00083 ((HTTPContext*)h->priv_data)->chunksize = is_chunked ? 0 : -1; 00084 } 00085 00086 void ff_http_init_auth_state(URLContext *dest, const URLContext *src) 00087 { 00088 memcpy(&((HTTPContext*)dest->priv_data)->auth_state, 00089 &((HTTPContext*)src->priv_data)->auth_state, sizeof(HTTPAuthState)); 00090 } 00091 00092 /* return non zero if error */ 00093 static int http_open_cnx(URLContext *h) 00094 { 00095 const char *path, *proxy_path; 00096 char hostname[1024], hoststr[1024]; 00097 char auth[1024]; 00098 char path1[1024]; 00099 char buf[1024]; 00100 int port, use_proxy, err, location_changed = 0, redirects = 0; 00101 HTTPAuthType cur_auth_type; 00102 HTTPContext *s = h->priv_data; 00103 URLContext *hd = NULL; 00104 00105 proxy_path = getenv("http_proxy"); 00106 use_proxy = (proxy_path != NULL) && !getenv("no_proxy") && 00107 av_strstart(proxy_path, "http://", NULL); 00108 00109 /* fill the dest addr */ 00110 redo: 00111 /* needed in any case to build the host string */ 00112 av_url_split(NULL, 0, auth, sizeof(auth), hostname, sizeof(hostname), &port, 00113 path1, sizeof(path1), s->location); 00114 ff_url_join(hoststr, sizeof(hoststr), NULL, NULL, hostname, port, NULL); 00115 00116 if (use_proxy) { 00117 av_url_split(NULL, 0, auth, sizeof(auth), hostname, sizeof(hostname), &port, 00118 NULL, 0, proxy_path); 00119 path = s->location; 00120 } else { 00121 if (path1[0] == '\0') 00122 path = "/"; 00123 else 00124 path = path1; 00125 } 00126 if (port < 0) 00127 port = 80; 00128 00129 ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL); 00130 err = ffurl_open(&hd, buf, AVIO_FLAG_READ_WRITE); 00131 if (err < 0) 00132 goto fail; 00133 00134 s->hd = hd; 00135 cur_auth_type = s->auth_state.auth_type; 00136 if (http_connect(h, path, hoststr, auth, &location_changed) < 0) 00137 goto fail; 00138 if (s->http_code == 401) { 00139 if (cur_auth_type == HTTP_AUTH_NONE && s->auth_state.auth_type != HTTP_AUTH_NONE) { 00140 ffurl_close(hd); 00141 goto redo; 00142 } else 00143 goto fail; 00144 } 00145 if ((s->http_code == 301 || s->http_code == 302 || s->http_code == 303 || s->http_code == 307) 00146 && location_changed == 1) { 00147 /* url moved, get next */ 00148 ffurl_close(hd); 00149 if (redirects++ >= MAX_REDIRECTS) 00150 return AVERROR(EIO); 00151 location_changed = 0; 00152 goto redo; 00153 } 00154 return 0; 00155 fail: 00156 if (hd) 00157 ffurl_close(hd); 00158 s->hd = NULL; 00159 return AVERROR(EIO); 00160 } 00161 00162 static int http_open(URLContext *h, const char *uri, int flags) 00163 { 00164 HTTPContext *s = h->priv_data; 00165 00166 h->is_streamed = 1; 00167 00168 s->filesize = -1; 00169 av_strlcpy(s->location, uri, sizeof(s->location)); 00170 00171 return http_open_cnx(h); 00172 } 00173 static int http_getc(HTTPContext *s) 00174 { 00175 int len; 00176 if (s->buf_ptr >= s->buf_end) { 00177 len = ffurl_read(s->hd, s->buffer, BUFFER_SIZE); 00178 if (len < 0) { 00179 return AVERROR(EIO); 00180 } else if (len == 0) { 00181 return -1; 00182 } else { 00183 s->buf_ptr = s->buffer; 00184 s->buf_end = s->buffer + len; 00185 } 00186 } 00187 return *s->buf_ptr++; 00188 } 00189 00190 static int http_get_line(HTTPContext *s, char *line, int line_size) 00191 { 00192 int ch; 00193 char *q; 00194 00195 q = line; 00196 for(;;) { 00197 ch = http_getc(s); 00198 if (ch < 0) 00199 return AVERROR(EIO); 00200 if (ch == '\n') { 00201 /* process line */ 00202 if (q > line && q[-1] == '\r') 00203 q--; 00204 *q = '\0'; 00205 00206 return 0; 00207 } else { 00208 if ((q - line) < line_size - 1) 00209 *q++ = ch; 00210 } 00211 } 00212 } 00213 00214 static int process_line(URLContext *h, char *line, int line_count, 00215 int *new_location) 00216 { 00217 HTTPContext *s = h->priv_data; 00218 char *tag, *p, *end; 00219 00220 /* end of header */ 00221 if (line[0] == '\0') 00222 return 0; 00223 00224 p = line; 00225 if (line_count == 0) { 00226 while (!isspace(*p) && *p != '\0') 00227 p++; 00228 while (isspace(*p)) 00229 p++; 00230 s->http_code = strtol(p, &end, 10); 00231 00232 av_dlog(NULL, "http_code=%d\n", s->http_code); 00233 00234 /* error codes are 4xx and 5xx, but regard 401 as a success, so we 00235 * don't abort until all headers have been parsed. */ 00236 if (s->http_code >= 400 && s->http_code < 600 && s->http_code != 401) { 00237 end += strspn(end, SPACE_CHARS); 00238 av_log(h, AV_LOG_WARNING, "HTTP error %d %s\n", 00239 s->http_code, end); 00240 return -1; 00241 } 00242 } else { 00243 while (*p != '\0' && *p != ':') 00244 p++; 00245 if (*p != ':') 00246 return 1; 00247 00248 *p = '\0'; 00249 tag = line; 00250 p++; 00251 while (isspace(*p)) 00252 p++; 00253 if (!strcasecmp(tag, "Location")) { 00254 strcpy(s->location, p); 00255 *new_location = 1; 00256 } else if (!strcasecmp (tag, "Content-Length") && s->filesize == -1) { 00257 s->filesize = atoll(p); 00258 } else if (!strcasecmp (tag, "Content-Range")) { 00259 /* "bytes $from-$to/$document_size" */ 00260 const char *slash; 00261 if (!strncmp (p, "bytes ", 6)) { 00262 p += 6; 00263 s->off = atoll(p); 00264 if ((slash = strchr(p, '/')) && strlen(slash) > 0) 00265 s->filesize = atoll(slash+1); 00266 } 00267 h->is_streamed = 0; /* we _can_ in fact seek */ 00268 } else if (!strcasecmp (tag, "Transfer-Encoding") && !strncasecmp(p, "chunked", 7)) { 00269 s->filesize = -1; 00270 s->chunksize = 0; 00271 } else if (!strcasecmp (tag, "WWW-Authenticate")) { 00272 ff_http_auth_handle_header(&s->auth_state, tag, p); 00273 } else if (!strcasecmp (tag, "Authentication-Info")) { 00274 ff_http_auth_handle_header(&s->auth_state, tag, p); 00275 } else if (!strcasecmp (tag, "Connection")) { 00276 if (!strcmp(p, "close")) 00277 s->willclose = 1; 00278 } 00279 } 00280 return 1; 00281 } 00282 00283 static inline int has_header(const char *str, const char *header) 00284 { 00285 /* header + 2 to skip over CRLF prefix. (make sure you have one!) */ 00286 return av_stristart(str, header + 2, NULL) || av_stristr(str, header); 00287 } 00288 00289 static int http_connect(URLContext *h, const char *path, const char *hoststr, 00290 const char *auth, int *new_location) 00291 { 00292 HTTPContext *s = h->priv_data; 00293 int post, err; 00294 char line[1024]; 00295 char headers[1024] = ""; 00296 char *authstr = NULL; 00297 int64_t off = s->off; 00298 int len = 0; 00299 00300 00301 /* send http header */ 00302 post = h->flags & AVIO_FLAG_WRITE; 00303 authstr = ff_http_auth_create_response(&s->auth_state, auth, path, 00304 post ? "POST" : "GET"); 00305 00306 /* set default headers if needed */ 00307 if (!has_header(s->headers, "\r\nUser-Agent: ")) 00308 len += av_strlcatf(headers + len, sizeof(headers) - len, 00309 "User-Agent: %s\r\n", LIBAVFORMAT_IDENT); 00310 if (!has_header(s->headers, "\r\nAccept: ")) 00311 len += av_strlcpy(headers + len, "Accept: */*\r\n", 00312 sizeof(headers) - len); 00313 if (!has_header(s->headers, "\r\nRange: ")) 00314 len += av_strlcatf(headers + len, sizeof(headers) - len, 00315 "Range: bytes=%"PRId64"-\r\n", s->off); 00316 if (!has_header(s->headers, "\r\nConnection: ")) 00317 len += av_strlcpy(headers + len, "Connection: close\r\n", 00318 sizeof(headers)-len); 00319 if (!has_header(s->headers, "\r\nHost: ")) 00320 len += av_strlcatf(headers + len, sizeof(headers) - len, 00321 "Host: %s\r\n", hoststr); 00322 00323 /* now add in custom headers */ 00324 av_strlcpy(headers+len, s->headers, sizeof(headers)-len); 00325 00326 snprintf(s->buffer, sizeof(s->buffer), 00327 "%s %s HTTP/1.1\r\n" 00328 "%s" 00329 "%s" 00330 "%s" 00331 "\r\n", 00332 post ? "POST" : "GET", 00333 path, 00334 post && s->chunksize >= 0 ? "Transfer-Encoding: chunked\r\n" : "", 00335 headers, 00336 authstr ? authstr : ""); 00337 00338 av_freep(&authstr); 00339 if (ffurl_write(s->hd, s->buffer, strlen(s->buffer)) < 0) 00340 return AVERROR(EIO); 00341 00342 /* init input buffer */ 00343 s->buf_ptr = s->buffer; 00344 s->buf_end = s->buffer; 00345 s->line_count = 0; 00346 s->off = 0; 00347 s->filesize = -1; 00348 s->willclose = 0; 00349 if (post) { 00350 /* Pretend that it did work. We didn't read any header yet, since 00351 * we've still to send the POST data, but the code calling this 00352 * function will check http_code after we return. */ 00353 s->http_code = 200; 00354 return 0; 00355 } 00356 s->chunksize = -1; 00357 00358 /* wait for header */ 00359 for(;;) { 00360 if (http_get_line(s, line, sizeof(line)) < 0) 00361 return AVERROR(EIO); 00362 00363 av_dlog(NULL, "header='%s'\n", line); 00364 00365 err = process_line(h, line, s->line_count, new_location); 00366 if (err < 0) 00367 return err; 00368 if (err == 0) 00369 break; 00370 s->line_count++; 00371 } 00372 00373 return (off == s->off) ? 0 : -1; 00374 } 00375 00376 00377 static int http_read(URLContext *h, uint8_t *buf, int size) 00378 { 00379 HTTPContext *s = h->priv_data; 00380 int len; 00381 00382 if (s->chunksize >= 0) { 00383 if (!s->chunksize) { 00384 char line[32]; 00385 00386 for(;;) { 00387 do { 00388 if (http_get_line(s, line, sizeof(line)) < 0) 00389 return AVERROR(EIO); 00390 } while (!*line); /* skip CR LF from last chunk */ 00391 00392 s->chunksize = strtoll(line, NULL, 16); 00393 00394 av_dlog(NULL, "Chunked encoding data size: %"PRId64"'\n", s->chunksize); 00395 00396 if (!s->chunksize) 00397 return 0; 00398 break; 00399 } 00400 } 00401 size = FFMIN(size, s->chunksize); 00402 } 00403 /* read bytes from input buffer first */ 00404 len = s->buf_end - s->buf_ptr; 00405 if (len > 0) { 00406 if (len > size) 00407 len = size; 00408 memcpy(buf, s->buf_ptr, len); 00409 s->buf_ptr += len; 00410 } else { 00411 if (!s->willclose && s->filesize >= 0 && s->off >= s->filesize) 00412 return AVERROR_EOF; 00413 len = ffurl_read(s->hd, buf, size); 00414 } 00415 if (len > 0) { 00416 s->off += len; 00417 if (s->chunksize > 0) 00418 s->chunksize -= len; 00419 } 00420 return len; 00421 } 00422 00423 /* used only when posting data */ 00424 static int http_write(URLContext *h, const uint8_t *buf, int size) 00425 { 00426 char temp[11] = ""; /* 32-bit hex + CRLF + nul */ 00427 int ret; 00428 char crlf[] = "\r\n"; 00429 HTTPContext *s = h->priv_data; 00430 00431 if (s->chunksize == -1) { 00432 /* non-chunked data is sent without any special encoding */ 00433 return ffurl_write(s->hd, buf, size); 00434 } 00435 00436 /* silently ignore zero-size data since chunk encoding that would 00437 * signal EOF */ 00438 if (size > 0) { 00439 /* upload data using chunked encoding */ 00440 snprintf(temp, sizeof(temp), "%x\r\n", size); 00441 00442 if ((ret = ffurl_write(s->hd, temp, strlen(temp))) < 0 || 00443 (ret = ffurl_write(s->hd, buf, size)) < 0 || 00444 (ret = ffurl_write(s->hd, crlf, sizeof(crlf) - 1)) < 0) 00445 return ret; 00446 } 00447 return size; 00448 } 00449 00450 static int http_close(URLContext *h) 00451 { 00452 int ret = 0; 00453 char footer[] = "0\r\n\r\n"; 00454 HTTPContext *s = h->priv_data; 00455 00456 /* signal end of chunked encoding if used */ 00457 if ((h->flags & AVIO_FLAG_WRITE) && s->chunksize != -1) { 00458 ret = ffurl_write(s->hd, footer, sizeof(footer) - 1); 00459 ret = ret > 0 ? 0 : ret; 00460 } 00461 00462 if (s->hd) 00463 ffurl_close(s->hd); 00464 return ret; 00465 } 00466 00467 static int64_t http_seek(URLContext *h, int64_t off, int whence) 00468 { 00469 HTTPContext *s = h->priv_data; 00470 URLContext *old_hd = s->hd; 00471 int64_t old_off = s->off; 00472 uint8_t old_buf[BUFFER_SIZE]; 00473 int old_buf_size; 00474 00475 if (whence == AVSEEK_SIZE) 00476 return s->filesize; 00477 else if ((s->filesize == -1 && whence == SEEK_END) || h->is_streamed) 00478 return -1; 00479 00480 /* we save the old context in case the seek fails */ 00481 old_buf_size = s->buf_end - s->buf_ptr; 00482 memcpy(old_buf, s->buf_ptr, old_buf_size); 00483 s->hd = NULL; 00484 if (whence == SEEK_CUR) 00485 off += s->off; 00486 else if (whence == SEEK_END) 00487 off += s->filesize; 00488 s->off = off; 00489 00490 /* if it fails, continue on old connection */ 00491 if (http_open_cnx(h) < 0) { 00492 memcpy(s->buffer, old_buf, old_buf_size); 00493 s->buf_ptr = s->buffer; 00494 s->buf_end = s->buffer + old_buf_size; 00495 s->hd = old_hd; 00496 s->off = old_off; 00497 return -1; 00498 } 00499 ffurl_close(old_hd); 00500 return off; 00501 } 00502 00503 static int 00504 http_get_file_handle(URLContext *h) 00505 { 00506 HTTPContext *s = h->priv_data; 00507 return ffurl_get_file_handle(s->hd); 00508 } 00509 00510 URLProtocol ff_http_protocol = { 00511 .name = "http", 00512 .url_open = http_open, 00513 .url_read = http_read, 00514 .url_write = http_write, 00515 .url_seek = http_seek, 00516 .url_close = http_close, 00517 .url_get_file_handle = http_get_file_handle, 00518 .priv_data_size = sizeof(HTTPContext), 00519 .priv_data_class = &httpcontext_class, 00520 };