Remove ZOOM_RESULT_LISTS not in use
[yaz-moved-to-github.git] / src / zoom-c.c
index 005e724..2877a16 100644 (file)
@@ -1,5 +1,5 @@
 /* This file is part of the YAZ toolkit.
- * Copyright (C) 1995-2011 Index Data
+ * Copyright (C) 1995-2013 Index Data
  * See the file LICENSE for details.
  */
 /**
@@ -70,8 +70,9 @@ void ZOOM_set_dset_error(ZOOM_connection c, int error,
     }
     if (addinfo && addinfo2)
     {
-        c->addinfo = (char*) xmalloc(strlen(addinfo) + strlen(addinfo2) + 2);
+        c->addinfo = (char*) xmalloc(strlen(addinfo) + strlen(addinfo2) + 3);
         strcpy(c->addinfo, addinfo);
+        strcat(c->addinfo, ": ");
         strcat(c->addinfo, addinfo2);
     }
     else if (addinfo)
@@ -88,7 +89,7 @@ void ZOOM_set_dset_error(ZOOM_connection c, int error,
 
 int ZOOM_uri_to_code(const char *uri)
 {
-    int code = 0;       
+    int code = 0;
     const char *cp;
     if ((cp = strrchr(uri, '/')))
         code = atoi(cp+1);
@@ -198,11 +199,13 @@ void ZOOM_connection_remove_task(ZOOM_connection c)
             resultset_destroy(task->u.search.resultset);
             xfree(task->u.search.syntax);
             xfree(task->u.search.elementSetName);
+            xfree(task->u.search.schema);
             break;
         case ZOOM_TASK_RETRIEVE:
             resultset_destroy(task->u.retrieve.resultset);
             xfree(task->u.retrieve.syntax);
             xfree(task->u.retrieve.elementSetName);
+            xfree(task->u.retrieve.schema);
             break;
         case ZOOM_TASK_CONNECT:
             break;
@@ -235,6 +238,12 @@ void ZOOM_connection_remove_tasks(ZOOM_connection c)
         ZOOM_connection_remove_task(c);
 }
 
+static void odr_wrbuf_write(ODR o, void *handle, int type,
+                            const char *buf, int len)
+{
+    WRBUF w = (WRBUF) handle;
+    wrbuf_write(w, buf, len);
+}
 
 ZOOM_API(ZOOM_connection)
     ZOOM_connection_create(ZOOM_options options)
@@ -265,9 +274,9 @@ ZOOM_API(ZOOM_connection)
     c->options = ZOOM_options_create_with_parent(options);
 
     c->host_port = 0;
-    c->path = 0;
     c->proxy = 0;
-    
+    c->tproxy = 0;
+
     c->charset = c->lang = 0;
 
     c->cookie_out = 0;
@@ -278,6 +287,7 @@ ZOOM_API(ZOOM_connection)
     c->user = 0;
     c->group = 0;
     c->password = 0;
+    c->url_authentication = 0;
 
     c->maximum_record_size = 0;
     c->preferred_message_size = 0;
@@ -285,6 +295,7 @@ ZOOM_API(ZOOM_connection)
     c->odr_in = odr_createmem(ODR_DECODE);
     c->odr_out = odr_createmem(ODR_ENCODE);
     c->odr_print = 0;
+    c->odr_save = 0;
 
     c->async = 0;
     c->support_named_resultsets = 0;
@@ -295,9 +306,23 @@ ZOOM_API(ZOOM_connection)
 
     c->sru_version = 0;
     c->no_redirects = 0;
+    c->saveAPDU_wrbuf = 0;
     return c;
 }
 
+ZOOM_API(void) ZOOM_connection_save_apdu_wrbuf(ZOOM_connection c, WRBUF w)
+{
+    if (c->odr_save)
+    {
+        odr_destroy(c->odr_save);
+        c->odr_save = 0;
+    }
+    if (w)
+    {
+        c->odr_save = odr_createmem(ODR_PRINT);
+        odr_set_stream(c->odr_save, w, odr_wrbuf_write, 0);
+    }
+}
 
 /* set database names. Take local databases (if set); otherwise
    take databases given in ZURL (if set); otherwise use Default */
