1/* 2 * HTTP protocol for avconv client 3 * Copyright (c) 2000, 2001 Fabrice Bellard 4 * 5 * This file is part of Libav. 6 * 7 * Libav 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 * Libav 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 Libav; 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 "internal.h" 26#include "network.h" 27#include "http.h" 28#include "os_support.h" 29#include "httpauth.h" 30#include "url.h" 31#include "libavutil/opt.h" 32 33/* XXX: POST protocol is not completely implemented because avconv uses 34 only a subset of it. */ 35 36/* used for protocol handling */ 37#define BUFFER_SIZE 1024 38#define MAX_REDIRECTS 8 39 40typedef struct { 41 const AVClass *class; 42 URLContext *hd; 43 unsigned char buffer[BUFFER_SIZE], *buf_ptr, *buf_end; 44 int line_count; 45 int http_code; 46 int64_t chunksize; /**< Used if "Transfer-Encoding: chunked" otherwise -1. */ 47 int64_t off, filesize; 48 char location[MAX_URL_SIZE]; 49 HTTPAuthState auth_state; 50 HTTPAuthState proxy_auth_state; 51 char *headers; 52 int willclose; /**< Set if the server correctly handles Connection: close and will close the connection after feeding us the content. */ 53 int chunked_post; 54} HTTPContext; 55 56#define OFFSET(x) offsetof(HTTPContext, x) 57#define D AV_OPT_FLAG_DECODING_PARAM 58#define E AV_OPT_FLAG_ENCODING_PARAM 59static const AVOption options[] = { 60{"chunked_post", "use chunked transfer-encoding for posts", OFFSET(chunked_post), AV_OPT_TYPE_INT, {.dbl = 1}, 0, 1, E }, 61{"headers", "custom HTTP headers, can override built in default headers", OFFSET(headers), AV_OPT_TYPE_STRING, { 0 }, 0, 0, D|E }, 62{NULL} 63}; 64#define HTTP_CLASS(flavor)\ 65static const AVClass flavor ## _context_class = {\ 66 .class_name = #flavor,\ 67 .item_name = av_default_item_name,\ 68 .option = options,\ 69 .version = LIBAVUTIL_VERSION_INT,\ 70} 71 72HTTP_CLASS(http); 73HTTP_CLASS(https); 74 75static int http_connect(URLContext *h, const char *path, const char *local_path, 76 const char *hoststr, const char *auth, 77 const char *proxyauth, int *new_location); 78 79void ff_http_init_auth_state(URLContext *dest, const URLContext *src) 80{ 81 memcpy(&((HTTPContext*)dest->priv_data)->auth_state, 82 &((HTTPContext*)src->priv_data)->auth_state, sizeof(HTTPAuthState)); 83 memcpy(&((HTTPContext*)dest->priv_data)->proxy_auth_state, 84 &((HTTPContext*)src->priv_data)->proxy_auth_state, 85 sizeof(HTTPAuthState)); 86} 87 88/* return non zero if error */ 89static int http_open_cnx(URLContext *h) 90{ 91 const char *path, *proxy_path, *lower_proto = "tcp", *local_path; 92 char hostname[1024], hoststr[1024], proto[10]; 93 char auth[1024], proxyauth[1024] = ""; 94 char path1[1024]; 95 char buf[1024], urlbuf[1024]; 96 int port, use_proxy, err, location_changed = 0, redirects = 0; 97 HTTPAuthType cur_auth_type, cur_proxy_auth_type; 98 HTTPContext *s = h->priv_data; 99 URLContext *hd = NULL; 100 101 proxy_path = getenv("http_proxy"); 102 use_proxy = (proxy_path != NULL) && !getenv("no_proxy") && 103 av_strstart(proxy_path, "http://", NULL); 104 105 /* fill the dest addr */ 106 redo: 107 /* needed in any case to build the host string */ 108 av_url_split(proto, sizeof(proto), auth, sizeof(auth), 109 hostname, sizeof(hostname), &port, 110 path1, sizeof(path1), s->location); 111 ff_url_join(hoststr, sizeof(hoststr), NULL, NULL, hostname, port, NULL); 112 113 if (!strcmp(proto, "https")) { 114 lower_proto = "tls"; 115 use_proxy = 0; 116 if (port < 0) 117 port = 443; 118 } 119 if (port < 0) 120 port = 80; 121 122 if (path1[0] == '\0') 123 path = "/"; 124 else 125 path = path1; 126 local_path = path; 127 if (use_proxy) { 128 /* Reassemble the request URL without auth string - we don't 129 * want to leak the auth to the proxy. */ 130 ff_url_join(urlbuf, sizeof(urlbuf), proto, NULL, hostname, port, "%s", 131 path1); 132 path = urlbuf; 133 av_url_split(NULL, 0, proxyauth, sizeof(proxyauth), 134 hostname, sizeof(hostname), &port, NULL, 0, proxy_path); 135 } 136 137 ff_url_join(buf, sizeof(buf), lower_proto, NULL, hostname, port, NULL); 138 err = ffurl_open(&hd, buf, AVIO_FLAG_READ_WRITE, 139 &h->interrupt_callback, NULL); 140 if (err < 0) 141 goto fail; 142 143 s->hd = hd; 144 cur_auth_type = s->auth_state.auth_type; 145 cur_proxy_auth_type = s->auth_state.auth_type; 146 if (http_connect(h, path, local_path, hoststr, auth, proxyauth, &location_changed) < 0) 147 goto fail; 148 if (s->http_code == 401) { 149 if (cur_auth_type == HTTP_AUTH_NONE && s->auth_state.auth_type != HTTP_AUTH_NONE) { 150 ffurl_close(hd); 151 goto redo; 152 } else 153 goto fail; 154 } 155 if (s->http_code == 407) { 156 if (cur_proxy_auth_type == HTTP_AUTH_NONE && 157 s->proxy_auth_state.auth_type != HTTP_AUTH_NONE) { 158 ffurl_close(hd); 159 goto redo; 160 } else 161 goto fail; 162 } 163 if ((s->http_code == 301 || s->http_code == 302 || s->http_code == 303 || s->http_code == 307) 164 && location_changed == 1) { 165 /* url moved, get next */ 166 ffurl_close(hd); 167 if (redirects++ >= MAX_REDIRECTS) 168 return AVERROR(EIO); 169 location_changed = 0; 170 goto redo; 171 } 172 return 0; 173 fail: 174 if (hd) 175 ffurl_close(hd); 176 s->hd = NULL; 177 return AVERROR(EIO); 178} 179 180static int http_open(URLContext *h, const char *uri, int flags) 181{ 182 HTTPContext *s = h->priv_data; 183 184 h->is_streamed = 1; 185 186 s->filesize = -1; 187 av_strlcpy(s->location, uri, sizeof(s->location)); 188 189 if (s->headers) { 190 int len = strlen(s->headers); 191 if (len < 2 || strcmp("\r\n", s->headers + len - 2)) 192 av_log(h, AV_LOG_WARNING, "No trailing CRLF found in HTTP header.\n"); 193 } 194 195 return http_open_cnx(h); 196} 197static int http_getc(HTTPContext *s) 198{ 199 int len; 200 if (s->buf_ptr >= s->buf_end) { 201 len = ffurl_read(s->hd, s->buffer, BUFFER_SIZE); 202 if (len < 0) { 203 return AVERROR(EIO); 204 } else if (len == 0) { 205 return -1; 206 } else { 207 s->buf_ptr = s->buffer; 208 s->buf_end = s->buffer + len; 209 } 210 } 211 return *s->buf_ptr++; 212} 213 214static int http_get_line(HTTPContext *s, char *line, int line_size) 215{ 216 int ch; 217 char *q; 218 219 q = line; 220 for(;;) { 221 ch = http_getc(s); 222 if (ch < 0) 223 return AVERROR(EIO); 224 if (ch == '\n') { 225 /* process line */ 226 if (q > line && q[-1] == '\r') 227 q--; 228 *q = '\0'; 229 230 return 0; 231 } else { 232 if ((q - line) < line_size - 1) 233 *q++ = ch; 234 } 235 } 236} 237 238static int process_line(URLContext *h, char *line, int line_count, 239 int *new_location) 240{ 241 HTTPContext *s = h->priv_data; 242 char *tag, *p, *end; 243 244 /* end of header */ 245 if (line[0] == '\0') 246 return 0; 247 248 p = line; 249 if (line_count == 0) { 250 while (!isspace(*p) && *p != '\0') 251 p++; 252 while (isspace(*p)) 253 p++; 254 s->http_code = strtol(p, &end, 10); 255 256 av_dlog(NULL, "http_code=%d\n", s->http_code); 257 258 /* error codes are 4xx and 5xx, but regard 401 as a success, so we 259 * don't abort until all headers have been parsed. */ 260 if (s->http_code >= 400 && s->http_code < 600 && (s->http_code != 401 261 || s->auth_state.auth_type != HTTP_AUTH_NONE) && 262 (s->http_code != 407 || s->proxy_auth_state.auth_type != HTTP_AUTH_NONE)) { 263 end += strspn(end, SPACE_CHARS); 264 av_log(h, AV_LOG_WARNING, "HTTP error %d %s\n", 265 s->http_code, end); 266 return -1; 267 } 268 } else { 269 while (*p != '\0' && *p != ':') 270 p++; 271 if (*p != ':') 272 return 1; 273 274 *p = '\0'; 275 tag = line; 276 p++; 277 while (isspace(*p)) 278 p++; 279 if (!av_strcasecmp(tag, "Location")) { 280 strcpy(s->location, p); 281 *new_location = 1; 282 } else if (!av_strcasecmp (tag, "Content-Length") && s->filesize == -1) { 283 s->filesize = atoll(p); 284 } else if (!av_strcasecmp (tag, "Content-Range")) { 285 /* "bytes $from-$to/$document_size" */ 286 const char *slash; 287 if (!strncmp (p, "bytes ", 6)) { 288 p += 6; 289 s->off = atoll(p); 290 if ((slash = strchr(p, '/')) && strlen(slash) > 0) 291 s->filesize = atoll(slash+1); 292 } 293 h->is_streamed = 0; /* we _can_ in fact seek */ 294 } else if (!av_strcasecmp(tag, "Accept-Ranges") && !strncmp(p, "bytes", 5)) { 295 h->is_streamed = 0; 296 } else if (!av_strcasecmp (tag, "Transfer-Encoding") && !av_strncasecmp(p, "chunked", 7)) { 297 s->filesize = -1; 298 s->chunksize = 0; 299 } else if (!av_strcasecmp (tag, "WWW-Authenticate")) { 300 ff_http_auth_handle_header(&s->auth_state, tag, p); 301 } else if (!av_strcasecmp (tag, "Authentication-Info")) { 302 ff_http_auth_handle_header(&s->auth_state, tag, p); 303 } else if (!av_strcasecmp (tag, "Proxy-Authenticate")) { 304 ff_http_auth_handle_header(&s->proxy_auth_state, tag, p); 305 } else if (!av_strcasecmp (tag, "Connection")) { 306 if (!strcmp(p, "close")) 307 s->willclose = 1; 308 } 309 } 310 return 1; 311} 312 313static inline int has_header(const char *str, const char *header) 314{ 315 /* header + 2 to skip over CRLF prefix. (make sure you have one!) */ 316 if (!str) 317 return 0; 318 return av_stristart(str, header + 2, NULL) || av_stristr(str, header); 319} 320 321static int http_connect(URLContext *h, const char *path, const char *local_path, 322 const char *hoststr, const char *auth, 323 const char *proxyauth, int *new_location) 324{ 325 HTTPContext *s = h->priv_data; 326 int post, err; 327 char line[1024]; 328 char headers[1024] = ""; 329 char *authstr = NULL, *proxyauthstr = NULL; 330 int64_t off = s->off; 331 int len = 0; 332 const char *method; 333 334 335 /* send http header */ 336 post = h->flags & AVIO_FLAG_WRITE; 337 method = post ? "POST" : "GET"; 338 authstr = ff_http_auth_create_response(&s->auth_state, auth, local_path, 339 method); 340 proxyauthstr = ff_http_auth_create_response(&s->proxy_auth_state, proxyauth, 341 local_path, method); 342 343 /* set default headers if needed */ 344 if (!has_header(s->headers, "\r\nUser-Agent: ")) 345 len += av_strlcatf(headers + len, sizeof(headers) - len, 346 "User-Agent: %s\r\n", LIBAVFORMAT_IDENT); 347 if (!has_header(s->headers, "\r\nAccept: ")) 348 len += av_strlcpy(headers + len, "Accept: */*\r\n", 349 sizeof(headers) - len); 350 if (!has_header(s->headers, "\r\nRange: ") && !post) 351 len += av_strlcatf(headers + len, sizeof(headers) - len, 352 "Range: bytes=%"PRId64"-\r\n", s->off); 353 if (!has_header(s->headers, "\r\nConnection: ")) 354 len += av_strlcpy(headers + len, "Connection: close\r\n", 355 sizeof(headers)-len); 356 if (!has_header(s->headers, "\r\nHost: ")) 357 len += av_strlcatf(headers + len, sizeof(headers) - len, 358 "Host: %s\r\n", hoststr); 359 360 /* now add in custom headers */ 361 if (s->headers) 362 av_strlcpy(headers + len, s->headers, sizeof(headers) - len); 363 364 snprintf(s->buffer, sizeof(s->buffer), 365 "%s %s HTTP/1.1\r\n" 366 "%s" 367 "%s" 368 "%s" 369 "%s%s" 370 "\r\n", 371 method, 372 path, 373 post && s->chunked_post ? "Transfer-Encoding: chunked\r\n" : "", 374 headers, 375 authstr ? authstr : "", 376 proxyauthstr ? "Proxy-" : "", proxyauthstr ? proxyauthstr : ""); 377 378 av_freep(&authstr); 379 av_freep(&proxyauthstr); 380 if (ffurl_write(s->hd, s->buffer, strlen(s->buffer)) < 0) 381 return AVERROR(EIO); 382 383 /* init input buffer */ 384 s->buf_ptr = s->buffer; 385 s->buf_end = s->buffer; 386 s->line_count = 0; 387 s->off = 0; 388 s->filesize = -1; 389 s->willclose = 0; 390 if (post) { 391 /* Pretend that it did work. We didn't read any header yet, since 392 * we've still to send the POST data, but the code calling this 393 * function will check http_code after we return. */ 394 s->http_code = 200; 395 return 0; 396 } 397 s->chunksize = -1; 398 399 /* wait for header */ 400 for(;;) { 401 if (http_get_line(s, line, sizeof(line)) < 0) 402 return AVERROR(EIO); 403 404 av_dlog(NULL, "header='%s'\n", line); 405 406 err = process_line(h, line, s->line_count, new_location); 407 if (err < 0) 408 return err; 409 if (err == 0) 410 break; 411 s->line_count++; 412 } 413 414 return (off == s->off) ? 0 : -1; 415} 416 417 418static int http_buf_read(URLContext *h, uint8_t *buf, int size) 419{ 420 HTTPContext *s = h->priv_data; 421 int len; 422 /* read bytes from input buffer first */ 423 len = s->buf_end - s->buf_ptr; 424 if (len > 0) { 425 if (len > size) 426 len = size; 427 memcpy(buf, s->buf_ptr, len); 428 s->buf_ptr += len; 429 } else { 430 if (!s->willclose && s->filesize >= 0 && s->off >= s->filesize) 431 return AVERROR_EOF; 432 len = ffurl_read(s->hd, buf, size); 433 } 434 if (len > 0) { 435 s->off += len; 436 if (s->chunksize > 0) 437 s->chunksize -= len; 438 } 439 return len; 440} 441 442static int http_read(URLContext *h, uint8_t *buf, int size) 443{ 444 HTTPContext *s = h->priv_data; 445 446 if (s->chunksize >= 0) { 447 if (!s->chunksize) { 448 char line[32]; 449 450 for(;;) { 451 do { 452 if (http_get_line(s, line, sizeof(line)) < 0) 453 return AVERROR(EIO); 454 } while (!*line); /* skip CR LF from last chunk */ 455 456 s->chunksize = strtoll(line, NULL, 16); 457 458 av_dlog(NULL, "Chunked encoding data size: %"PRId64"'\n", s->chunksize); 459 460 if (!s->chunksize) 461 return 0; 462 break; 463 } 464 } 465 size = FFMIN(size, s->chunksize); 466 } 467 return http_buf_read(h, buf, size); 468} 469 470/* used only when posting data */ 471static int http_write(URLContext *h, const uint8_t *buf, int size) 472{ 473 char temp[11] = ""; /* 32-bit hex + CRLF + nul */ 474 int ret; 475 char crlf[] = "\r\n"; 476 HTTPContext *s = h->priv_data; 477 478 if (!s->chunked_post) { 479 /* non-chunked data is sent without any special encoding */ 480 return ffurl_write(s->hd, buf, size); 481 } 482 483 /* silently ignore zero-size data since chunk encoding that would 484 * signal EOF */ 485 if (size > 0) { 486 /* upload data using chunked encoding */ 487 snprintf(temp, sizeof(temp), "%x\r\n", size); 488 489 if ((ret = ffurl_write(s->hd, temp, strlen(temp))) < 0 || 490 (ret = ffurl_write(s->hd, buf, size)) < 0 || 491 (ret = ffurl_write(s->hd, crlf, sizeof(crlf) - 1)) < 0) 492 return ret; 493 } 494 return size; 495} 496 497static int http_close(URLContext *h) 498{ 499 int ret = 0; 500 char footer[] = "0\r\n\r\n"; 501 HTTPContext *s = h->priv_data; 502 503 /* signal end of chunked encoding if used */ 504 if ((h->flags & AVIO_FLAG_WRITE) && s->chunked_post) { 505 ret = ffurl_write(s->hd, footer, sizeof(footer) - 1); 506 ret = ret > 0 ? 0 : ret; 507 } 508 509 if (s->hd) 510 ffurl_close(s->hd); 511 return ret; 512} 513 514static int64_t http_seek(URLContext *h, int64_t off, int whence) 515{ 516 HTTPContext *s = h->priv_data; 517 URLContext *old_hd = s->hd; 518 int64_t old_off = s->off; 519 uint8_t old_buf[BUFFER_SIZE]; 520 int old_buf_size; 521 522 if (whence == AVSEEK_SIZE) 523 return s->filesize; 524 else if ((s->filesize == -1 && whence == SEEK_END) || h->is_streamed) 525 return -1; 526 527 /* we save the old context in case the seek fails */ 528 old_buf_size = s->buf_end - s->buf_ptr; 529 memcpy(old_buf, s->buf_ptr, old_buf_size); 530 s->hd = NULL; 531 if (whence == SEEK_CUR) 532 off += s->off; 533 else if (whence == SEEK_END) 534 off += s->filesize; 535 s->off = off; 536 537 /* if it fails, continue on old connection */ 538 if (http_open_cnx(h) < 0) { 539 memcpy(s->buffer, old_buf, old_buf_size); 540 s->buf_ptr = s->buffer; 541 s->buf_end = s->buffer + old_buf_size; 542 s->hd = old_hd; 543 s->off = old_off; 544 return -1; 545 } 546 ffurl_close(old_hd); 547 return off; 548} 549 550static int 551http_get_file_handle(URLContext *h) 552{ 553 HTTPContext *s = h->priv_data; 554 return ffurl_get_file_handle(s->hd); 555} 556 557#if CONFIG_HTTP_PROTOCOL 558URLProtocol ff_http_protocol = { 559 .name = "http", 560 .url_open = http_open, 561 .url_read = http_read, 562 .url_write = http_write, 563 .url_seek = http_seek, 564 .url_close = http_close, 565 .url_get_file_handle = http_get_file_handle, 566 .priv_data_size = sizeof(HTTPContext), 567 .priv_data_class = &http_context_class, 568 .flags = URL_PROTOCOL_FLAG_NETWORK, 569}; 570#endif 571#if CONFIG_HTTPS_PROTOCOL 572URLProtocol ff_https_protocol = { 573 .name = "https", 574 .url_open = http_open, 575 .url_read = http_read, 576 .url_write = http_write, 577 .url_seek = http_seek, 578 .url_close = http_close, 579 .url_get_file_handle = http_get_file_handle, 580 .priv_data_size = sizeof(HTTPContext), 581 .priv_data_class = &https_context_class, 582 .flags = URL_PROTOCOL_FLAG_NETWORK, 583}; 584#endif 585 586#if CONFIG_HTTPPROXY_PROTOCOL 587static int http_proxy_close(URLContext *h) 588{ 589 HTTPContext *s = h->priv_data; 590 if (s->hd) 591 ffurl_close(s->hd); 592 return 0; 593} 594 595static int http_proxy_open(URLContext *h, const char *uri, int flags) 596{ 597 HTTPContext *s = h->priv_data; 598 char hostname[1024], hoststr[1024]; 599 char auth[1024], pathbuf[1024], *path; 600 char line[1024], lower_url[100]; 601 int port, ret = 0; 602 HTTPAuthType cur_auth_type; 603 char *authstr; 604 605 h->is_streamed = 1; 606 607 av_url_split(NULL, 0, auth, sizeof(auth), hostname, sizeof(hostname), &port, 608 pathbuf, sizeof(pathbuf), uri); 609 ff_url_join(hoststr, sizeof(hoststr), NULL, NULL, hostname, port, NULL); 610 path = pathbuf; 611 if (*path == '/') 612 path++; 613 614 ff_url_join(lower_url, sizeof(lower_url), "tcp", NULL, hostname, port, 615 NULL); 616redo: 617 ret = ffurl_open(&s->hd, lower_url, AVIO_FLAG_READ_WRITE, 618 &h->interrupt_callback, NULL); 619 if (ret < 0) 620 return ret; 621 622 authstr = ff_http_auth_create_response(&s->proxy_auth_state, auth, 623 path, "CONNECT"); 624 snprintf(s->buffer, sizeof(s->buffer), 625 "CONNECT %s HTTP/1.1\r\n" 626 "Host: %s\r\n" 627 "Connection: close\r\n" 628 "%s%s" 629 "\r\n", 630 path, 631 hoststr, 632 authstr ? "Proxy-" : "", authstr ? authstr : ""); 633 av_freep(&authstr); 634 635 if ((ret = ffurl_write(s->hd, s->buffer, strlen(s->buffer))) < 0) 636 goto fail; 637 638 s->buf_ptr = s->buffer; 639 s->buf_end = s->buffer; 640 s->line_count = 0; 641 s->filesize = -1; 642 cur_auth_type = s->proxy_auth_state.auth_type; 643 644 for (;;) { 645 int new_loc; 646 // Note: This uses buffering, potentially reading more than the 647 // HTTP header. If tunneling a protocol where the server starts 648 // the conversation, we might buffer part of that here, too. 649 // Reading that requires using the proper ffurl_read() function 650 // on this URLContext, not using the fd directly (as the tls 651 // protocol does). This shouldn't be an issue for tls though, 652 // since the client starts the conversation there, so there 653 // is no extra data that we might buffer up here. 654 if (http_get_line(s, line, sizeof(line)) < 0) { 655 ret = AVERROR(EIO); 656 goto fail; 657 } 658 659 av_dlog(h, "header='%s'\n", line); 660 661 ret = process_line(h, line, s->line_count, &new_loc); 662 if (ret < 0) 663 goto fail; 664 if (ret == 0) 665 break; 666 s->line_count++; 667 } 668 if (s->http_code == 407 && cur_auth_type == HTTP_AUTH_NONE && 669 s->proxy_auth_state.auth_type != HTTP_AUTH_NONE) { 670 ffurl_close(s->hd); 671 s->hd = NULL; 672 goto redo; 673 } 674 675 if (s->http_code < 400) 676 return 0; 677 ret = AVERROR(EIO); 678 679fail: 680 http_proxy_close(h); 681 return ret; 682} 683 684static int http_proxy_write(URLContext *h, const uint8_t *buf, int size) 685{ 686 HTTPContext *s = h->priv_data; 687 return ffurl_write(s->hd, buf, size); 688} 689 690URLProtocol ff_httpproxy_protocol = { 691 .name = "httpproxy", 692 .url_open = http_proxy_open, 693 .url_read = http_buf_read, 694 .url_write = http_proxy_write, 695 .url_close = http_proxy_close, 696 .url_get_file_handle = http_get_file_handle, 697 .priv_data_size = sizeof(HTTPContext), 698 .flags = URL_PROTOCOL_FLAG_NETWORK, 699}; 700#endif 701