ifdeffed-out the Location-header rewrite in proxy_io(). It appears that
[pazpar2-moved-to-github.git] / src / http.c
index bb61e9f..021d730 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * $Id: http.c,v 1.3 2006-12-21 04:16:25 quinn Exp $
+ * $Id: http.c,v 1.16 2007-03-29 15:23:17 quinn Exp $
  */
 
 #include <stdio.h>
 #include <netdb.h>
 #include <errno.h>
 #include <assert.h>
+#include <string.h>
+
+#if HAVE_CONFIG_H
+#include <cconfig.h>
+#endif
+
+#include <netinet/in.h>
 
 #include <yaz/yaz-util.h>
 #include <yaz/comstack.h>
 #include <netdb.h>
 
-#include "command.h"
+#include "cconfig.h"
 #include "util.h"
 #include "eventl.h"
 #include "pazpar2.h"
@@ -31,9 +38,12 @@ static struct http_channel *http_create(void);
 static void http_destroy(IOCHAN i);
 
 extern IOCHAN channel_list;
+extern struct parameters global_parameters;
 
-static struct sockaddr_in *proxy_addr = 0; // If this is set, we proxy normal HTTP requests
+// If this is set, we proxy normal HTTP requests
+static struct sockaddr_in *proxy_addr = 0; 
 static char proxy_url[256] = "";
+static char myurl[256] = "";
 static struct http_buf *http_buf_freelist = 0;
 static struct http_channel *http_channel_freelist = 0;
 
@@ -89,10 +99,10 @@ static struct http_buf *http_buf_bybuf(char *b, int len)
 
     while (len)
     {
-        *p = http_buf_create();
         int tocopy = len;
         if (tocopy > HTTP_BUF_SIZE)
             tocopy = HTTP_BUF_SIZE;
+        *p = http_buf_create();
         memcpy((*p)->buf, b, tocopy);
         (*p)->len = tocopy;
         len -= tocopy;
@@ -162,7 +172,8 @@ static int http_buf_read(struct http_buf **b, char *buf, int len)
     return rd;
 }
 
-void static urldecode(char *i, char *o)
+// Buffers may overlap.
+static void urldecode(char *i, char *o)
 {
     while (*i)
     {
@@ -184,6 +195,23 @@ void static urldecode(char *i, char *o)
     *o = '\0';
 }
 
+// Warning: Buffers may not overlap
+void urlencode(const char *i, char *o)
+{
+    while (*i)
+    {
+        if (strchr(" /:", *i))
+        {
+            sprintf(o, "%%%.2X", (int) *i);
+            o += 3;
+        }
+        else
+            *(o++) = *i;
+        i++;
+    }
+    *o = '\0';
+}
+
 void http_addheader(struct http_response *r, const char *name, const char *value)
 {
     struct http_channel *c = r->channel;
@@ -205,12 +233,11 @@ char *http_argbyname(struct http_request *r, char *name)
     return 0;
 }
 
-char *http_headerbyname(struct http_request *r, char *name)
+char *http_headerbyname(struct http_header *h, char *name)
 {
-    struct http_header *p;
-    for (p = r->headers; p; p = p->next)
-        if (!strcmp(p->name, name))
-            return p->value;
+    for (; h; h = h->next)
+        if (!strcmp(h->name, name))
+            return h->value;
     return 0;
 }
 
@@ -225,16 +252,10 @@ struct http_response *http_create_response(struct http_channel *c)
     return r;
 }
 
