GPLv2. Added appendix with full license. Added refernece to that from
[pazpar2-moved-to-github.git] / src / http.c
index 401f5ae..5248ede 100644 (file)
@@ -1,5 +1,22 @@
-/*
- * $Id: http.c,v 1.1 2006-12-20 20:47:16 quinn Exp $
+/* $Id: http.c,v 1.23 2007-04-10 08:48:56 adam Exp $
+   Copyright (c) 2006-2007, Index Data.
+
+This file is part of Pazpar2.
+
+Pazpar2 is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 2, or (at your option) any later
+version.
+
+Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with Pazpar2; see the file LICENSE.  If not, write to the
+Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+02111-1307, USA.
  */
 
 #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 <arpa/inet.h>
+#include <netdb.h>
 
 #include <yaz/yaz-util.h>
 #include <yaz/comstack.h>
-#include <netdb.h>
+#include <yaz/nmem.h>
 
-#include "command.h"
+#include "cconfig.h"
 #include "util.h"
 #include "eventl.h"
 #include "pazpar2.h"
 #include "http_command.h"
 
 static void proxy_io(IOCHAN i, int event);
-static struct http_channel *http_create(void);
+static struct http_channel *http_create(const char *addr);
 static void http_destroy(IOCHAN i);
 
 extern IOCHAN channel_list;
+extern struct parameters global_parameters;
+//extern NMEM nmem;
 
-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 +119,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 +192,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 +215,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 +253,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 +272,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 +291,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)
 {
@@ -263,6 +367,7 @@ struct http_request *http_parse_request(struct http_channel *c, struct http_buf
     if (http_buf_read(queue, buf, len) < len)
         return 0;
 
+    r->search = "";
     r->channel = c;
     r->arguments = 0;
     r->headers = 0;
@@ -293,6 +398,7 @@ struct http_request *http_parse_request(struct http_channel *c, struct http_buf
     r->path = nmem_strdup(c->nmem, buf);
     if (p2)
     {
+        r->search = nmem_strdup(c->nmem, p2);
         // Parse Arguments
         while (*p2)
         {
@@ -366,18 +472,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)
@@ -390,10 +499,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)
@@ -424,12 +533,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
     {
@@ -453,7 +596,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");
@@ -463,6 +607,7 @@ 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);
@@ -471,7 +616,7 @@ static int http_proxy(struct http_request *rq)
         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;
@@ -480,7 +625,23 @@ 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, 
+                                "X-Pazpar2-Version", PACKAGE_VERSION);
+        hp = http_header_append(c, hp, 
+                                "X-Pazpar2-Server-Host", ser->host);
+        sprintf(server_port, "%d",  ser->port);
+        hp = http_header_append(c, hp, 
+                                "X-Pazpar2-Server-Port", server_port);
+        sprintf(server_via,  "1.1 %s:%s (%s/%s)",  
+                ser->host, server_port, PACKAGE_NAME, PACKAGE_VERSION);
+        hp = http_header_append(c, hp, "Via" , server_via);
+        hp = http_header_append(c, hp, "X-Forwarded-For", c->addr);
+    }
+    
     requestbuf = http_serialize_request(rq);
     http_buf_enqueue(&p->oqueue, requestbuf);
     iochan_setflag(p->iochan, EVENT_OUTPUT);
@@ -490,8 +651,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");
@@ -517,7 +680,12 @@ static void http_io(IOCHAN i, int event)
         case EVENT_INPUT:
             htbuf = http_buf_create();
             res = read(iochan_getfd(i), htbuf->buf, HTTP_BUF_SIZE -1);
-            if (res <= 0 && errno != EAGAIN)
+            if (res == -1 && errno == EAGAIN)
+            {
+                http_buf_destroy(htbuf);
+                return;
+            }
+            if (res <= 0)
             {
                 http_buf_destroy(htbuf);
                 http_destroy(i);
@@ -532,7 +700,6 @@ static void http_io(IOCHAN i, int event)
 
             if (hc->state == Http_Busy)
                 return;
-
             if ((reqlen = request_check(hc->iqueue)) <= 2)
                 return;
 
@@ -544,8 +711,10 @@ static void http_io(IOCHAN i, int event)
                 return;
             }
             hc->response = 0;
-            yaz_log(YLOG_LOG, "Request: %s %s v %s", hc->request->method,
-                    hc->request->path, hc->request->http_version);
+            yaz_log(YLOG_LOG, "Request: %s %s%s%s", hc->request->method,
+                    hc->request->path,
+                    *hc->request->search ? "?" : "",
+                    hc->request->search);
             if (http_weshouldproxy(hc->request))
                 http_proxy(hc->request);
             else
@@ -607,6 +776,25 @@ static void http_io(IOCHAN i, int event)
     }
 }
 