@@ -306,17 +331,10 @@ char **ZOOM_connection_get_databases(ZOOM_connection con, ZOOM_options options,
 {
     char **databaseNames;
     const char *cp = ZOOM_options_get(options, "databaseName");
-    
+
     if ((!cp || !*cp) && con->host_port)
-    {
-        if (strncmp(con->host_port, "unix:", 5) == 0)
-            cp = strchr(con->host_port+5, ':');
-        else
-            cp = strchr(con->host_port, '/');
-        if (cp)
-            cp++;
-    }
-    if (!cp)
+        cs_get_host_args(con->host_port, &cp);
+    if (!cp || !*cp)
         cp = "Default";
     nmem_strsplit(odr_getmem(odr), "+", cp,  &databaseNames, num);
     return databaseNames;
@@ -351,7 +369,6 @@ ZOOM_API(void)
                             const char *host, int portnum)
 {
     const char *val;
-    ZOOM_task task;
 
     initlog();
 
@@ -390,6 +407,15 @@ ZOOM_API(void)
         c->proxy = xstrdup(val);
     }
 
+    xfree(c->tproxy);
+    c->tproxy = 0;
+    val = ZOOM_options_get(c->options, "tproxy");
+    if (val && *val)
+    {
+        yaz_log(c->log_details, "%p ZOOM_connection_connect tproxy=%s", c, val);
+        c->tproxy = xstrdup(val);
+    }
+
     xfree(c->charset);
     c->charset = 0;
     val = ZOOM_options_get(c->options, "charset");
@@ -420,7 +446,7 @@ ZOOM_API(void)
         }
         else
             c->host_port = xstrdup(host);
-    }        
+    }
 
     {
         /*
@@ -436,20 +462,22 @@ ZOOM_API(void)
         char *pcomma;
         char *pequals;
         while ((pcomma = strchr(remainder, ',')) != 0 &&
-               (pcolon == 0 || pcomma < pcolon)) {
+               (pcolon == 0 || pcomma < pcolon))
+        {
             *pcomma = '\0';
-            if ((pequals = strchr(remainder, '=')) != 0) {
+            if ((pequals = strchr(remainder, '=')) != 0)
+            {
                 *pequals = '\0';
-                /*printf("# setting '%s'='%s'\n", remainder, pequals+1);*/
                 ZOOM_connection_option_set(c, remainder, pequals+1);
             }
             remainder = pcomma+1;
         }
 
-        if (remainder != c->host_port) {
+        if (remainder != c->host_port)
+        {
+            remainder = xstrdup(remainder);
             xfree(c->host_port);
-            c->host_port = xstrdup(remainder);
-            /*printf("# reset hp='%s'\n", remainder);*/
+            c->host_port = remainder;
         }
     }
 
@@ -466,7 +494,7 @@ ZOOM_API(void)
     c->cookie_out = 0;
     val = ZOOM_options_get(c->options, "cookie");
     if (val && *val)
