[2/5] http: Add a new protocol for opening connections via http proxies

Message ID 1321014025-81875-2-git-send-email-martin@martin.st
State Committed
Headers show

Commit Message

Martin Storsjö Nov. 11, 2011, 12:20 p.m.
This opens a plain TCP connection through the proxy via the
CONNECT HTTP method. Normally, this is allowed for connections
on port 443, but can in general be used to allow connections
to any port (depending on proxy configuration), and could thus
be used to tunnel any TCP connection via a HTTP proxy.
---
 libavformat/allformats.c |    1 +
 libavformat/http.c       |  114 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 115 insertions(+), 0 deletions(-)

Comments

Martin Storsjö Nov. 11, 2011, 1:04 p.m. | #1
On Fri, 11 Nov 2011, Martin Storsjö wrote:

> This opens a plain TCP connection through the proxy via the
> CONNECT HTTP method. Normally, this is allowed for connections
> on port 443, but can in general be used to allow connections
> to any port (depending on proxy configuration), and could thus
> be used to tunnel any TCP connection via a HTTP proxy.
> ---
> libavformat/allformats.c |    1 +
> libavformat/http.c       |  114 ++++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 115 insertions(+), 0 deletions(-)

This is missing a minor bump, added locally.

// Martin

Patch

diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 00924c8..bee2f5f 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -242,6 +242,7 @@  void av_register_all(void)
     REGISTER_PROTOCOL (FILE, file);
     REGISTER_PROTOCOL (GOPHER, gopher);
     REGISTER_PROTOCOL (HTTP, http);
+    REGISTER_PROTOCOL (HTTPPROXY, httpproxy);
     REGISTER_PROTOCOL (HTTPS, https);
     REGISTER_PROTOCOL (MMSH, mmsh);
     REGISTER_PROTOCOL (MMST, mmst);
diff --git a/libavformat/http.c b/libavformat/http.c
index 66cfd69..e476fa1 100644
--- a/libavformat/http.c
+++ b/libavformat/http.c
@@ -577,3 +577,117 @@  URLProtocol ff_https_protocol = {
     .priv_data_class     = &https_context_class,
 };
 #endif
+
+#if CONFIG_HTTPPROXY_PROTOCOL
+static int http_proxy_close(URLContext *h)
+{
+    HTTPContext *s = h->priv_data;
+    if (s->hd)
+        ffurl_close(s->hd);
+    return 0;
+}
+
+static int http_proxy_open(URLContext *h, const char *uri, int flags)
+{
+    HTTPContext *s = h->priv_data;
+    char hostname[1024], hoststr[1024];
+    char auth[1024], pathbuf[1024], *path;
+    char line[1024], lower_url[100];
+    int port, ret = 0;
+    HTTPAuthType cur_auth_type;
+    char *authstr;
+
+    h->is_streamed = 1;
+
+    av_url_split(NULL, 0, auth, sizeof(auth), hostname, sizeof(hostname), &port,
+                 pathbuf, sizeof(pathbuf), uri);
+    ff_url_join(hoststr, sizeof(hoststr), NULL, NULL, hostname, port, NULL);
+    path = pathbuf;
+    if (*path == '/')
+        path++;
+
+    ff_url_join(lower_url, sizeof(lower_url), "tcp", NULL, hostname, port,
+                NULL);
+redo:
+    ret = ffurl_open(&s->hd, lower_url, AVIO_FLAG_READ_WRITE);
+    if (ret < 0)
+        return ret;
+
+    authstr = ff_http_auth_create_response(&s->proxy_auth_state, auth,
+                                           path, "CONNECT");
+    snprintf(s->buffer, sizeof(s->buffer),
+             "CONNECT %s HTTP/1.1\r\n"
+             "Host: %s\r\n"
+             "Connection: close\r\n"
+             "%s%s"
+             "\r\n",
+             path,
+             hoststr,
+             authstr ? "Proxy-" : "", authstr ? authstr : "");
+    av_freep(&authstr);
+
+    if ((ret = ffurl_write(s->hd, s->buffer, strlen(s->buffer))) < 0)
+        goto fail;
+
+    s->buf_ptr = s->buffer;
+    s->buf_end = s->buffer;
+    s->line_count = 0;
+    s->filesize = -1;
+    cur_auth_type = s->proxy_auth_state.auth_type;
+
+    for (;;) {
+        int new_loc;
+        // Note: This uses buffering, potentially reading more than the
+        // HTTP header. If tunneling a protocol where the server starts
+        // the conversation, we might buffer part of that here, too.
+        // Reading that requires using the proper ffurl_read() function
+        // on this URLContext, not using the fd directly (as the tls
+        // protocol does). This shouldn't be an issue for tls though,
+        // since the client starts the conversation there, so there
+        // is no extra data that we might buffer up here.
+        if (http_get_line(s, line, sizeof(line)) < 0) {
+            ret = AVERROR(EIO);
+            goto fail;
+        }
+
+        av_dlog(h, "header='%s'\n", line);
+
+        ret = process_line(h, line, s->line_count, &new_loc);
+        if (ret < 0)
+            goto fail;
+        if (ret == 0)
+            break;
+        s->line_count++;
+    }
+    if (s->http_code == 407 && cur_auth_type == HTTP_AUTH_NONE &&
+        s->proxy_auth_state.auth_type != HTTP_AUTH_NONE) {
+        ffurl_close(s->hd);
+        s->hd = NULL;
+        goto redo;
+    }
+
+    if (s->http_code < 400)
+        return 0;
+    ret = AVERROR(EIO);
+
+fail:
+    http_proxy_close(h);
+    return ret;
+}
+
+static int http_proxy_write(URLContext *h, const uint8_t *buf, int size)
+{
+    HTTPContext *s = h->priv_data;
+    return ffurl_write(s->hd, buf, size);
+}
+
+URLProtocol ff_httpproxy_protocol = {
+    .name                = "httpproxy",
+    .url_open            = http_proxy_open,
+    .url_read            = http_buf_read,
+    .url_write           = http_proxy_write,
+    .url_close           = http_proxy_close,
+    .url_get_file_handle = http_get_file_handle,
+    .priv_data_size      = sizeof(HTTPContext),
+};
+#endif