+#ifdef GAGA
+// 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;
+}
+#endif
+
 // Handles I/O on a client connection to a backend web server (proxy mode)
 static void proxy_io(IOCHAN pi, int event)
 {
@@ -641,8 +829,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;
@@ -702,7 +920,7 @@ static void http_destroy(IOCHAN i)
     iochan_destroy(i);
 }
 
-static struct http_channel *http_create(void)
+static struct http_channel *http_create(const char *addr)
 {
     struct http_channel *r = http_channel_freelist;
 
@@ -724,6 +942,12 @@ static struct http_channel *http_create(void)
     r->state = Http_Idle;
     r->request = 0;
     r->response = 0;
+    if (!addr)
+    {
+        yaz_log(YLOG_WARN, "Invalid HTTP forward address");
+        exit(1);
+    }
+    r->addr = nmem_strdup(r->nmem, addr);
     return r;
 }
 
@@ -750,10 +974,10 @@ 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");
+    yaz_log(YLOG_DEBUG, "New command connection");
     c = iochan_create(s, http_io, EVENT_INPUT | EVENT_EXCEPT);
-
-    ch = http_create();
+    
+    ch = http_create(inet_ntoa(addr.sin_addr));
     ch->iochan = c;
     iochan_setdata(c, ch);
 
@@ -772,9 +996,9 @@ void http_init(const char *addr)
     const char *pp;
     int port;
 
-    yaz_log(YLOG_LOG, "HTTP listener is %s", addr);
+    yaz_log(YLOG_LOG, "HTTP listener %s", addr);
 
-    bzero(&myaddr, sizeof myaddr);
+    memset(&myaddr, 0, sizeof myaddr);
     myaddr.sin_family = AF_INET;
     pp = strchr(addr, ':');
     if (pp)
@@ -790,16 +1014,37 @@ void http_init(const char *addr)
             yaz_log(YLOG_FATAL, "Unable to resolve '%s'", hostname);
             exit(1);
         }
+        
         memcpy(&myaddr.sin_addr.s_addr, he->h_addr_list[0], he->h_length);
         port = atoi(pp + 1);
+
+        yaz_log(YLOG_LOG, "HTTP address  %s:%d", 
+                "" == he->h_addr_list[0] ? he->h_addr_list[0] : "127.0.0.1" , 
+                    port);
+
     }
     else
     {
+        //size_t len = 128;
+        //char h[len];
         port = atoi(addr);
         myaddr.sin_addr.s_addr = INADDR_ANY;
+
+#if 0
+        // get hostname from system - after deciding to bind to any 
+        // IP address this box might have.
+        if (0 == gethostname(h, len)){
+            h[len - 1] = '\0';
+            global_parameters.server->host = nmem_strdup(nmem, h);
+        } else 
+            yaz_log(YLOG_WARN, "Could not get host name");
+#endif
     }
+
+
     myaddr.sin_port = htons(port);
 
+
     if (!(p = getprotobyname("tcp"))) {
         abort();
     }
@@ -819,15 +1064,17 @@ void http_init(const char *addr)
     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);
+    yaz_log(YLOG_LOG, "HTTP backend  %s", proxy_url);
     if (p) {
         port = atoi(p + 1);
         *p = '\0';