-    { 
+    {
         yaz_log(c->log_details, "%p ZOOM_connection_connect cookie=%s", c, val);
         c->cookie_out = xstrdup(val);
     }
@@ -498,19 +526,31 @@ ZOOM_API(void)
     val = ZOOM_options_get(c->options, "password");
     if (!val)
         val = ZOOM_options_get(c->options, "pass");
-
     if (val && *val)
         c->password = xstrdup(val);
-    
+
+    val = ZOOM_options_get(c->options, "authenticationMode");
+    if (val && !strcmp(val, "url"))
+        c->url_authentication = 1;
+    else
+        c->url_authentication = 0;
+
     c->maximum_record_size =
-        ZOOM_options_get_int(c->options, "maximumRecordSize", 1024*1024);
+        ZOOM_options_get_int(c->options, "maximumRecordSize", 64*1024*1024);
     c->preferred_message_size =
-        ZOOM_options_get_int(c->options, "preferredMessageSize", 1024*1024);
+        ZOOM_options_get_int(c->options, "preferredMessageSize", 64*1024*1024);
 
     c->async = ZOOM_options_get_bool(c->options, "async", 0);
+
+    if (c->sru_mode == zoom_sru_error)
+    {
+        ZOOM_set_error(c, ZOOM_ERROR_UNSUPPORTED_PROTOCOL, val);
+        ZOOM_connection_remove_tasks(c);
+        return;
+    }
+
     yaz_log(c->log_details, "%p ZOOM_connection_connect async=%d", c, c->async);
-    task = ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
+    ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
 
     if (!c->async)
     {
@@ -521,8 +561,6 @@ ZOOM_API(void)
 
 ZOOM_API(void) ZOOM_resultset_release(ZOOM_resultset r)
 {
-#if ZOOM_RESULT_LISTS
-#else
     if (r->connection)
     {
         /* remove ourselves from the resultsets in connection */
@@ -539,42 +577,28 @@ ZOOM_API(void) ZOOM_resultset_release(ZOOM_resultset r)
         }
         r->connection = 0;
     }
-#endif
 }
 
 ZOOM_API(void)
     ZOOM_connection_destroy(ZOOM_connection c)
 {
-#if ZOOM_RESULT_LISTS
-    ZOOM_resultsets list;
-#else
     ZOOM_resultset r;
-#endif
     if (!c)
         return;
     yaz_log(c->log_api, "%p ZOOM_connection_destroy", c);
     if (c->cs)
         cs_close(c->cs);
 
-#if ZOOM_RESULT_LISTS
-    // Remove the connection's usage of resultsets
-    list = c->resultsets;
-    while (list) {
-        ZOOM_resultsets removed = list;
-        ZOOM_resultset_destroy(list->resultset);
-        list = list->next;
-        xfree(removed);
-    }
-#else
     for (r = c->resultsets; r; r = r->next)
         r->connection = 0;
-#endif
 
     xfree(c->buf_in);
     xfree(c->addinfo);
     xfree(c->diagset);
     odr_destroy(c->odr_in);
     odr_destroy(c->odr_out);
+    if (c->odr_save)
+        odr_destroy(c->odr_save);
     if (c->odr_print)
     {
         odr_setprint(c->odr_print, 0); /* prevent destroy from fclose'ing */
@@ -584,8 +608,8 @@ ZOOM_API(void)
     ZOOM_connection_remove_tasks(c);
     ZOOM_connection_remove_events(c);
     xfree(c->host_port);
-    xfree(c->path);
     xfree(c->proxy);
+    xfree(c->tproxy);
     xfree(c->charset);
     xfree(c->lang);
     xfree(c->cookie_out);
@@ -595,6 +619,7 @@ ZOOM_API(void)
     xfree(c->group);
     xfree(c->password);
     xfree(c->sru_version);
+    wrbuf_destroy(c->saveAPDU_wrbuf);
     xfree(c);
 }
 
@@ -644,7 +669,6 @@ ZOOM_resultset ZOOM_resultset_create(void)
     r->odr = odr_createmem(ODR_ENCODE);
     r->piggyback = 1;
     r->setname = 0;
-    r->schema = 0;
     r->step = 0;
     for (i = 0; i<RECORD_HASH_SIZE; i++)
         r->record_hash[i] = 0;
@@ -688,17 +712,14 @@ ZOOM_API(ZOOM_resultset)
     ZOOM_task task;
     const char *cp;
     int start, count;
-    const char *syntax, *elementSetName;
-#if ZOOM_RESULT_LISTS
-    ZOOM_resultsets set;
-#endif
+    const char *syntax, *elementSetName, *schema;
 
     yaz_log(c->log_api, "%p ZOOM_connection_search set %p query %p", c, r, q);
     r->r_sort_spec = ZOOM_query_get_sortspec(q);
     r->query = q;
 
     r->options = ZOOM_options_create_with_parent(c->options);
-    
+
     start = ZOOM_options_get_int(r->options, "start", 0);
     count = ZOOM_options_get_int(r->options, "count", 0);
     {
@@ -711,26 +732,13 @@ ZOOM_API(ZOOM_resultset)
     cp = ZOOM_options_get(r->options, "setname");
     if (cp)
         r->setname = xstrdup(cp);
-    cp = ZOOM_options_get(r->options, "schema");
-    if (cp)
-        r->schema = xstrdup(cp);
 
     r->databaseNames = ZOOM_connection_get_databases(c, c->options, &r->num_databaseNames,
                                          r->odr);
-    
-    r->connection = c;
 
-#if ZOOM_RESULT_LISTS
-    yaz_log(log_details, "%p ZOOM_connection_search: Adding new resultset (%p) to resultsets (%p) ", c, r, c->resultsets);
-    set = xmalloc(sizeof(*set));
-    ZOOM_resultset_addref(r);
-    set->resultset = r;
-    set->next = c->resultsets;
-    c->resultsets = set;
-#else
+    r->connection = c;
     r->next = c->resultsets;
     c->resultsets = r;
-#endif
     if (c->host_port && c->proto == PROTO_HTTP)
     {
         if (!c->cs)
@@ -751,12 +759,14 @@ ZOOM_API(ZOOM_resultset)
     task->u.search.count = count;
     task->u.search.recv_search_fired = 0;
 
-    syntax = ZOOM_options_get(r->options, "preferredRecordSyntax"); 
+    syntax = ZOOM_options_get(r->options, "preferredRecordSyntax");
     task->u.search.syntax = syntax ? xstrdup(syntax) : 0;
     elementSetName = ZOOM_options_get(r->options, "elementSetName");
-    task->u.search.elementSetName = elementSetName 
-        ? xstrdup(elementSetName) : 0;
-   
+    task->u.search.elementSetName = elementSetName ?
+        xstrdup(elementSetName) : 0;
+    schema = ZOOM_options_get(r->options, "schema");
+    task->u.search.schema = schema ? xstrdup(schema) : 0;
+
     ZOOM_resultset_addref(r);
 
     ZOOM_query_addref(q);
@@ -807,13 +817,13 @@ ZOOM_API(int)
             c->reconnect_ok = 1;
         }
     }
-    
+
     ZOOM_resultset_cache_reset(r);
     task = ZOOM_connection_add_task(c, ZOOM_TASK_SORT);
     task->u.sort.resultset = r;
     task->u.sort.q = newq;
 
-    ZOOM_resultset_addref(r);  
+    ZOOM_resultset_addref(r);
 
     if (!c->async)
     {
@@ -849,7 +859,6 @@ static void resultset_destroy(ZOOM_resultset r)
         ZOOM_options_destroy(r->options);
         odr_destroy(r->odr);
         xfree(r->setname);
-        xfree(r->schema);
         yaz_mutex_destroy(&r->mutex);
 #if SHPTR
         YAZ_SHPTR_DEC(r->record_wrbuf, wrbuf_destroy);
@@ -919,21 +928,14 @@ static void ZOOM_resultset_retrieve(ZOOM_resultset r,
     task->u.retrieve.start = start;
     task->u.retrieve.count = count;
 
-    syntax = ZOOM_options_get(r->options, "preferredRecordSyntax"); 
+    syntax = ZOOM_options_get(r->options, "preferredRecordSyntax");
     task->u.retrieve.syntax = syntax ? xstrdup(syntax) : 0;
     elementSetName = ZOOM_options_get(r->options, "elementSetName");
-    task->u.retrieve.elementSetName = elementSetName 
+    task->u.retrieve.elementSetName = elementSetName
         ? xstrdup(elementSetName) : 0;
 
     cp = ZOOM_options_get(r->options, "schema");
-    if (cp)
-    {
-        if (!r->schema || strcmp(r->schema, cp))
-        {
-            xfree(r->schema);
-            r->schema = xstrdup(cp);
-        }
-    }
+    task->u.retrieve.schema = cp ? xstrdup(cp) : 0;
 
     ZOOM_resultset_addref(r);
 
@@ -981,6 +983,15 @@ ZOOM_API(ZOOM_facet_field)
     return 0;
 }
 
+ZOOM_API(ZOOM_facet_field)
+    ZOOM_resultset_get_facet_field_by_index(ZOOM_resultset r, int index) {
+    int num = r->num_facets;
+    ZOOM_facet_field *facets = r->facets;
+    if (index >= 0 && index < num) {
+        return facets[index];
+    }
+    return 0;
+}
 
 ZOOM_API(ZOOM_facet_field *)
     ZOOM_resultset_facets(ZOOM_resultset r)
@@ -989,7 +1000,7 @@ ZOOM_API(ZOOM_facet_field *)
 }
 
 ZOOM_API(const char**)
-    ZOOM_resultset_facet_names(ZOOM_resultset r)
+    ZOOM_resultset_facets_names(ZOOM_resultset r)
 {
     return (const char **) r->facets_names;
 }
@@ -1017,7 +1028,7 @@ static void get_cert(ZOOM_connection c)
 {
     char *cert_buf;
     int cert_len;
-    
+
     if (cs_get_peer_certificate_x509(c->cs, &cert_buf, &cert_len))
     {
         ZOOM_connection_option_setl(c, "sslPeerCert",
@@ -1027,45 +1038,26 @@ static void get_cert(ZOOM_connection c)
 }
 
 static zoom_ret do_connect_host(ZOOM_connection c,
-                                const char *effective_host,
                                 const char *logical_url);
 
 static zoom_ret do_connect(ZOOM_connection c)
 {
-    const char *effective_host;
-
-    if (c->proxy)
-        effective_host = c->proxy;
-    else
-        effective_host = c->host_port;
-    return do_connect_host(c, effective_host, c->host_port);
+    return do_connect_host(c, c->host_port);
 }
 
-static zoom_ret do_connect_host(ZOOM_connection c, const char *effective_host,
-    const char *logical_url)
+static zoom_ret do_connect_host(ZOOM_connection c, const char *logical_url)
 {
     void *add;
 
-    yaz_log(c->log_details, "%p do_connect effective_host=%s", c, effective_host);
-
     if (c->cs)
         cs_close(c->cs);
-    c->cs = cs_create_host(effective_host, 0, &add);
+    c->cs = cs_create_host_proxy(logical_url, 0, &add,
+                                 c->tproxy ? c->tproxy : c->proxy);
 
     if (c->cs && c->cs->protocol == PROTO_HTTP)
     {
 #if YAZ_HAVE_XML2
-        if (logical_url)
-        {
-            const char *db = 0;
-            
-            c->proto = PROTO_HTTP;
-            cs_get_host_args(logical_url, &db);
-            xfree(c->path);
-            
-            c->path = xmalloc(strlen(db) * 3 + 2);
-            yaz_encode_sru_dbpath_buf(c->path, db);
-        }
+        c->proto = PROTO_HTTP;
 #else
         ZOOM_set_error(c, ZOOM_ERROR_UNSUPPORTED_PROTOCOL, "SRW");
         ZOOM_connection_close(c);
@@ -1101,7 +1093,7 @@ static zoom_ret do_connect_host(ZOOM_connection c, const char *effective_host,
             if (c->cs->io_pending & CS_WANT_READ)
                 mask += ZOOM_SELECT_READ;
             ZOOM_connection_set_mask(c, mask);
-            c->state = STATE_CONNECTING; 
+            c->state = STATE_CONNECTING;
             return zoom_pending;
         }
     }
@@ -1111,18 +1103,20 @@ static zoom_ret do_connect_host(ZOOM_connection c, const char *effective_host,
 }
 
 /* returns 1 if PDU was sent OK (still pending )
-   0 if PDU was not sent OK (nothing to wait for) 
+   0 if PDU was not sent OK (nothing to wait for)
 */
 
 ZOOM_API(ZOOM_record)
     ZOOM_resultset_record_immediate(ZOOM_resultset s,size_t pos)
 {
     const char *syntax =
-        ZOOM_options_get(s->options, "preferredRecordSyntax"); 
+        ZOOM_options_get(s->options, "preferredRecordSyntax");
     const char *elementSetName =
         ZOOM_options_get(s->options, "elementSetName");
+    const char *schema =
+        ZOOM_options_get(s->options, "schema");
 
-    return ZOOM_record_cache_lookup(s, pos, syntax, elementSetName);
+    return ZOOM_record_cache_lookup(s, pos, syntax, elementSetName, schema);
 }
 
 ZOOM_API(ZOOM_record)
@@ -1186,7 +1180,7 @@ ZOOM_API(ZOOM_scanset)
     {
         ZOOM_task task = ZOOM_connection_add_task(c, ZOOM_TASK_SCAN);
         task->u.scan.scan = scan;
-        
+
         (scan->refcount)++;
         if (!c->async)
         {
@@ -1208,7 +1202,7 @@ ZOOM_API(void)
         ZOOM_query_destroy(scan->query);
 
         odr_destroy(scan->odr);
-        
+
         ZOOM_options_destroy(scan->options);
         xfree(scan);
     }
@@ -1222,10 +1216,10 @@ static zoom_ret send_package(ZOOM_connection c)
     if (!c->tasks)
         return zoom_complete;
     assert (c->tasks->which == ZOOM_TASK_PACKAGE);
-    
+
     event = ZOOM_Event_create(ZOOM_EVENT_SEND_APDU);
     ZOOM_connection_put_event(c, event);
-    
+
     c->buf_out = c->tasks->u.package->buf_out;
     c->len_out = c->tasks->u.package->len_out;
 
@@ -1251,7 +1245,7 @@ static void ZOOM_scanset_term_x(ZOOM_scanset scan, size_t pos,
                                 const char **disp_term, size_t *disp_len)
 {
     size_t noent = ZOOM_scanset_size(scan);
-    
+
     *value_term = 0;
     *value_len = 0;
 
@@ -1267,7 +1261,7 @@ static void ZOOM_scanset_term_x(ZOOM_scanset scan, size_t pos,
         if (res->entries->entries[pos]->which == Z_Entry_termInfo)
         {
             Z_TermInfo *t = res->entries->entries[pos]->u.termInfo;
-            
+
             *value_term = (const char *) t->term->u.general->buf;
             *value_len = t->term->u.general->len;
             if (t->displayTerm)
@@ -1313,7 +1307,7 @@ ZOOM_API(const char *)
 
     ZOOM_scanset_term_x(scan, pos, occ, &value_term, &value_len,
                         &disp_term, &disp_len);
-    
+
     *len = value_len;
     return value_term;
 }
@@ -1329,7 +1323,7 @@ ZOOM_API(const char *)
 
     ZOOM_scanset_term_x(scan, pos, occ, &value_term, &value_len,
                         &disp_term, &disp_len);
-    
+
     *len = disp_len;
     return disp_term;
 }
@@ -1372,7 +1366,7 @@ ZOOM_API(void)
     {
         odr_destroy(p->odr_out);
         xfree(p->buf_out);
-        
+
         ZOOM_options_destroy(p->options);
         xfree(p);
     }
@@ -1458,7 +1452,7 @@ ZOOM_API(int)
             ret = send_package(c);
             break;
         case ZOOM_TASK_SORT:
-            c->tasks->u.sort.resultset->r_sort_spec = 
+            c->tasks->u.sort.resultset->r_sort_spec =
                 ZOOM_query_get_sortspec(c->tasks->u.sort.q);
             ret = send_Z3950_sort(c, c->tasks->u.sort.resultset);
             break;
@@ -1483,39 +1477,14 @@ ZOOM_API(int)
 }
 
 #if YAZ_HAVE_XML2
-static Z_GDU *get_HTTP_Request_url(ODR odr, const char *url)
-{
-    Z_GDU *p = z_get_HTTP_Request(odr);
-    const char *host = url;
-    const char *cp0 = strstr(host, "://");
-    const char *cp1 = 0;
-    if (cp0)
-        cp0 = cp0+3;
-    else
-        cp0 = host;
-    
-    cp1 = strchr(cp0, '/');
-    if (!cp1)
-        cp1 = cp0 + strlen(cp0);
-    
-    if (cp0 && cp1)
-    {
-        char *h = (char*) odr_malloc(odr, cp1 - cp0 + 1);
-        memcpy (h, cp0, cp1 - cp0);
-        h[cp1-cp0] = '\0';
-        z_HTTP_header_add(odr, &p->u.HTTP_Request->headers, "Host", h);
-    }
-    p->u.HTTP_Request->path = odr_strdup(odr, *cp1 ? cp1 : "/");
-    return p;
-}
 
 static zoom_ret send_HTTP_redirect(ZOOM_connection c, const char *uri,
                                   Z_HTTP_Response *cookie_hres)
 {
     struct Z_HTTP_Header *h;
-    Z_GDU *gdu = get_HTTP_Request_url(c->odr_out, uri);
-    char *combined_cookies;
+    char *combined_cookies = 0;
     int combined_cookies_len = 0;
+    Z_GDU *gdu = z_get_HTTP_Request_uri(c->odr_out, uri, 0, c->proxy ? 1 : 0);
 
     gdu->u.HTTP_Request->method = odr_strdup(c->odr_out, "GET");
     z_HTTP_header_add(c->odr_out, &gdu->u.HTTP_Request->headers, "Accept",
@@ -1551,13 +1520,26 @@ static zoom_ret send_HTTP_redirect(ZOOM_connection c, const char *uri,
         z_HTTP_header_add_basic_auth(c->odr_out, &gdu->u.HTTP_Request->headers,
                                      c->user, c->password);
     }
-    if (!z_GDU(c->odr_out, &gdu, 0, 0))
+    return ZOOM_send_GDU(c, gdu);
+}
+
+zoom_ret ZOOM_send_GDU(ZOOM_connection c, Z_GDU *gdu)
+{
+    ZOOM_Event event;
+
+    int r = z_GDU(c->odr_out, &gdu, 0, 0);
+    if (!r)
         return zoom_complete;
     if (c->odr_print)
         z_GDU(c->odr_print, &gdu, 0, 0);
+    if (c->odr_save)
+        z_GDU(c->odr_save, &gdu, 0, 0);
     c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
-
     odr_reset(c->odr_out);
+
+    event = ZOOM_Event_create(ZOOM_EVENT_SEND_APDU);
+    ZOOM_connection_put_event(c, event);
+
     return ZOOM_send_buf(c);
 }
 
@@ -1574,14 +1556,14 @@ static void handle_http(ZOOM_connection c, Z_HTTP_Response *hres)
 {
     zoom_ret cret = zoom_complete;
     int ret = -1;
-    const char *addinfo = 0;
+    char *addinfo = 0;
     const char *connection_head = z_HTTP_header_lookup(hres->headers,
                                                        "Connection");
     const char *location;
 
     ZOOM_connection_set_mask(c, 0);
     yaz_log(c->log_details, "%p handle_http", c);
-    
+
     if ((hres->code == 301 || hres->code == 302) && c->sru_mode == zoom_sru_get
         && (location = z_HTTP_header_lookup(hres->headers, "Location")))
     {
@@ -1596,16 +1578,15 @@ static void handle_http(ZOOM_connection c, Z_HTTP_Response *hres)
         {
             /* since redirect may change host we just reconnect. A smarter
                implementation might check whether it's the same server */
-            do_connect_host(c, location, 0);
+            do_connect_host(c, location);
             send_HTTP_redirect(c, location, hres);
             /* we're OK for now. Operation is not really complete */
-            ret = 0;
-            cret = zoom_pending;
+            return;
         }
     }
-    else 
-    {  
-        ret = ZOOM_handle_sru(c, hres, &cret);
+    else
+    {
+        ret = ZOOM_handle_sru(c, hres, &cret, &addinfo);
         if (ret == 0)
         {
             if (c->no_redirects) /* end of redirect. change hosts again */
@@ -1618,7 +1599,11 @@ static void handle_http(ZOOM_connection c, Z_HTTP_Response *hres)
         if (hres->code != 200)
             ZOOM_set_HTTP_error(c, hres->code, 0, 0);
         else
+        {
+            yaz_log(YLOG_LOG, "set error... addinfo=%s", addinfo ?
+                    addinfo : "NULL");
             ZOOM_set_error(c, ZOOM_ERROR_DECODE, addinfo);
+        }
         ZOOM_connection_close(c);
     }
     if (cret == zoom_complete)
@@ -1650,6 +1635,8 @@ static void handle_http(ZOOM_connection c, Z_HTTP_Response *hres)
                 c->reconnect_ok = 0;
             }
         }
+        else
+            c->reconnect_ok = 1; /* if the server closes anyway */
     }
 }
 #endif
@@ -1658,10 +1645,10 @@ static int do_read(ZOOM_connection c)
 {
     int r, more;
     ZOOM_Event event;
-    
+
     event = ZOOM_Event_create(ZOOM_EVENT_RECV_DATA);
     ZOOM_connection_put_event(c, event);
-    
+
     r = cs_get(c->cs, &c->buf_in, &c->len_in);
     more = cs_more(c->cs);
     yaz_log(c->log_details, "%p do_read len=%d more=%d", c, r, more);
@@ -1708,6 +1695,8 @@ static int do_read(ZOOM_connection c)
         {
             if (c->odr_print)
                 z_GDU(c->odr_print, &gdu, 0, 0);
+            if (c->odr_save)
+                z_GDU(c->odr_save, &gdu, 0, 0);
             if (gdu->which == Z_GDU_Z3950)
                 ZOOM_handle_Z3950_apdu(c, gdu->u.z3950);
             else if (gdu->which == Z_GDU_HTTP_Response)
@@ -1720,7 +1709,6 @@ static int do_read(ZOOM_connection c)
 #endif
             }
         }
-        c->reconnect_ok = 0;
     }
     return 1;
 }
@@ -1729,7 +1717,7 @@ static zoom_ret do_write_ex(ZOOM_connection c, char *buf_out, int len_out)
 {
     int r;
     ZOOM_Event event;
-    
+
     event = ZOOM_Event_create(ZOOM_EVENT_SEND_DATA);
     ZOOM_connection_put_event(c, event);
 
@@ -1749,7 +1737,7 @@ static zoom_ret do_write_ex(ZOOM_connection c, char *buf_out, int len_out)
         return zoom_complete;
     }
     else if (r == 1)
-    {    
+    {
         int mask = ZOOM_SELECT_EXCEPT;
         if (c->cs->io_pending & CS_WANT_WRITE)
             mask += ZOOM_SELECT_WRITE;
@@ -1777,20 +1765,56 @@ zoom_ret ZOOM_send_buf(ZOOM_connection c)
 ZOOM_API(const char *)
     ZOOM_connection_option_get(ZOOM_connection c, const char *key)
 {
-    return ZOOM_options_get(c->options, key);
+    if (!strcmp(key, "APDU"))
+    {
+        return c->saveAPDU_wrbuf ? wrbuf_cstr(c->saveAPDU_wrbuf) : "";
+    }
+    else
+        return ZOOM_options_get(c->options, key);
 }
 
 ZOOM_API(const char *)
     ZOOM_connection_option_getl(ZOOM_connection c, const char *key, int *lenp)
 {
-    return ZOOM_options_getl(c->options, key, lenp);
+    if (!strcmp(key, "APDU"))
+    {
+        if (c->saveAPDU_wrbuf)
+        {
+            *lenp = wrbuf_len(c->saveAPDU_wrbuf);
+            return wrbuf_cstr(c->saveAPDU_wrbuf);
+        }
+        else
+        {
+            *lenp = 0;
+            return "";
+        }
+    }
+    else
+        return ZOOM_options_getl(c->options, key, lenp);
 }
 
 ZOOM_API(void)
     ZOOM_connection_option_set(ZOOM_connection c, const char *key,
                                const char *val)
 {
-    ZOOM_options_set(c->options, key, val);
+    if (!strcmp(key, "saveAPDU"))
+    {
+        if (val && strcmp(val, "0"))
+        {
+            if (!c->saveAPDU_wrbuf)
+                c->saveAPDU_wrbuf = wrbuf_alloc();
+            else
+                wrbuf_rewind(c->saveAPDU_wrbuf);
+        }
+        else
+        {
+            wrbuf_destroy(c->saveAPDU_wrbuf);
+            c->saveAPDU_wrbuf = 0;
+        }
+        ZOOM_connection_save_apdu_wrbuf(c, c->saveAPDU_wrbuf);
+    }
+    else
+        ZOOM_options_set(c->options, key, val);
 }
 
 ZOOM_API(void)
@@ -1881,6 +1905,12 @@ ZOOM_API(const char *)
         return "CCL configuration error";
     case ZOOM_ERROR_CCL_PARSE:
         return "CCL parsing error";
+    case ZOOM_ERROR_ES_INVALID_ACTION:
+        return "Extended Service. invalid action";
+    case ZOOM_ERROR_ES_INVALID_VERSION:
+        return "Extended Service. invalid version";
+    case ZOOM_ERROR_ES_INVALID_SYNTAX:
+        return "Extended Service. invalid syntax";
     default:
         return diagbib1_str(error);
     }
@@ -1924,7 +1954,7 @@ static void ZOOM_connection_do_io(ZOOM_connection c, int mask)
     int r = cs_look(c->cs);
     yaz_log(c->log_details, "%p ZOOM_connection_do_io mask=%d cs_look=%d",
             c, mask, r);
-    
+
     if (r == CS_NONE)
     {
         event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
@@ -2040,7 +2070,7 @@ ZOOM_API(int)
     int i;
 
     yaz_log(log_details0, "ZOOM_process_event(no=%d,cs=%p)", no, cs);
-    
+
     for (i = 0; i<no; i++)
     {
         ZOOM_connection c = cs[i];
@@ -2069,7 +2099,7 @@ ZOOM_API(int) ZOOM_connection_set_mask(ZOOM_connection c, int mask)
 {
     c->mask = mask;
     if (!c->cs)
-        return -1; 
+        return -1;
     return 0;
 }