-// Check if we have a complete request. Return 0 or length (including trailing newline)
-// FIXME: Does not deal gracefully with requests carrying payload
-// but this is kind of OK since we will reject anything other than an empty GET
-static int request_check(struct http_buf *queue)
+// Check if buf contains a package (minus payload)
+static int package_check(const char *buf)
 {
-    char tmp[4096];
     int len = 0;
-    char *buf = tmp;
-
-    http_buf_peek(queue, tmp, 4096);
     while (*buf) // Check if we have a sequence of lines terminated by an empty line
     {
         char *b = strstr(buf, "\r\n");
@@ -250,6 +271,69 @@ static int request_check(struct http_buf *queue)
     return 0;
 }
 
+// Check if we have a request. Return 0 or length
+// (including trailing CRNL) FIXME: Does not deal gracefully with requests
+// carrying payload but this is kind of OK since we will reject anything
+// other than an empty GET
+static int request_check(struct http_buf *queue)
+{
+    char tmp[4096];
+
+    http_buf_peek(queue, tmp, 4096);
+    return package_check(tmp);
+}
+
+struct http_response *http_parse_response_buf(struct http_channel *c, const char *buf, int len)
+{
+    char tmp[4096];
+    struct http_response *r = http_create_response(c);
+    char *p, *p2;
+    struct http_header **hp = &r->headers;
+
+    if (len >= 4096)
+        return 0;
+    memcpy(tmp, buf, len);
+    for (p = tmp; *p && *p != ' '; p++) // Skip HTTP version
+        ;
+    p++;
+    // Response code
+    for (p2 = p; *p2 && *p2 != ' ' && p2 - p < 3; p2++)
+        r->code[p2 - p] = *p2;
+    if (!(p = strstr(tmp, "\r\n")))
+        return 0;
+    p += 2;
+    while (*p)
+    {
+        if (!(p2 = strstr(p, "\r\n")))
+            return 0;
+        if (p == p2) // End of headers
+            break;
+        else
+        {
+            struct http_header *h = *hp = nmem_malloc(c->nmem, sizeof(*h));
+            char *value = strchr(p, ':');
+            if (!value)
+                return 0;
+            *(value++) = '\0';
+            h->name = nmem_strdup(c->nmem, p);
+            while (isspace(*value))
+                value++;
+            if (value >= p2)  // Empty header;
+            {
+                h->value = "";
+                p = p2 + 2;
+                continue;
+            }
+            *p2 = '\0';
+            h->value = nmem_strdup(c->nmem, value);
+            h->next = 0;
+            hp = &h->next;
+            p = p2 + 2;
+        }
+    }
+    return r;
+}
+
 struct http_request *http_parse_request(struct http_channel *c, struct http_buf **queue,
         int len)
 {
@@ -368,18 +452,21 @@ struct http_request *http_parse_request(struct http_channel *c, struct http_buf
     return r;
 }
 
-
 static struct http_buf *http_serialize_response(struct http_channel *c,
         struct http_response *r)
 {
-    wrbuf_rewind(c->wrbuf);
     struct http_header *h;
 
+    wrbuf_rewind(c->wrbuf);
     wrbuf_printf(c->wrbuf, "HTTP/1.1 %s %s\r\n", r->code, r->msg);
     for (h = r->headers; h; h = h->next)
         wrbuf_printf(c->wrbuf, "%s: %s\r\n", h->name, h->value);
-    wrbuf_printf(c->wrbuf, "Content-length: %d\r\n", r->payload ? (int) strlen(r->payload) : 0);
-    wrbuf_printf(c->wrbuf, "Content-type: text/xml\r\n");
+    if (r->payload)
+    {
+        wrbuf_printf(c->wrbuf, "Content-length: %d\r\n", r->payload ?
+                (int) strlen(r->payload) : 0);
+        wrbuf_printf(c->wrbuf, "Content-type: text/xml\r\n");
+    }
     wrbuf_puts(c->wrbuf, "\r\n");
 
     if (r->payload)
@@ -392,10 +479,10 @@ static struct http_buf *http_serialize_response(struct http_channel *c,
 static struct http_buf *http_serialize_request(struct http_request *r)
 {
     struct http_channel *c = r->channel;
-    wrbuf_rewind(c->wrbuf);
     struct http_header *h;
     struct http_argument *a;
 
+    wrbuf_rewind(c->wrbuf);
     wrbuf_printf(c->wrbuf, "%s %s", r->method, r->path);
 
     if (r->arguments)
@@ -426,12 +513,46 @@ static int http_weshouldproxy(struct http_request *rq)
     return 0;
 }
 
+
+struct http_header * http_header_append(struct http_channel *ch, 
+                                        struct http_header * hp, 
+                                        const char *name, 
+                                        const char *value)
+{
+    struct http_header *hpnew = 0; 
+
+    if (!hp | !ch)
+        return 0;
+
+    while (hp && hp->next)
+        hp = hp->next;
+
+    if(name && strlen(name)&& value && strlen(value)){
+        hpnew = nmem_malloc(ch->nmem, sizeof *hpnew);
+        hpnew->name = nmem_strdup(ch->nmem, name);
+        hpnew->value = nmem_strdup(ch->nmem, value);
+        
+        hpnew->next = 0;
+        hp->next = hpnew;
+        hp = hp->next;
+        
+        return hpnew;
+    }
+
+    return hp;
+}
+
+    
+
 static int http_proxy(struct http_request *rq)
 {
     struct http_channel *c = rq->channel;
     struct http_proxy *p = c->proxy;
     struct http_header *hp;
     struct http_buf *requestbuf;
+    char server_via[128] = "";
+    char server_port[16] = "";
+    struct conf_server *ser = global_parameters.server;
 
     if (!p) // This is a new connection. Create a proxy channel
     {
@@ -455,7 +576,8 @@ static int http_proxy(struct http_request *rq)
             yaz_log(YLOG_FATAL|YLOG_ERRNO, "fcntl");
         if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0)
             yaz_log(YLOG_FATAL|YLOG_ERRNO, "fcntl2");
-        if (connect(sock, (struct sockaddr *) proxy_addr, sizeof(*proxy_addr)) < 0)
+        if (connect(sock, (struct sockaddr *) proxy_addr, 
+                    sizeof(*proxy_addr)) < 0)
             if (errno != EINPROGRESS)
             {
                 yaz_log(YLOG_WARN|YLOG_ERRNO, "Proxy connect");
@@ -465,15 +587,16 @@ static int http_proxy(struct http_request *rq)
         p = xmalloc(sizeof(struct http_proxy));
         p->oqueue = 0;
         p->channel = c;
+        p->first_response = 1;
         c->proxy = p;
         // We will add EVENT_OUTPUT below
-        p->iochan = iochan_create(sock, proxy_io, EVENT_INPUT);
+        p->iochan = iochan_create(sock, 0, proxy_io, EVENT_INPUT);
         iochan_setdata(p->iochan, p);
         p->iochan->next = channel_list;
         channel_list = p->iochan;
     }
 
-    // Modify Host: header
+    // Do _not_ modify Host: header, just checking it's existence
     for (hp = rq->headers; hp; hp = hp->next)
         if (!strcmp(hp->name, "Host"))
             break;
@@ -482,7 +605,27 @@ static int http_proxy(struct http_request *rq)
         yaz_log(YLOG_WARN, "Failed to find Host header in proxy");
         return -1;
     }
-    hp->value = nmem_strdup(c->nmem, proxy_url);
+    
+    // Add new header about paraz2 version, host, remote client address, etc.
+    {
+        hp = rq->headers;
+        hp = http_header_append(c, hp, 
+                                PACKAGE_NAME "-version", PACKAGE_VERSION);
+        hp = http_header_append(c, hp, 
+                                PACKAGE_NAME "-server-host", ser->myurl);
+        sprintf(server_port, "%d",  ser->port);
+        hp = http_header_append(c, hp, 
+                                PACKAGE_NAME "-server-port", server_port);
+        sprintf(server_via,  "1.1 %s:%s (%s/%s)",  
+                ser->myurl, server_port, PACKAGE_NAME, PACKAGE_VERSION);
+        hp = http_header_append(c, hp, 
+                                "Via" , server_via);
+        //hp = http_header_append(c, hp,"Client-ip", 
+        //                        c->iochan->addr_str);
+        hp = http_header_append(c, hp,"X-Forwarded-For", 
+                                c->iochan->addr_str);
+      }
+
     requestbuf = http_serialize_request(rq);
     http_buf_enqueue(&p->oqueue, requestbuf);
     iochan_setflag(p->iochan, EVENT_OUTPUT);
@@ -492,8 +635,10 @@ static int http_proxy(struct http_request *rq)
 void http_send_response(struct http_channel *ch)
 {
     struct http_response *rs = ch->response;
+    struct http_buf *hb;
+
     assert(rs);
-    struct http_buf *hb = http_serialize_response(ch, rs);
+    hb = http_serialize_response(ch, rs);
     if (!hb)
     {
         yaz_log(YLOG_WARN, "Failed to serialize HTTP response");
@@ -550,11 +695,10 @@ static void http_io(IOCHAN i, int event)
                 return;
             }
             hc->response = 0;
-            yaz_log(YLOG_LOG, "Request: %s %s%s%s v %s", hc->request->method,
+            yaz_log(YLOG_LOG, "Request: %s %s%s%s", hc->request->method,
                     hc->request->path,
                     *hc->request->search ? "?" : "",
-                    hc->request->search,
-                    hc->request->http_version);
+                    hc->request->search);
             if (http_weshouldproxy(hc->request))
                 http_proxy(hc->request);
             else
@@ -616,6 +760,23 @@ static void http_io(IOCHAN i, int event)
     }
 }
 
+// If this hostname contains our proxy host as a prefix, replace with myurl
+static char *sub_hostname(struct http_channel *c, char *buf)
+{
+    char tmp[1024];
+    if (strlen(buf) > 1023)
+        return buf;
+    if (strncmp(buf, "http://", 7))
+        return buf;
+    if (!strncmp(buf + 7, proxy_url, strlen(proxy_url)))
+    {
+        strcpy(tmp, myurl);
+        strcat(tmp, buf + strlen(proxy_url) + 7);
+        return nmem_strdup(c->nmem, tmp);
+    }
+    return buf;
+}
+
 // Handles I/O on a client connection to a backend web server (proxy mode)
 static void proxy_io(IOCHAN pi, int event)
 {
@@ -650,8 +811,38 @@ static void proxy_io(IOCHAN pi, int event)
             else
             {
                 htbuf->buf[res] = '\0';
+                htbuf->offset = 0;
                 htbuf->len = res;
-                http_buf_enqueue(&hc->oqueue, htbuf);
+#ifdef GAGA
+                if (pc->first_response) // Check if this is a redirect
+                {
+                    int len;
+                    if ((len = package_check(htbuf->buf)))
+                    {
+                        struct http_response *res = http_parse_response_buf(hc, htbuf->buf, len);
+                        if (res)
+                        {
+                            struct http_header *h;
+                            for (h = res->headers; h; h = h->next)
+                                if (!strcmp(h->name, "Location"))
+                                {
+                                    // We found a location header. Rewrite it.
+                                    struct http_buf *buf;
+                                    h->value = sub_hostname(hc, h->value);
+                                    buf = http_serialize_response(hc, res);
+                                    yaz_log(YLOG_LOG, "Proxy rewrite");
+                                    http_buf_enqueue(&hc->oqueue, buf);
+                                    htbuf->offset = len;
+                                    break;
+                                }
+                        }
+                    }
+                    pc->first_response = 0;
+                }
+#endif
+                // Write any remaining payload
+                if (htbuf->len - htbuf->offset > 0)
+                    http_buf_enqueue(&hc->oqueue, htbuf);
             }
             iochan_setflag(hc->iochan, EVENT_OUTPUT);
             break;
@@ -759,8 +950,8 @@ static void http_accept(IOCHAN i, int event)
     if (fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0)
         yaz_log(YLOG_FATAL|YLOG_ERRNO, "fcntl2");
 
-    yaz_log(YLOG_LOG, "New command connection");
-    c = iochan_create(s, http_io, EVENT_INPUT | EVENT_EXCEPT);
+    yaz_log(YLOG_DEBUG, "New command connection");
+    c = iochan_create(s, &addr, http_io, EVENT_INPUT | EVENT_EXCEPT);
 
     ch = http_create();
     ch->iochan = c;
@@ -783,7 +974,7 @@ void http_init(const char *addr)
 
     yaz_log(YLOG_LOG, "HTTP listener is %s", addr);
 
-    bzero(&myaddr, sizeof myaddr);
+    memset(&myaddr, 0, sizeof myaddr);
     myaddr.sin_family = AF_INET;
     pp = strchr(addr, ':');
     if (pp)
@@ -823,17 +1014,18 @@ void http_init(const char *addr)
     if (listen(l, SOMAXCONN) < 0) 
         yaz_log(YLOG_FATAL|YLOG_ERRNO, "listen");
 
-    c = iochan_create(l, http_accept, EVENT_INPUT | EVENT_EXCEPT);
+    c = iochan_create(l, &myaddr, http_accept, EVENT_INPUT | EVENT_EXCEPT);
     c->next = channel_list;
     channel_list = c;
 }
 
-void http_set_proxyaddr(char *host)
+void http_set_proxyaddr(char *host, char *base_url)
 {
     char *p;
     int port;
     struct hostent *he;
 
+    strcpy(myurl, base_url);
     strcpy(proxy_url, host);
     p = strchr(host, ':');
     yaz_log(YLOG_DEBUG, "Proxying for %s", host);