Fix case for http:close and SRU step (bug #3484)
[yaz-moved-to-github.git] / src / zoom-c.c
index ea23a86..dd1698c 100644 (file)
@@ -1,8 +1,6 @@
-/*
- * Copyright (C) 1995-2007, Index Data ApS
+/* This file is part of the YAZ toolkit.
+ * Copyright (C) 1995-2010 Index Data
  * See the file LICENSE for details.
- *
- * $Id: zoom-c.c,v 1.136 2007-06-25 18:34:09 adam Exp $
  */
 /**
  * \file zoom-c.c
 #include <yaz/ccl.h>
 #include <yaz/query-charset.h>
 #include <yaz/copy_types.h>
+#include <yaz/snprintf.h>
+
+#include <yaz/shptr.h>
+
+#if SHPTR
+YAZ_SHPTR_TYPE(WRBUF)
+#endif
 
 static int log_api = 0;
 static int log_details = 0;
@@ -42,13 +47,30 @@ static zoom_ret ZOOM_connection_send_init(ZOOM_connection c);
 static zoom_ret do_write_ex(ZOOM_connection c, char *buf_out, int len_out);
 static char *cql2pqf(ZOOM_connection c, const char *cql);
 
+ZOOM_API(const char *) ZOOM_get_event_str(int event)
+{
+    static const char *ar[] = {
+        "NONE",
+        "CONNECT",
+        "SEND_DATA",
+        "RECV_DATA",
+        "TIMEOUT",
+        "UNKNOWN",
+        "SEND_APDU",
+        "RECV_APDU",
+        "RECV_RECORD",
+        "RECV_SEARCH",
+        "END"
+    };
+    return ar[event];
+}
 
 /*
  * This wrapper is just for logging failed lookups.  It would be nicer
  * if it could cause failure when a lookup fails, but that's hard.
  */
 static Odr_oid *zoom_yaz_str_to_z3950oid(ZOOM_connection c,
-                                     int oid_class, const char *str) {
+                                     oid_class oid_class, const char *str) {
     Odr_oid *res = yaz_string_to_oid_odr(yaz_oid_std(), oid_class, str,
                                      c->odr_out);
     if (res == 0)
@@ -173,12 +195,29 @@ static void set_dset_error(ZOOM_connection c, int error,
     }
 }
 
+static int uri_to_code(const char *uri)
+{
+    int code = 0;       
+    const char *cp;
+    if ((cp = strrchr(uri, '/')))
+        code = atoi(cp+1);
+    return code;
+}
+
 #if YAZ_HAVE_XML2
 static void set_HTTP_error(ZOOM_connection c, int error,
                            const char *addinfo, const char *addinfo2)
 {
     set_dset_error(c, error, "HTTP", addinfo, addinfo2);
 }
+
+static void set_SRU_error(ZOOM_connection c, Z_SRW_diagnostic *d)
+{
+    const char *uri = d->uri;
+    if (uri)
+        set_dset_error(c, uri_to_code(uri), uri, d->details, 0);
+}
+
 #endif
 
 
@@ -228,7 +267,7 @@ void ZOOM_connection_show_task(ZOOM_task task)
         yaz_log(YLOG_LOG, "connect p=%p", task);
         break;
     case ZOOM_TASK_SCAN:
-        yaz_log(YLOG_LOG, "scant p=%p", task);
+        yaz_log(YLOG_LOG, "scan p=%p", task);
         break;
     }
 }
@@ -316,8 +355,6 @@ void ZOOM_connection_remove_task(ZOOM_connection c)
     }
 }
 
-static int ZOOM_connection_exec_task(ZOOM_connection c);
-
 void ZOOM_connection_remove_tasks(ZOOM_connection c)
 {
     while (c->tasks)
@@ -373,6 +410,7 @@ ZOOM_API(ZOOM_connection)
 
     c->odr_in = odr_createmem(ODR_DECODE);
     c->odr_out = odr_createmem(ODR_ENCODE);
+    c->odr_print = 0;
 
     c->async = 0;
     c->support_named_resultsets = 0;
@@ -380,6 +418,9 @@ ZOOM_API(ZOOM_connection)
 
     c->m_queue_front = 0;
     c->m_queue_back = 0;
+
+    c->sru_version = 0;
+    c->no_redirects = 0;
     return c;
 }
 
@@ -444,6 +485,19 @@ ZOOM_API(void)
     set_ZOOM_error(c, ZOOM_ERROR_NONE, 0);
     ZOOM_connection_remove_tasks(c);
 
+    if (c->odr_print)
+    {
+        odr_setprint(c->odr_print, 0); /* prevent destroy from fclose'ing */
+        odr_destroy(c->odr_print);
+    }
+    if (ZOOM_options_get_bool(c->options, "apdulog", 0))
+    {
+        c->odr_print = odr_createmem(ODR_PRINT);
+        odr_setprint(c->odr_print, yaz_log_file());
+    }
+    else
+        c->odr_print = 0;
+
     if (c->cs)
     {
         yaz_log(log_details, "%p ZOOM_connection_connect reconnect ok", c);
@@ -526,6 +580,10 @@ ZOOM_API(void)
     val = ZOOM_options_get(c->options, "sru");
     c->sru_mode = get_sru_mode_from_string(val);
 
+    xfree(c->sru_version);
+    val = ZOOM_options_get(c->options, "sru_version");
+    c->sru_version = xstrdup(val ? val : "1.2");
+
     ZOOM_options_set(c->options, "host", c->host_port);
 
     xfree(c->cookie_out);
@@ -640,7 +698,7 @@ ZOOM_API(int)
     s->query_string = odr_strdup(s->odr, str);
 
     ext = (Z_External *) odr_malloc(s->odr, sizeof(*ext));
-    ext->direct_reference = odr_getoidbystr(s->odr, "1.2.840.10003.16.2");
+    ext->direct_reference = odr_oiddup(s->odr, yaz_oid_userinfo_cql);
     ext->indirect_reference = 0;
     ext->descriptor = 0;
     ext->which = Z_External_CQL;
@@ -738,20 +796,41 @@ static zoom_ret do_write(ZOOM_connection c);
 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(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_print)
+    {
+        odr_setprint(c->odr_print, 0); /* prevent destroy from fclose'ing */
+        odr_destroy(c->odr_print);
+    }
     ZOOM_options_destroy(c->options);
     ZOOM_connection_remove_tasks(c);
     ZOOM_connection_remove_events(c);
@@ -766,6 +845,7 @@ ZOOM_API(void)
     xfree(c->user);
     xfree(c->group);
     xfree(c->password);
+    xfree(c->sru_version);
     xfree(c);
 }
 
@@ -773,9 +853,11 @@ void ZOOM_resultset_addref(ZOOM_resultset r)
 {
     if (r)
     {
+        yaz_mutex_enter(r->mutex);
         (r->refcount)++;
         yaz_log(log_details, "%p ZOOM_resultset_addref count=%d",
                 r, r->refcount);
+        yaz_mutex_leave(r->mutex);
     }
 }
 
@@ -799,10 +881,16 @@ ZOOM_resultset ZOOM_resultset_create(void)
     r->r_sort_spec = 0;
     r->query = 0;
     r->connection = 0;
-    r->next = 0;
     r->databaseNames = 0;
     r->num_databaseNames = 0;
-    r->rpn_iconv = 0;
+    r->mutex = 0;
+    yaz_mutex_create(&r->mutex);
+#if SHPTR
+    {
+        WRBUF w = wrbuf_alloc();
+        YAZ_SHPTR_INIT(r->record_wrbuf, w);
+    }
+#endif
     return r;
 }
 
@@ -827,6 +915,9 @@ ZOOM_API(ZOOM_resultset)
     const char *cp;
     int start, count;
     const char *syntax, *elementSetName;
+#if ZOOM_RESULT_LISTS
+    ZOOM_resultsets set;
+#endif
 
     yaz_log(log_api, "%p ZOOM_connection_search set %p query %p", c, r, q);
     r->r_sort_spec = q->sort_spec;
@@ -834,13 +925,6 @@ ZOOM_API(ZOOM_resultset)
 
     r->options = ZOOM_options_create_with_parent(c->options);
     
-    {
-        const char *cp = ZOOM_options_get(r->options, "rpnCharset");
-        if (cp)
-            r->rpn_iconv = yaz_iconv_open(cp, "UTF-8");
-    }
-
-
     start = ZOOM_options_get_int(r->options, "start", 0);
     count = ZOOM_options_get_int(r->options, "count", 0);
     {
@@ -862,11 +946,17 @@ ZOOM_API(ZOOM_resultset)
     
     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->next = c->resultsets;
     c->resultsets = r;
-
-    
-
+#endif
     if (c->host_port && c->proto == PROTO_HTTP)
     {
         if (!c->cs)
@@ -885,6 +975,7 @@ ZOOM_API(ZOOM_resultset)
     task->u.search.resultset = r;
     task->u.search.start = start;
     task->u.search.count = count;
+    task->u.search.recv_search_fired = 0;
 
     syntax = ZOOM_options_get(r->options, "preferredRecordSyntax"); 
     task->u.search.syntax = syntax ? xstrdup(syntax) : 0;
@@ -959,6 +1050,23 @@ ZOOM_API(int)
     return 0;
 }
 
+static void ZOOM_record_release(ZOOM_record rec)
+{
+    if (!rec)
+        return;
+
+#if SHPTR
+    if (rec->record_wrbuf)
+        YAZ_SHPTR_DEC(rec->record_wrbuf, wrbuf_destroy);
+#else
+    if (rec->wrbuf)
+        wrbuf_destroy(rec->wrbuf);
+#endif
+
+    if (rec->odr)
+        odr_destroy(rec->odr);
+}
+
 ZOOM_API(void)
     ZOOM_resultset_cache_reset(ZOOM_resultset r)
 {
@@ -968,12 +1076,7 @@ ZOOM_API(void)
         ZOOM_record_cache rc;
         for (rc = r->record_hash[i]; rc; rc = rc->next)
         {
-            if (rc->rec.wrbuf_marc)
-                wrbuf_destroy(rc->rec.wrbuf_marc);
-            if (rc->rec.wrbuf_iconv)
-                wrbuf_destroy(rc->rec.wrbuf_iconv);
-            if (rc->rec.wrbuf_opac)
-                wrbuf_destroy(rc->rec.wrbuf_opac);
+            ZOOM_record_release(&rc->rec);
         }
         r->record_hash[i] = 0;
     }
@@ -989,13 +1092,18 @@ static void resultset_destroy(ZOOM_resultset r)
 {
     if (!r)
         return;
+    yaz_mutex_enter(r->mutex);
     (r->refcount)--;
     yaz_log(log_details, "%p ZOOM_resultset_destroy r=%p count=%d",
             r, r, r->refcount);
     if (r->refcount == 0)
     {
-        ZOOM_resultset_cache_reset(r);
+        yaz_mutex_leave(r->mutex);
 
+        yaz_log(log_details, "%p ZOOM_connection resultset_destroy: Deleting resultset (%p) ", r->connection, r);
+        ZOOM_resultset_cache_reset(r);
+#if ZOOM_RESULT_LISTS
+#else
         if (r->connection)
         {
             /* remove ourselves from the resultsets in connection */
@@ -1011,21 +1119,26 @@ static void resultset_destroy(ZOOM_resultset r)
                 rp = &(*rp)->next;
             }
         }
+#endif
         ZOOM_query_destroy(r->query);
         ZOOM_options_destroy(r->options);
-        if (r->rpn_iconv)
-            yaz_iconv_close(r->rpn_iconv);
         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);
+#endif
         xfree(r);
     }
+    else
+        yaz_mutex_leave(r->mutex);
 }
 
 ZOOM_API(size_t)
     ZOOM_resultset_size(ZOOM_resultset r)
 {
-    yaz_log(log_details, "ZOOM_resultset_size r=%p count=%d",
+    yaz_log(log_details, "ZOOM_resultset_size r=%p count=" ODR_INT_PRINTF,
             r, r->size);
     return r->size;
 }
@@ -1148,15 +1261,25 @@ 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)
 {
-    void *add;
     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);
+}
+
+static zoom_ret do_connect_host(ZOOM_connection c, const char *effective_host,
+    const char *logical_url)
+{
+    void *add;
 
     yaz_log(log_details, "%p do_connect effective_host=%s", c, effective_host);
 
@@ -1167,14 +1290,17 @@ static zoom_ret do_connect(ZOOM_connection c)
     if (c->cs && c->cs->protocol == PROTO_HTTP)
     {
 #if YAZ_HAVE_XML2
-        const char *path = 0;
-
-        c->proto = PROTO_HTTP;
-        cs_get_host_args(c->host_port, &path);
-        xfree(c->path);
-        c->path = (char*) xmalloc(strlen(path)+2);
-        c->path[0] = '/';
-        strcpy(c->path+1, path);
+        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);
+        }
 #else
         set_ZOOM_error(c, ZOOM_ERROR_UNSUPPORTED_PROTOCOL, "SRW");
         do_close(c);
@@ -1215,7 +1341,7 @@ static zoom_ret do_connect(ZOOM_connection c)
         }
     }
     c->state = STATE_IDLE;
-    set_ZOOM_error(c, ZOOM_ERROR_CONNECT, c->host_port);
+    set_ZOOM_error(c, ZOOM_ERROR_CONNECT, logical_url);
     return zoom_complete;
 }
 
@@ -1288,6 +1414,8 @@ static int encode_APDU(ZOOM_connection c, Z_APDU *a, ODR out)
         odr_reset(out);
         return -1;
     }
+    if (c->odr_print)
+        z_APDU(c->odr_print, &a, 0, 0);
     yaz_log(log_details, "%p encoding_APDU encoding OK", c);
     return 0;
 }
@@ -1316,7 +1444,6 @@ static zoom_ret ZOOM_connection_send_init(ZOOM_connection c)
     Z_InitRequest *ireq = apdu->u.initRequest;
     Z_IdAuthentication *auth = (Z_IdAuthentication *)
         odr_malloc(c->odr_out, sizeof(*auth));
-    char *version;
 
     ODR_MASK_SET(ireq->options, Z_Options_search);
     ODR_MASK_SET(ireq->options, Z_Options_present);
@@ -1329,11 +1456,10 @@ static zoom_ret ZOOM_connection_send_init(ZOOM_connection c)
     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_2);
     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_3);
     
-    /* Index Data's Z39.50 Implementor Id is 81 */
     ireq->implementationId =
         odr_prepend(c->odr_out,
                     ZOOM_options_get(c->options, "implementationId"),
-                    odr_prepend(c->odr_out, "81", ireq->implementationId));
+                    ireq->implementationId);
     
     ireq->implementationName = 
         odr_prepend(c->odr_out,
@@ -1341,14 +1467,10 @@ static zoom_ret ZOOM_connection_send_init(ZOOM_connection c)
                     odr_prepend(c->odr_out, "ZOOM-C",
                                 ireq->implementationName));
     
-    version = odr_strdup(c->odr_out, "$Revision: 1.136 $");
-    if (strlen(version) > 10)   /* check for unexpanded CVS strings */
-        version[strlen(version)-2] = '\0';
     ireq->implementationVersion = 
         odr_prepend(c->odr_out,
                     ZOOM_options_get(c->options, "implementationVersion"),
-                    odr_prepend(c->odr_out, &version[11],
-                                ireq->implementationVersion));
+                                ireq->implementationVersion);
     
     *ireq->maximumRecordSize = c->maximum_record_size;
     *ireq->preferredMessageSize = c->preferred_message_size;
@@ -1356,15 +1478,9 @@ static zoom_ret ZOOM_connection_send_init(ZOOM_connection c)
     if (c->group || c->password)
     {
         Z_IdPass *pass = (Z_IdPass *) odr_malloc(c->odr_out, sizeof(*pass));
-        pass->groupId = 0;
-        if (c->group)
-            pass->groupId = odr_strdup(c->odr_out, c->group);
-        pass->userId = 0;
-        if (c->user)
-            pass->userId = odr_strdup(c->odr_out, c->user);
-        pass->password = 0;
-        if (c->password)
-            pass->password = odr_strdup(c->odr_out, c->password);
+        pass->groupId = odr_strdup_null(c->odr_out, c->group);
+        pass->userId = odr_strdup_null(c->odr_out, c->user);
+        pass->password = odr_strdup_null(c->odr_out, c->password);
         auth->which = Z_IdAuthentication_idPass;
         auth->u.idPass = pass;
         ireq->idAuthentication = auth;
@@ -1405,8 +1521,13 @@ static zoom_ret send_srw(ZOOM_connection c, Z_SRW_PDU *sr)
 {
     Z_GDU *gdu;
     ZOOM_Event event;
-
-    gdu = z_get_HTTP_Request_host_path(c->odr_out, c->host_port, c->path);
+    const char *database =  ZOOM_options_get(c->options, "databaseName");
+    char *fdatabase = 0;
+    
+    if (database)
+        fdatabase = yaz_encode_sru_dbpath_odr(c->odr_out, database);
+    gdu = z_get_HTTP_Request_host_path(c->odr_out, c->host_port,
+                                       fdatabase ? fdatabase : c->path);
 
     if (c->sru_mode == zoom_sru_get)
     {
@@ -1422,6 +1543,8 @@ static zoom_ret send_srw(ZOOM_connection c, Z_SRW_PDU *sr)
     }
     if (!z_GDU(c->odr_out, &gdu, 0, 0))
         return zoom_complete;
+    if (c->odr_print)
+        z_GDU(c->odr_print, &gdu, 0, 0);
     c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
         
     event = ZOOM_Event_create(ZOOM_EVENT_SEND_APDU);
@@ -1432,6 +1555,16 @@ static zoom_ret send_srw(ZOOM_connection c, Z_SRW_PDU *sr)
 #endif
 
 #if YAZ_HAVE_XML2
+static Z_SRW_PDU *ZOOM_srw_get_pdu(ZOOM_connection c, int type)
+{
+    Z_SRW_PDU *sr = yaz_srw_get_pdu(c->odr_out, type, c->sru_version);
+    sr->username = c->user;
+    sr->password = c->password;
+    return sr;
+}
+#endif
+
+#if YAZ_HAVE_XML2
 static zoom_ret ZOOM_connection_srw_send_search(ZOOM_connection c)
 {
     int i;
@@ -1447,7 +1580,8 @@ static zoom_ret ZOOM_connection_srw_send_search(ZOOM_connection c)
     {
     case ZOOM_TASK_SEARCH:
         resultset = c->tasks->u.search.resultset;
-        resultset->setname = xstrdup("default");
+        if (!resultset->setname)
+            resultset->setname = xstrdup("default");
         ZOOM_options_set(resultset->options, "setname", resultset->setname);
         start = &c->tasks->u.search.start;
         count = &c->tasks->u.search.count;
@@ -1457,7 +1591,7 @@ static zoom_ret ZOOM_connection_srw_send_search(ZOOM_connection c)
 
         start = &c->tasks->u.retrieve.start;
         count = &c->tasks->u.retrieve.count;
-        
+
         if (*start >= resultset->size)
             return zoom_complete;
         if (*start + *count > resultset->size)
@@ -1488,8 +1622,7 @@ static zoom_ret ZOOM_connection_srw_send_search(ZOOM_connection c)
     }
     assert(resultset->query);
         
-    sr = yaz_srw_get(c->odr_out, Z_SRW_searchRetrieve_request);
-
+    sr = ZOOM_srw_get_pdu(c, Z_SRW_searchRetrieve_request);
     if (resultset->query->z_query->which == Z_Query_type_104
         && resultset->query->z_query->u.type_104->which == Z_External_CQL)
     {
@@ -1509,7 +1642,8 @@ static zoom_ret ZOOM_connection_srw_send_search(ZOOM_connection c)
     }
     sr->u.request->startRecord = odr_intdup(c->odr_out, *start + 1);
     sr->u.request->maximumRecords = odr_intdup(
-        c->odr_out, resultset->step>0 ? resultset->step : *count);
+        c->odr_out, (resultset->step > 0 && resultset->step < *count) ? 
+        resultset->step : *count);
     sr->u.request->recordSchema = resultset->schema;
     
     option_val = ZOOM_resultset_option_get(resultset, "recordPacking");
@@ -1517,8 +1651,7 @@ static zoom_ret ZOOM_connection_srw_send_search(ZOOM_connection c)
         sr->u.request->recordPacking = odr_strdup(c->odr_out, option_val);
 
     option_val = ZOOM_resultset_option_get(resultset, "extraArgs");
-    if (option_val)
-        sr->extra_args = odr_strdup(c->odr_out, option_val);
+    yaz_encode_sru_extra(sr, c->odr_out, option_val);
     return send_srw(c, sr);
 }
 #else
@@ -1569,12 +1702,30 @@ static zoom_ret ZOOM_connection_send_search(ZOOM_connection c)
         set_ZOOM_error(c, ZOOM_ERROR_INVALID_QUERY, 0);
         return zoom_complete;
     }
-    if (r->query->z_query->which == Z_Query_type_1 && r->rpn_iconv)
+    if (r->query->z_query->which == Z_Query_type_1 || 
+        r->query->z_query->which == Z_Query_type_101)
     {
-        search_req->query = yaz_copy_Z_Query(r->query->z_query, c->odr_out);
-        
-        yaz_query_charset_convert_rpnquery(search_req->query->u.type_1,
-                                           c->odr_out, r->rpn_iconv);
+        const char *cp = ZOOM_options_get(r->options, "rpnCharset");
+        if (cp)
+        {
+            yaz_iconv_t cd = yaz_iconv_open(cp, "UTF-8");
+            if (cd)
+            {
+                int r;
+                search_req->query = yaz_copy_Z_Query(search_req->query,
+                                                     c->odr_out);
+                
+                r = yaz_query_charset_convert_rpnquery_check(
+                    search_req->query->u.type_1,
+                    c->odr_out, cd);
+                yaz_iconv_close(cd);
+                if (r)
+                {  /* query could not be char converted */
+                    set_ZOOM_error(c, ZOOM_ERROR_INVALID_QUERY, 0);
+                    return zoom_complete;
+                }
+            }
+        }
     }
     search_req->databaseNames = r->databaseNames;
     search_req->num_databaseNames = r->num_databaseNames;
@@ -1640,6 +1791,15 @@ static zoom_ret ZOOM_connection_send_search(ZOOM_connection c)
                result sets on the server. */
             for (ord = 1; ; ord++)
             {
+#if ZOOM_RESULT_LISTS
+                ZOOM_resultsets rsp;
+                sprintf(setname, "%d", ord);
+                for (rsp = c->resultsets; rsp; rsp = rsp->next)
+                    if (rsp->resultset->setname && !strcmp(rsp->resultset->setname, setname))
+                        break;
+                if (!rsp)
+                    break;
+#else
                 ZOOM_resultset rp;
                 sprintf(setname, "%d", ord);
                 for (rp = c->resultsets; rp; rp = rp->next)
@@ -1647,6 +1807,8 @@ static zoom_ret ZOOM_connection_send_search(ZOOM_connection c)
                         break;
                 if (!rp)
                     break;
+#endif
+
             }
             r->setname = xstrdup(setname);
             yaz_log(log_details, "%p ZOOM_connection_send_search: allocating "
@@ -1707,13 +1869,21 @@ ZOOM_API(ZOOM_record)
     buf = odr_getbuf(odr_enc, &size, 0);
     
     nrec = (ZOOM_record) xmalloc(sizeof(*nrec));
+    yaz_log(log_details, "ZOOM_record create");
     nrec->odr = odr_createmem(ODR_DECODE);
-    nrec->wrbuf_marc = 0;
-    nrec->wrbuf_iconv = 0;
-    nrec->wrbuf_opac = 0;
+#if SHPTR
+    nrec->record_wrbuf = 0;
+#else
+    nrec->wrbuf = 0;
+#endif
     odr_setbuf(nrec->odr, buf, size, 0);
     z_NamePlusRecord(nrec->odr, &nrec->npr, 0, 0);
     
+    nrec->schema = odr_strdup_null(nrec->odr, srec->schema);
+    nrec->diag_uri = odr_strdup_null(nrec->odr, srec->diag_uri);
+    nrec->diag_message = odr_strdup_null(nrec->odr, srec->diag_message);
+    nrec->diag_details = odr_strdup_null(nrec->odr, srec->diag_details);
+    nrec->diag_set = odr_strdup_null(nrec->odr, srec->diag_set);
     odr_destroy(odr_enc);
     return nrec;
 }
@@ -1753,27 +1923,17 @@ ZOOM_API(ZOOM_record)
 ZOOM_API(void)
     ZOOM_record_destroy(ZOOM_record rec)
 {
-    if (!rec)
-        return;
-    if (rec->wrbuf_marc)
-        wrbuf_destroy(rec->wrbuf_marc);
-    if (rec->wrbuf_iconv)
-        wrbuf_destroy(rec->wrbuf_iconv);
-    if (rec->wrbuf_opac)
-        wrbuf_destroy(rec->wrbuf_opac);
-    odr_destroy(rec->odr);
+    ZOOM_record_release(rec);
+    yaz_log(log_details, "ZOOM_record destroy");
     xfree(rec);
 }
 
-static const char *marc_iconv_return(ZOOM_record rec, int marc_type,
-                                     int *len,
-                                     const char *buf, int sz,
-                                     const char *record_charset)
+
+static yaz_iconv_t iconv_create_charset(const char *record_charset)
 {
     char to[40];
     char from[40];
     yaz_iconv_t cd = 0;
-    yaz_marc_t mt = yaz_marc_create();
 
     *from = '\0';
     strcpy(to, "UTF-8");
@@ -1781,7 +1941,7 @@ static const char *marc_iconv_return(ZOOM_record rec, int marc_type,
     {
         /* Use "from,to" or just "from" */
         const char *cp = strchr(record_charset, ',');
-        int clen = strlen(record_charset);
+        size_t clen = strlen(record_charset);
         if (cp && cp[1])
         {
             strncpy( to, cp+1, sizeof(to)-1);
@@ -1795,74 +1955,73 @@ static const char *marc_iconv_return(ZOOM_record rec, int marc_type,
             strncpy(from, record_charset, clen);
         from[clen] = '\0';
     }
-
     if (*from && *to)
-    {
         cd = yaz_iconv_open(to, from);
-        yaz_marc_iconv(mt, cd);
-    }
+    return cd;
+}
+
+static const char *return_marc_record(WRBUF wrbuf,
+                                      int marc_type,
+                                      int *len,
+                                      const char *buf, int sz,
+                                      const char *record_charset)
+{
+    yaz_iconv_t cd = iconv_create_charset(record_charset);
+    yaz_marc_t mt = yaz_marc_create();
+    const char *ret_string = 0;
 
+    if (cd)
+        yaz_marc_iconv(mt, cd);
     yaz_marc_xml(mt, marc_type);
-    if (!rec->wrbuf_marc)
-        rec->wrbuf_marc = wrbuf_alloc();
-    wrbuf_rewind(rec->wrbuf_marc);
-    if (yaz_marc_decode_wrbuf(mt, buf, sz, rec->wrbuf_marc) > 0)
-    {
-        yaz_marc_destroy(mt);
-        if (cd)
-            yaz_iconv_close(cd);
+    if (yaz_marc_decode_wrbuf(mt, buf, sz, wrbuf) > 0)
+    {
         if (len)
-            *len = wrbuf_len(rec->wrbuf_marc);
-        return wrbuf_cstr(rec->wrbuf_marc);
+            *len = wrbuf_len(wrbuf);
+        ret_string = wrbuf_cstr(wrbuf);
     }
     yaz_marc_destroy(mt);
     if (cd)
         yaz_iconv_close(cd);
-    return 0;
+    return ret_string;
 }
 
-static const char *record_iconv_return(ZOOM_record rec, int *len,
-                                       const char *buf, int sz,
-                                       const char *record_charset)
+static const char *return_opac_record(WRBUF wrbuf,
+                                      int marc_type,
+                                      int *len,
+                                      Z_OPACRecord *opac_rec,
+                                      const char *record_charset)
 {
-    char to[40];
-    char from[40];
-    yaz_iconv_t cd = 0;
+    yaz_iconv_t cd = iconv_create_charset(record_charset);
+    yaz_marc_t mt = yaz_marc_create();
 
-    *from = '\0';
-    strcpy(to, "UTF-8");
+    if (cd)
+        yaz_marc_iconv(mt, cd);
+    yaz_marc_xml(mt, marc_type);
 
-    if (record_charset && *record_charset)
-    {
-        /* Use "from,to" or just "from" */
-        const char *cp = strchr(record_charset, ',');
-        int clen = strlen(record_charset);
-        if (cp && cp[1])
-        {
-            strncpy( to, cp+1, sizeof(to)-1);
-            to[sizeof(to)-1] = '\0';
-            clen = cp - record_charset;
-        }
-        if (clen > sizeof(from)-1)
-            clen = sizeof(from)-1;
-        
-        if (clen)
-            strncpy(from, record_charset, clen);
-        from[clen] = '\0';
-    }
+    yaz_opac_decode_wrbuf(mt, opac_rec, wrbuf);
+    yaz_marc_destroy(mt);
 
-    if (*from && *to && (cd = yaz_iconv_open(to, from)))
-    {
-        if (!rec->wrbuf_iconv)
-            rec->wrbuf_iconv = wrbuf_alloc();
+    if (cd)
+        yaz_iconv_close(cd);
+    if (len)
+        *len = wrbuf_len(wrbuf);
+    return wrbuf_cstr(wrbuf);
+}
 
-        wrbuf_rewind(rec->wrbuf_iconv);
+static const char *return_string_record(WRBUF wrbuf,
+                                        int *len,
+                                        const char *buf, int sz,
+                                        const char *record_charset)
+{
+    yaz_iconv_t cd = iconv_create_charset(record_charset);
 
-        wrbuf_iconv_write(rec->wrbuf_iconv, cd, buf, sz);
-        wrbuf_iconv_reset(rec->wrbuf_iconv, cd);
+    if (cd)
+    {
+        wrbuf_iconv_write(wrbuf, cd, buf, sz);
+        wrbuf_iconv_reset(wrbuf, cd);
 
-        buf = wrbuf_cstr(rec->wrbuf_iconv);
-        sz = wrbuf_len(rec->wrbuf_iconv);
+        buf = wrbuf_cstr(wrbuf);
+        sz = wrbuf_len(wrbuf);
         yaz_iconv_close(cd);
     }
     if (len)
@@ -1870,6 +2029,56 @@ static const char *record_iconv_return(ZOOM_record rec, int *len,
     return buf;
 }
 
+static const char *return_record_wrbuf(WRBUF wrbuf, int *len,
+                                       Z_NamePlusRecord *npr,
+                                       int marctype, const char *charset)
+{
+    Z_External *r = (Z_External *) npr->u.databaseRecord;
+    const Odr_oid *oid = r->direct_reference;
+
+    wrbuf_rewind(wrbuf);
+    /* render bibliographic record .. */
+    if (r->which == Z_External_OPAC)
+    {
+        return return_opac_record(wrbuf, marctype, len,
+                                  r->u.opac, charset);
+    }
+    if (r->which == Z_External_sutrs)
+        return return_string_record(wrbuf, len,
+                                    (char*) r->u.sutrs->buf,
+                                    r->u.sutrs->len,
+                                    charset);
+    else if (r->which == Z_External_octet)
+    {
+        if (yaz_oid_is_iso2709(oid))
+        {
+            const char *ret_buf = return_marc_record(
+                wrbuf, marctype, len,
+                (const char *) r->u.octet_aligned->buf,
+                r->u.octet_aligned->len,
+                charset);
+            if (ret_buf)
+                return ret_buf;
+            /* bad ISO2709. Return fail unless raw (ISO2709) is wanted */
+            if (marctype != YAZ_MARC_ISO2709)
+                return 0;
+        }
+        return return_string_record(wrbuf, len,
+                                    (const char *) r->u.octet_aligned->buf,
+                                    r->u.octet_aligned->len,
+                                    charset);
+    }
+    else if (r->which == Z_External_grs1)
+    {
+        yaz_display_grs1(wrbuf, r->u.grs1, 0);
+        return return_string_record(wrbuf, len,
+                                    wrbuf_buf(wrbuf),
+                                    wrbuf_len(wrbuf),
+                                    charset);
+    }
+    return 0;
+}
+    
 ZOOM_API(int)
     ZOOM_record_error(ZOOM_record rec, const char **cp,
                       const char **addinfo, const char **diagset)
@@ -1878,7 +2087,18 @@ ZOOM_API(int)
     
     if (!rec)
         return 0;
+
     npr = rec->npr;
+    if (rec->diag_uri)
+    {
+        if (cp)
+            *cp = rec->diag_message;
+        if (addinfo)
+            *addinfo = rec->diag_details;
+        if (diagset)
+            *diagset = rec->diag_set;
+        return uri_to_code(rec->diag_uri);
+    }
     if (npr && npr->which == Z_NamePlusRecord_surrogateDiagnostic)
     {
         Z_DiagRec *diag_rec = npr->u.surrogateDiagnostic;
@@ -1919,61 +2139,84 @@ ZOOM_API(int)
     return 0;
 }
 
-ZOOM_API(const char *)
-    ZOOM_record_get(ZOOM_record rec, const char *type_spec, int *len)
+static const char *get_record_format(WRBUF wrbuf, int *len,
+                                     Z_NamePlusRecord *npr,
+                                     int marctype, const char *charset,
+                                     const char *format)
+{
+    const char *res = return_record_wrbuf(wrbuf, len, npr, marctype, charset);
+#if YAZ_HAVE_XML2
+    if (*format == '1' && len)
+    {
+        /* try to XML format res */
+        xmlDocPtr doc;
+        xmlKeepBlanksDefault(0); /* get get xmlDocFormatMemory to work! */
+        doc = xmlParseMemory(res, *len);
+        if (doc)
+        {
+            xmlChar *xml_mem;
+            int xml_size;
+            xmlDocDumpFormatMemory(doc, &xml_mem, &xml_size, 1);
+            wrbuf_rewind(wrbuf);
+            wrbuf_write(wrbuf, (const char *) xml_mem, xml_size);
+            xmlFree(xml_mem);
+            xmlFreeDoc(doc);
+            res = wrbuf_cstr(wrbuf);
+            *len = wrbuf_len(wrbuf);
+        } 
+    }
+#endif
+    return res;
+}
+
+
+static const char *npr_format(Z_NamePlusRecord *npr, const char *schema,
+                              WRBUF wrbuf,
+                              const char *type_spec, int *len)
 {
+    size_t i;
     char type[40];
     char charset[40];
-    char xpath[512];
-    const char *cp;
-    int i;
-    Z_NamePlusRecord *npr;
-    
-    if (len)
-        *len = 0; /* default return */
-        
-    if (!rec)
-        return 0;
-    npr = rec->npr;
-    if (!npr)
-        return 0;
+    char format[3];
+    const char *cp = type_spec;
 
-    cp = type_spec;
-    for (i = 0; cp[i] && i < sizeof(type)-1; i++)
-    {
-        if (cp[i] == ';' || cp[i] == ' ')
-            break;
+    for (i = 0; cp[i] && cp[i] != ';' && cp[i] != ' ' && i < sizeof(type)-1;
+         i++)
         type[i] = cp[i];
-    }
     type[i] = '\0';
     charset[0] = '\0';
-    while (type_spec[i] == ';')
+    format[0] = '\0';
+    while (1)
     {
+        while (cp[i] == ' ')
+            i++;
+        if (cp[i] != ';')
+            break;
         i++;
-        while (type_spec[i] == ' ')
+        while (cp[i] == ' ')
             i++;
-        if (!strncmp(type_spec+i, "charset=", 8))
+        if (!strncmp(cp + i, "charset=", 8))
         {
-            int j = 0;
+            size_t j = 0;
             i = i + 8; /* skip charset= */
-            for (j = 0; type_spec[i]  && j < sizeof(charset)-1; i++, j++)
+            for (j = 0; cp[i] && cp[i] != ';' && cp[i] != ' '; i++)
             {
-                if (type_spec[i] == ';' || type_spec[i] == ' ')
-                    break;
-                charset[j] = cp[i];
+                if (j < sizeof(charset)-1)
+                    charset[j++] = cp[i];
             }
             charset[j] = '\0';
         }
-        else if (!strncmp(type_spec+i, "xpath=", 6))
+        else if (!strncmp(cp + i, "format=", 7))
         {
-            int j = 0; 
-            i = i + 6;
-            for (j = 0; type_spec[i] && j < sizeof(xpath)-1; i++, j++)
-                xpath[j] = cp[i];
-            xpath[j] = '\0';
+            size_t j = 0; 
+            i = i + 7;
+            for (j = 0; cp[i] && cp[i] != ';' && cp[i] != ' '; i++)
+            {
+                if (j < sizeof(format)-1)
+                    format[j++] = cp[i];
+            }
+            format[j] = '\0';
         } 
-        while (type_spec[i] == ' ')
-            i++;
     }
     if (!strcmp(type, "database"))
     {
@@ -1981,6 +2224,12 @@ ZOOM_API(const char *)
             *len = (npr->databaseName ? strlen(npr->databaseName) : 0);
         return npr->databaseName;
     }
+    else if (!strcmp(type, "schema"))
+    {
+        if (len)
+            *len = schema ? strlen(schema) : 0;
+        return schema;
+    }
     else if (!strcmp(type, "syntax"))
     {
         const char *desc = 0;   
@@ -2001,151 +2250,73 @@ ZOOM_API(const char *)
     /* from now on - we have a database record .. */
     if (!strcmp(type, "render"))
     {
-        Z_External *r = (Z_External *) npr->u.databaseRecord;
-        const Odr_oid *oid = r->direct_reference;
-
-        /* render bibliographic record .. */
-        if (r->which == Z_External_OPAC)
-        {
-            r = r->u.opac->bibliographicRecord;
-            if (!r)
-                return 0;
-            oid = r->direct_reference;
-        }
-        if (r->which == Z_External_sutrs)
-            return record_iconv_return(rec, len,
-                                       (char*) r->u.sutrs->buf,
-                                       r->u.sutrs->len,
-                                       charset);
-        else if (r->which == Z_External_octet)
-        {
-            if (yaz_oid_is_iso2709(oid))
-            {
-                const char *ret_buf = marc_iconv_return(
-                    rec, YAZ_MARC_LINE, len,
-                    (const char *) r->u.octet_aligned->buf,
-                    r->u.octet_aligned->len,
-                    charset);
-                if (ret_buf)
-                    return ret_buf;
-            }
-            return record_iconv_return(rec, len,
-                                       (const char *) r->u.octet_aligned->buf,
-                                       r->u.octet_aligned->len,
-                                       charset);
-        }
-        else if (r->which == Z_External_grs1)
-        {
-            if (!rec->wrbuf_marc)
-                rec->wrbuf_marc = wrbuf_alloc();
-            wrbuf_rewind(rec->wrbuf_marc);
-            yaz_display_grs1(rec->wrbuf_marc, r->u.grs1, 0);
-            return record_iconv_return(rec, len,
-                                       wrbuf_buf(rec->wrbuf_marc),
-                                       wrbuf_len(rec->wrbuf_marc),
-                                       charset);
-        }
-        return 0;
+        return get_record_format(wrbuf, len, npr, YAZ_MARC_LINE, charset, format);
     }
     else if (!strcmp(type, "xml"))
     {
-        Z_External *r = (Z_External *) npr->u.databaseRecord;
-        const Odr_oid *oid = r->direct_reference;
-
-        /* render bibliographic record .. */
-        if (r->which == Z_External_OPAC)
-        {
-            r = r->u.opac->bibliographicRecord;
-            if (!r)
-                return 0;
-            oid = r->direct_reference;
-        }
-        
-        if (r->which == Z_External_sutrs)
-            return record_iconv_return(rec, len,
-                                       (const char *) r->u.sutrs->buf,
-                                       r->u.sutrs->len,
-                                       charset);
-        else if (r->which == Z_External_octet)
-        {
-            int marc_decode_type = YAZ_MARC_MARCXML;
-            if (yaz_oid_is_iso2709(oid))
-            {
-                const char *ret_buf = marc_iconv_return(
-                    rec, marc_decode_type, len,
-                    (const char *) r->u.octet_aligned->buf,
-                    r->u.octet_aligned->len,
-                    charset);
-                if (ret_buf)
-                    return ret_buf;
-            }
-            return record_iconv_return(rec, len,
-                                       (const char *) r->u.octet_aligned->buf,
-                                       r->u.octet_aligned->len,
-                                       charset);
-        }
-        else if (r->which == Z_External_grs1)
-        {
-            if (len) *len = 5;
-            return "GRS-1";
-        }
-        return 0;
+        return get_record_format(wrbuf, len, npr, YAZ_MARC_MARCXML, charset,
+                                 format);
+    }
+    else if (!strcmp(type, "txml"))
+    {
+        return get_record_format(wrbuf, len, npr, YAZ_MARC_TURBOMARC, charset,
+                                 format);
     }
     else if (!strcmp(type, "raw"))
     {
-        Z_External *r = (Z_External *) npr->u.databaseRecord;
-        
-        if (r->which == Z_External_sutrs)
-        {
-            if (len) *len = r->u.sutrs->len;
-            return (const char *) r->u.sutrs->buf;
-        }
-        else if (r->which == Z_External_octet)
-        {
-            if (len) *len = r->u.octet_aligned->len;
-            return (const char *) r->u.octet_aligned->buf;
-        }
-        else /* grs-1, explain, OPAC, ... */
-        {
-            if (len) *len = -1;
-            return (const char *) npr->u.databaseRecord;
-        }
-        return 0;
+        return get_record_format(wrbuf, len, npr, YAZ_MARC_ISO2709, charset,
+            format);
     }
-    else if (!strcmp (type, "ext"))
+    else if (!strcmp(type, "ext"))
     {
         if (len) *len = -1;
         return (const char *) npr->u.databaseRecord;
     }
-    else if (!strcmp (type, "opac"))
-             
+    else if (!strcmp(type, "opac"))
     {
-        Z_External *r = (Z_External *) npr->u.databaseRecord;
-        if (r->which == Z_External_OPAC)
-        {
-            if (!rec->wrbuf_opac)
-                rec->wrbuf_opac = wrbuf_alloc();
-            wrbuf_rewind(rec->wrbuf_opac);
-            yaz_display_OPAC(rec->wrbuf_opac, r->u.opac, 0);
-            return record_iconv_return(rec, len,
-                                       wrbuf_buf(rec->wrbuf_opac),
-                                       wrbuf_len(rec->wrbuf_opac),
-                                       charset);
-        }
+        if (npr->u.databaseRecord->which == Z_External_OPAC)
+            return get_record_format(wrbuf, len, npr, YAZ_MARC_MARCXML, charset,
+                                     format);
     }
     return 0;
 }
 
-static int strcmp_null(const char *v1, const char *v2)
+ZOOM_API(const char *)
+    ZOOM_record_get(ZOOM_record rec, const char *type_spec, int *len)
 {
-    if (!v1 && !v2)
+    WRBUF wrbuf;
+    
+    if (len)
+        *len = 0; /* default return */
+        
+    if (!rec || !rec->npr)
         return 0;
-    if (!v1 || !v2)
-        return -1;
-    return strcmp(v1, v2);
-}
 
-static size_t record_hash(int pos)
+#if SHPTR
+    if (!rec->record_wrbuf)
+    {
+        WRBUF w = wrbuf_alloc();
+        YAZ_SHPTR_INIT(rec->record_wrbuf, w);
+    }
+    wrbuf = rec->record_wrbuf->ptr;
+#else
+    if (!rec->wrbuf)
+        rec->wrbuf = wrbuf_alloc();
+    wrbuf = rec->wrbuf;
+#endif
+    return npr_format(rec->npr, rec->schema, wrbuf, type_spec, len);
+}
+
+static int strcmp_null(const char *v1, const char *v2)
+{
+    if (!v1 && !v2)
+        return 0;
+    if (!v1 || !v2)
+        return -1;
+    return strcmp(v1, v2);
+}
+
+static size_t record_hash(int pos)
 {
     if (pos < 0)
         pos = 0;
@@ -2154,53 +2325,62 @@ static size_t record_hash(int pos)
 
 static void record_cache_add(ZOOM_resultset r, Z_NamePlusRecord *npr, 
                              int pos,
-                             const char *syntax, const char *elementSetName)
+                             const char *syntax, const char *elementSetName,
+                             const char *schema,
+                             Z_SRW_diagnostic *diag)
 {
-    ZOOM_record_cache rc;
+    ZOOM_record_cache rc = 0;
     
     ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_RECV_RECORD);
     ZOOM_connection_put_event(r->connection, event);
 
     for (rc = r->record_hash[record_hash(pos)]; rc; rc = rc->next)
     {
-        if (pos == rc->pos)
+        if (pos == rc->pos 
+            && strcmp_null(r->schema, rc->schema) == 0
+            && strcmp_null(elementSetName,rc->elementSetName) == 0
+            && strcmp_null(syntax, rc->syntax) == 0)
+            break;
+    }
+    if (!rc)
+    {
+        rc = (ZOOM_record_cache) odr_malloc(r->odr, sizeof(*rc));
+        rc->rec.odr = 0;
+#if SHPTR
+        YAZ_SHPTR_INC(r->record_wrbuf);
+        rc->rec.record_wrbuf = r->record_wrbuf;
+#else
+        rc->rec.wrbuf = 0;
+#endif
+        rc->elementSetName = odr_strdup_null(r->odr, elementSetName);
+        
+        rc->syntax = odr_strdup_null(r->odr, syntax);
+        
+        rc->schema = odr_strdup_null(r->odr, r->schema);
+
+        rc->pos = pos;
+        rc->next = r->record_hash[record_hash(pos)];
+        r->record_hash[record_hash(pos)] = rc;
+    }
+    rc->rec.npr = npr;
+    rc->rec.schema = odr_strdup_null(r->odr, schema);
+    rc->rec.diag_set = 0;
+    rc->rec.diag_uri = 0;
+    rc->rec.diag_message = 0;
+    rc->rec.diag_details = 0;
+    if (diag)
+    {
+        if (diag->uri)
         {
-            if (strcmp_null(r->schema, rc->schema))
-                continue;
-            if (strcmp_null(elementSetName,rc->elementSetName))
-                continue;
-            if (strcmp_null(syntax, rc->syntax))
-                continue;
-            /* not destroying rc->npr (it's handled by nmem )*/
-            rc->rec.npr = npr;
-            /* keeping wrbuf_marc too */
-            return;
+            char *cp;
+            rc->rec.diag_set = odr_strdup(r->odr, diag->uri);
+            if ((cp = strrchr(rc->rec.diag_set, '/')))
+                *cp = '\0';
+            rc->rec.diag_uri = odr_strdup(r->odr, diag->uri);
         }
+        rc->rec.diag_message = odr_strdup_null(r->odr, diag->message);            
+        rc->rec.diag_details = odr_strdup_null(r->odr, diag->details);
     }
-    rc = (ZOOM_record_cache) odr_malloc(r->odr, sizeof(*rc));
-    rc->rec.npr = npr; 
-    rc->rec.odr = 0;
-    rc->rec.wrbuf_marc = 0;
-    rc->rec.wrbuf_iconv = 0;
-    rc->rec.wrbuf_opac = 0;
-    if (elementSetName)
-        rc->elementSetName = odr_strdup(r->odr, elementSetName);
-    else
-        rc->elementSetName = 0;
-
-    if (syntax)
-        rc->syntax = odr_strdup(r->odr, syntax);
-    else
-        rc->syntax = 0;
-
-    if (r->schema)
-        rc->schema = odr_strdup(r->odr, r->schema);
-    else
-        rc->schema = 0;
-
-    rc->pos = pos;
-    rc->next = r->record_hash[record_hash(pos)];
-    r->record_hash[record_hash(pos)] = rc;
 }
 
 static ZOOM_record record_cache_lookup(ZOOM_resultset r, int pos,
@@ -2277,7 +2457,8 @@ static void handle_records(ZOOM_connection c, Z_Records *sr,
             for (i = 0; i<p->num_records; i++)
             {
                 record_cache_add(resultset, p->records[i], i + *start,
-                                 syntax, elementSetName);
+                                 syntax, elementSetName,
+                                 elementSetName, 0);
             }
             *count -= i;
             if (*count < 0)
@@ -2294,17 +2475,24 @@ static void handle_records(ZOOM_connection c, Z_Records *sr,
             {
                 /* present response and we didn't get any records! */
                 Z_NamePlusRecord *myrec = 
-                    zget_surrogateDiagRec(resultset->odr, 0, 14, 0);
+                    zget_surrogateDiagRec(
+                        resultset->odr, 0, 
+                        YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
+                        "ZOOM C generated. Present phase and no records");
                 record_cache_add(resultset, myrec, *start,
-                                 syntax, elementSetName);
+                                 syntax, elementSetName, 0, 0);
             }
         }
         else if (present_phase)
         {
             /* present response and we didn't get any records! */
             Z_NamePlusRecord *myrec = 
-                zget_surrogateDiagRec(resultset->odr, 0, 14, 0);
-            record_cache_add(resultset, myrec, *start, syntax, elementSetName);
+                zget_surrogateDiagRec(
+                    resultset->odr, 0,
+                    YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
+                    "ZOOM C generated: Present response and no records");
+            record_cache_add(resultset, myrec, *start, syntax, elementSetName,
+                             0, 0);
         }
     }
 }
@@ -2467,6 +2655,7 @@ static int scan_response(ZOOM_connection c, Z_ScanResponse *res)
     if (res->entries && res->entries->nonsurrogateDiagnostics)
         response_diag(c, res->entries->nonsurrogateDiagnostics[0]);
     scan->scan_response = res;
+    scan->srw_scan_response = 0;
     nmem_transfer(odr_getmem(scan->odr), nmem);
     if (res->stepSize)
         ZOOM_options_set_int(scan->options, "stepSize", *res->stepSize);
@@ -2580,8 +2769,12 @@ static zoom_ret send_present(ZOOM_connection c)
         yaz_log(log_details, "%p send_present skip=%d", c, i);
 
     *req->resultSetStartPoint = *start + 1;
-    *req->numberOfRecordsRequested = resultset->step>0 ?
-        resultset->step : *count;
+
+    if (resultset->step > 0 && resultset->step < *count)
+        *req->numberOfRecordsRequested = resultset->step;
+    else
+        *req->numberOfRecordsRequested = *count;
+    
     if (*req->numberOfRecordsRequested + *start > resultset->size)
         *req->numberOfRecordsRequested = resultset->size - *start;
     assert(*req->numberOfRecordsRequested > 0);
@@ -2667,68 +2860,25 @@ ZOOM_API(ZOOM_scanset)
 ZOOM_API(ZOOM_scanset)
     ZOOM_connection_scan1(ZOOM_connection c, ZOOM_query q)
 {
-    char *start;
-    char *freeme = 0;
     ZOOM_scanset scan = 0;
 
-    /*
-     * We need to check the query-type, so we can recognise CQL and
-     * CCL and compile them into a form that we can use here.  The
-     * ZOOM_query structure has no explicit `type' member, but
-     * inspection of the ZOOM_query_prefix() and ZOOM_query_cql()
-     * functions shows how the structure is set up in each case.
-     */
     if (!q->z_query)
         return 0;
-    else if (q->z_query->which == Z_Query_type_1) 
-    {
-        yaz_log(log_api, "%p ZOOM_connection_scan1 q=%p PQF '%s'",
-                c, q, q->query_string);
-        start = q->query_string;
-    } 
-    else if (q->z_query->which == Z_Query_type_104)
-    {
-        yaz_log(log_api, "%p ZOOM_connection_scan1 q=%p CQL '%s'",
-                c, q, q->query_string);
-        start = freeme = cql2pqf(c, q->query_string);
-        if (start == 0)
-            return 0;
-    } 
-    else
-    {
-        yaz_log(YLOG_FATAL, "%p ZOOM_connection_scan1 q=%p unknown type '%s'",
-                c, q, q->query_string);
-        abort();
-    }
-    
     scan = (ZOOM_scanset) xmalloc(sizeof(*scan));
     scan->connection = c;
     scan->odr = odr_createmem(ODR_DECODE);
     scan->options = ZOOM_options_create_with_parent(c->options);
     scan->refcount = 1;
     scan->scan_response = 0;
-    scan->termListAndStartPoint =
-        p_query_scan(scan->odr, PROTO_Z3950, &scan->attributeSet, start);
-    xfree(freeme);
-
-    {
-        const char *cp = ZOOM_options_get(scan->options, "rpnCharset");
-        if (cp)
-        {
-            yaz_iconv_t cd = yaz_iconv_open(cp, "UTF-8");
-            if (cd)
-            {
-                yaz_query_charset_convert_apt(scan->termListAndStartPoint,
-                                              scan->odr, cd);
-            }
-        }
-    }
-        
+    scan->srw_scan_response = 0;
 
+    scan->query = q;
+    (q->refcount)++;
     scan->databaseNames = set_DatabaseNames(c, c->options,
                                             &scan->num_databaseNames,
                                             scan->odr);
-    if (scan->termListAndStartPoint != 0)
+
+    if (1)
     {
         ZOOM_task task = ZOOM_connection_add_task(c, ZOOM_TASK_SCAN);
         task->u.scan.scan = scan;
@@ -2751,6 +2901,8 @@ ZOOM_API(void)
     (scan->refcount)--;
     if (scan->refcount == 0)
     {
+        ZOOM_query_destroy(scan->query);
+
         odr_destroy(scan->odr);
         
         ZOOM_options_destroy(scan->options);
@@ -2776,7 +2928,7 @@ static zoom_ret send_package(ZOOM_connection c)
     return do_write(c);
 }
 
-static zoom_ret send_scan(ZOOM_connection c)
+static zoom_ret ZOOM_connection_send_scan(ZOOM_connection c)
 {
     ZOOM_scanset scan;
     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_scanRequest);
@@ -2788,11 +2940,47 @@ static zoom_ret send_scan(ZOOM_connection c)
     assert (c->tasks->which == ZOOM_TASK_SCAN);
     scan = c->tasks->u.scan.scan;
 
-    req->termListAndStartPoint = scan->termListAndStartPoint;
-    req->attributeSet = scan->attributeSet;
+    /* Z39.50 scan can only carry RPN */
+    if (scan->query->z_query->which == Z_Query_type_1 ||
+        scan->query->z_query->which == Z_Query_type_101)
+    {
+        Z_RPNQuery *rpn = scan->query->z_query->u.type_1;
+        const char *cp = ZOOM_options_get(scan->options, "rpnCharset");
+        if (cp)
+        {
+            yaz_iconv_t cd = yaz_iconv_open(cp, "UTF-8");
+            if (cd)
+            {
+                rpn = yaz_copy_z_RPNQuery(rpn, c->odr_out);
+
+                yaz_query_charset_convert_rpnquery(
+                    rpn, c->odr_out, cd);
+                yaz_iconv_close(cd);
+            }
+        }
+        req->attributeSet = rpn->attributeSetId;
+        if (!req->attributeSet)
+            req->attributeSet = odr_oiddup(c->odr_out, yaz_oid_attset_bib_1);
+        if (rpn->RPNStructure->which == Z_RPNStructure_simple &&
+            rpn->RPNStructure->u.simple->which == Z_Operand_APT)
+        {
+            req->termListAndStartPoint =
+                rpn->RPNStructure->u.simple->u.attributesPlusTerm;
+        }
+        else
+        {
+            set_ZOOM_error(c, ZOOM_ERROR_INVALID_QUERY, 0);
+            return zoom_complete;
+        }
+    }
+    else
+    {
+        set_ZOOM_error(c, ZOOM_ERROR_UNSUPPORTED_QUERY, 0);
+        return zoom_complete;
+    }
 
     *req->numberOfTermsRequested =
-        ZOOM_options_get_int(scan->options, "number", 10);
+        ZOOM_options_get_int(scan->options, "number", 20);
 
     req->preferredPositionInResponse =
         odr_intdup(c->odr_out,
@@ -2808,69 +2996,156 @@ static zoom_ret send_scan(ZOOM_connection c)
     return send_APDU(c, apdu);
 }
 
+#if YAZ_HAVE_XML2
+static zoom_ret ZOOM_connection_srw_send_scan(ZOOM_connection c)
+{
+    ZOOM_scanset scan;
+    Z_SRW_PDU *sr = 0;
+    const char *option_val = 0;
+
+    if (!c->tasks)
+        return zoom_complete;
+    assert (c->tasks->which == ZOOM_TASK_SCAN);
+    scan = c->tasks->u.scan.scan;
+        
+    sr = ZOOM_srw_get_pdu(c, Z_SRW_scan_request);
+
+    /* SRU scan can only carry CQL and PQF */
+    if (scan->query->z_query->which == Z_Query_type_104)
+    {
+        sr->u.scan_request->query_type = Z_SRW_query_type_cql;
+        sr->u.scan_request->scanClause.cql = scan->query->query_string;
+    }
+    else if (scan->query->z_query->which == Z_Query_type_1
+             || scan->query->z_query->which == Z_Query_type_101)
+    {
+        sr->u.scan_request->query_type = Z_SRW_query_type_pqf;
+        sr->u.scan_request->scanClause.pqf = scan->query->query_string;
+    }
+    else
+    {
+        set_ZOOM_error(c, ZOOM_ERROR_UNSUPPORTED_QUERY, 0);
+        return zoom_complete;
+    }
+
+    sr->u.scan_request->maximumTerms = odr_intdup(
+        c->odr_out, ZOOM_options_get_int(scan->options, "number", 10));
+    
+    sr->u.scan_request->responsePosition = odr_intdup(
+        c->odr_out, ZOOM_options_get_int(scan->options, "position", 1));
+    
+    option_val = ZOOM_options_get(scan->options, "extraArgs");
+    yaz_encode_sru_extra(sr, c->odr_out, option_val);
+    return send_srw(c, sr);
+}
+#else
+static zoom_ret ZOOM_connection_srw_send_scan(ZOOM_connection c)
+{
+    return zoom_complete;
+}
+#endif
+
+
 ZOOM_API(size_t)
     ZOOM_scanset_size(ZOOM_scanset scan)
 {
-    if (!scan || !scan->scan_response || !scan->scan_response->entries)
+    if (!scan)
         return 0;
-    return scan->scan_response->entries->num_entries;
+
+    if (scan->scan_response && scan->scan_response->entries)
+        return scan->scan_response->entries->num_entries;
+    else if (scan->srw_scan_response)
+        return scan->srw_scan_response->num_terms;
+    return 0;
 }
 
-ZOOM_API(const char *)
-    ZOOM_scanset_term(ZOOM_scanset scan, size_t pos,
-                      int *occ, int *len)
+static void ZOOM_scanset_term_x(ZOOM_scanset scan, size_t pos,
+                                size_t *occ,
+                                const char **value_term, size_t *value_len,
+                                const char **disp_term, size_t *disp_len)
 {
-    const char *term = 0;
     size_t noent = ZOOM_scanset_size(scan);
-    Z_ScanResponse *res = scan->scan_response;
     
-    *len = 0;
+    *value_term = 0;
+    *value_len = 0;
+
+    *disp_term = 0;
+    *disp_len = 0;
+
     *occ = 0;
     if (pos >= noent)
-        return 0;
-    if (res->entries->entries[pos]->which == Z_Entry_termInfo)
+        return;
+    if (scan->scan_response)
     {
-        Z_TermInfo *t = res->entries->entries[pos]->u.termInfo;
-        
-        if (t->term->which == Z_Term_general)
+        Z_ScanResponse *res = scan->scan_response;
+        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)
+            {
+                *disp_term = t->displayTerm;
+                *disp_len = strlen(*disp_term);
+            }
+            else if (t->term->which == Z_Term_general)
+            {
+                *disp_term = (const char *) t->term->u.general->buf;
+                *disp_len = t->term->u.general->len;
+            }
+            *occ = t->globalOccurrences ? *t->globalOccurrences : 0;
+        }
+    }
+    if (scan->srw_scan_response)
+    {
+        Z_SRW_scanResponse *res = scan->srw_scan_response;
+        Z_SRW_scanTerm *t = res->terms + pos;
+        if (t)
         {
-            term = (const char *) t->term->u.general->buf;
-            *len = t->term->u.general->len;
+            *value_term = t->value;
+            *value_len = strlen(*value_term);
+
+            if (t->displayTerm)
+                *disp_term = t->displayTerm;
+            else
+                *disp_term = t->value;
+            *disp_len = strlen(*disp_term);
+            *occ = t->numberOfRecords ? *t->numberOfRecords : 0;
         }
-        *occ = t->globalOccurrences ? *t->globalOccurrences : 0;
     }
-    return term;
 }
 
 ZOOM_API(const char *)
-    ZOOM_scanset_display_term(ZOOM_scanset scan, size_t pos,
-                              int *occ, int *len)
+    ZOOM_scanset_term(ZOOM_scanset scan, size_t pos,
+                      size_t *occ, size_t *len)
 {
-    const char *term = 0;
-    size_t noent = ZOOM_scanset_size(scan);
-    Z_ScanResponse *res = scan->scan_response;
+    const char *value_term = 0;
+    size_t value_len = 0;
+    const char *disp_term = 0;
+    size_t disp_len = 0;
+
+    ZOOM_scanset_term_x(scan, pos, occ, &value_term, &value_len,
+                        &disp_term, &disp_len);
     
-    *len = 0;
-    *occ = 0;
-    if (pos >= noent)
-        return 0;
-    if (res->entries->entries[pos]->which == Z_Entry_termInfo)
-    {
-        Z_TermInfo *t = res->entries->entries[pos]->u.termInfo;
+    *len = value_len;
+    return value_term;
+}
 
-        if (t->displayTerm)
-        {
-            term = t->displayTerm;
-            *len = strlen(term);
-        }
-        else if (t->term->which == Z_Term_general)
-        {
-            term = (const char *) t->term->u.general->buf;
-            *len = t->term->u.general->len;
-        }
-        *occ = t->globalOccurrences ? *t->globalOccurrences : 0;
-    }
-    return term;
+ZOOM_API(const char *)
+    ZOOM_scanset_display_term(ZOOM_scanset scan, size_t pos,
+                              size_t *occ, size_t *len)
+{
+    const char *value_term = 0;
+    size_t value_len = 0;
+    const char *disp_term = 0;
+    size_t disp_len = 0;
+
+    ZOOM_scanset_term_x(scan, pos, occ, &value_term, &value_len,
+                        &disp_term, &disp_len);
+    
+    *len = disp_len;
+    return disp_term;
 }
 
 ZOOM_API(const char *)
@@ -2898,7 +3173,7 @@ static Z_APDU *create_es_package(ZOOM_package p, const Odr_oid *oid)
     
     str = ZOOM_options_get(p->options, "user-id");
     if (str)
-        req->userId = odr_strdup(p->odr_out, str);
+        req->userId = odr_strdup_null(p->odr_out, str);
     
     req->packageType = odr_oiddup(p->odr_out, oid);
 
@@ -2978,6 +3253,7 @@ static Z_ItemOrder *encode_item_order(ZOOM_package p)
 {
     Z_ItemOrder *req = (Z_ItemOrder *) odr_malloc(p->odr_out, sizeof(*req));
     const char *str;
+    int len;
     
     req->which = Z_IOItemOrder_esRequest;
     req->u.esRequest = (Z_IORequest *) 
@@ -2991,16 +3267,16 @@ static Z_ItemOrder *encode_item_order(ZOOM_package p)
         odr_malloc(p->odr_out, sizeof(*req->u.esRequest->toKeep->contact));
         
     str = ZOOM_options_get(p->options, "contact-name");
-    req->u.esRequest->toKeep->contact->name = str ?
-        odr_strdup(p->odr_out, str) : 0;
+    req->u.esRequest->toKeep->contact->name =
+        odr_strdup_null(p->odr_out, str);
         
     str = ZOOM_options_get(p->options, "contact-phone");
-    req->u.esRequest->toKeep->contact->phone = str ?
-        odr_strdup(p->odr_out, str) : 0;
+    req->u.esRequest->toKeep->contact->phone =
+        odr_strdup_null(p->odr_out, str);
         
     str = ZOOM_options_get(p->options, "contact-email");
-    req->u.esRequest->toKeep->contact->email = str ?
-        odr_strdup(p->odr_out, str) : 0;
+    req->u.esRequest->toKeep->contact->email =
+        odr_strdup_null(p->odr_out, str);
         
     req->u.esRequest->toKeep->addlBilling = 0;
         
@@ -3022,18 +3298,18 @@ static Z_ItemOrder *encode_item_order(ZOOM_package p)
         req->u.esRequest->notToKeep->resultSetItem->resultSetId =
             odr_strdup(p->odr_out, str);
         req->u.esRequest->notToKeep->resultSetItem->item =
-            (int *) odr_malloc(p->odr_out, sizeof(int));
+            odr_intdup(p->odr_out, 0);
         
         str = ZOOM_options_get(p->options, "itemorder-item");
         *req->u.esRequest->notToKeep->resultSetItem->item =
             (str ? atoi(str) : 1);
     }
 
-    str = ZOOM_options_get(p->options, "doc");
+    str = ZOOM_options_getl(p->options, "doc", &len);
     if (str)
     {
         req->u.esRequest->notToKeep->itemRequest =
-            z_ext_record_xml(p->odr_out, str, strlen(str));
+            z_ext_record_xml(p->odr_out, str, len);
     }
     else
         req->u.esRequest->notToKeep->itemRequest = encode_ill_request(p);
@@ -3096,10 +3372,14 @@ static Z_APDU *create_xmlupdate_package(ZOOM_package p)
     Z_APDU *apdu = create_es_package(p, yaz_oid_extserv_xml_es);
     Z_ExtendedServicesRequest *req = apdu->u.extendedServicesRequest;
     Z_External *ext = (Z_External *) odr_malloc(p->odr_out, sizeof(*ext));
-    const char *doc = ZOOM_options_get(p->options, "doc");
+    int len;
+    const char *doc = ZOOM_options_getl(p->options, "doc", &len);
 
     if (!doc)
+    {
         doc = "";
+        len = 0;
+    }
 
     req->taskSpecificParameters = ext;
     ext->direct_reference = req->packageType;
@@ -3108,8 +3388,7 @@ static Z_APDU *create_xmlupdate_package(ZOOM_package p)
     
     ext->which = Z_External_octet;
     ext->u.single_ASN1_type =
-        odr_create_Odr_oct(p->odr_out, (const unsigned char *) doc,
-                           strlen(doc));
+        odr_create_Odr_oct(p->odr_out, (const unsigned char *) doc, len);
     return apdu;
 }
 
@@ -3120,10 +3399,18 @@ static Z_APDU *create_update_package(ZOOM_package p)
     int num_db;
     char **db = set_DatabaseNames(p->connection, p->options, &num_db, p->odr_out);
     const char *action = ZOOM_options_get(p->options, "action");
-    const char *recordIdOpaque = ZOOM_options_get(p->options, "recordIdOpaque");
+    int recordIdOpaque_len;
+    const char *recordIdOpaque = ZOOM_options_getl(p->options, "recordIdOpaque",
+        &recordIdOpaque_len);
     const char *recordIdNumber = ZOOM_options_get(p->options, "recordIdNumber");
-    const char *record_buf = ZOOM_options_get(p->options, "record");
+    int record_len;
+    const char *record_buf = ZOOM_options_getl(p->options, "record",
+        &record_len);
+    int recordOpaque_len;
+    const char *recordOpaque_buf = ZOOM_options_getl(p->options, "recordOpaque",
+        &recordOpaque_len);
     const char *syntax_str = ZOOM_options_get(p->options, "syntax");
+    const char *version = ZOOM_options_get(p->options, "updateVersion");
 
     const char *correlationInfo_note =
         ZOOM_options_get(p->options, "correlationInfo.note");
@@ -3131,12 +3418,16 @@ static Z_APDU *create_update_package(ZOOM_package p)
         ZOOM_options_get(p->options, "correlationInfo.id");
     int action_no = -1;
     Odr_oid *syntax_oid = 0;
+    const Odr_oid *package_oid = yaz_oid_extserv_database_update;
 
+    if (!version)
+        version = "3";
     if (!syntax_str)
         syntax_str = "xml";
-    if (!record_buf)
+    if (!record_buf && !recordOpaque_buf)
     {
         record_buf = "void";
+        record_len = 4;
         syntax_str = "SUTRS";
     }
 
@@ -3152,8 +3443,27 @@ static Z_APDU *create_update_package(ZOOM_package p)
     if (num_db > 0)
         first_db = db[0];
     
-    if (!action)
-        action = "specialUpdate";
+    switch(*version)
+    {
+    case '1':
+        package_oid = yaz_oid_extserv_database_update_first_version;
+        /* old update does not support specialUpdate */
+        if (!action)
+            action = "recordInsert";
+        break;
+    case '2':
+        if (!action)
+            action = "specialUpdate";
+        package_oid = yaz_oid_extserv_database_update_second_version;
+        break;
+    case '3':
+        if (!action)
+            action = "specialUpdate";
+        package_oid = yaz_oid_extserv_database_update;
+        break;
+    default:
+        return 0;
+    }
     
     if (!strcmp(action, "recordInsert"))
         action_no = Z_IUOriginPartToKeep_recordInsert;
@@ -3168,7 +3478,7 @@ static Z_APDU *create_update_package(ZOOM_package p)
     else
         return 0;
 
-    apdu = create_es_package(p, yaz_oid_extserv_database_update);
+    apdu = create_es_package(p, package_oid);
     if (apdu)
     {
         Z_IUOriginPartToKeep *toKeep;
@@ -3179,10 +3489,8 @@ static Z_APDU *create_update_package(ZOOM_package p)
             ZOOM_options_get(p->options, "elementSetName");
         
         apdu->u.extendedServicesRequest->taskSpecificParameters = r;
-
         
-        r->direct_reference = odr_oiddup(p->odr_out, 
-                                         yaz_oid_extserv_database_update);
+        r->direct_reference = odr_oiddup(p->odr_out, package_oid);
         r->descriptor = 0;
         r->which = Z_External_update;
         r->indirect_reference = 0;
@@ -3199,9 +3507,7 @@ static Z_APDU *create_update_package(ZOOM_package p)
         toKeep->databaseName = odr_strdup(p->odr_out, first_db);
         toKeep->schema = 0;
         
-        toKeep->elementSetName = 0;
-        if (elementSetName)
-            toKeep->elementSetName = odr_strdup(p->odr_out, elementSetName);
+        toKeep->elementSetName = odr_strdup_null(p->odr_out, elementSetName);
             
         toKeep->actionQualifier = 0;
         toKeep->action = odr_intdup(p->odr_out, action_no);
@@ -3220,7 +3526,7 @@ static Z_APDU *create_update_package(ZOOM_package p)
             notToKeep->elements[0]->u.opaque = 
                 odr_create_Odr_oct(p->odr_out,
                                    (const unsigned char *) recordIdOpaque,
-                                   strlen(recordIdOpaque));
+                                   recordIdOpaque_len);
         }
         else if (recordIdNumber)
         {
@@ -3236,17 +3542,25 @@ static Z_APDU *create_update_package(ZOOM_package p)
         {
             Z_IUCorrelationInfo *ci;
             ci = notToKeep->elements[0]->correlationInfo =
-                odr_malloc(p->odr_out, sizeof(*ci));
-            ci->note = correlationInfo_note ?
-                odr_strdup(p->odr_out, correlationInfo_note) : 0;
+                (Z_IUCorrelationInfo *) odr_malloc(p->odr_out, sizeof(*ci));
+            ci->note = odr_strdup_null(p->odr_out, correlationInfo_note);
             ci->id = correlationInfo_id ?
                 odr_intdup(p->odr_out, atoi(correlationInfo_id)) : 0;
         }
         else
             notToKeep->elements[0]->correlationInfo = 0;
-        notToKeep->elements[0]->record =
-            z_ext_record_oid(p->odr_out, syntax_oid,
-                             record_buf, strlen(record_buf));
+        if (recordOpaque_buf)
+        {
+            notToKeep->elements[0]->record =
+                z_ext_record_oid_any(p->odr_out, syntax_oid,
+                                 recordOpaque_buf, recordOpaque_len);
+        }
+        else
+        {
+            notToKeep->elements[0]->record =
+                z_ext_record_oid(p->odr_out, syntax_oid,
+                                 record_buf, record_len);
+        }
     }
     if (0 && apdu)
     {
@@ -3367,6 +3681,11 @@ ZOOM_API(const char *)
     return ZOOM_options_get(p->options, key);
 }
 
+ZOOM_API(const char *)
+    ZOOM_package_option_getl(ZOOM_package p, const char *key, int *lenp)
+{
+    return ZOOM_options_getl(p->options, key, lenp);
+}
 
 ZOOM_API(void)
     ZOOM_package_option_set(ZOOM_package p, const char *key,
@@ -3375,7 +3694,15 @@ ZOOM_API(void)
     ZOOM_options_set(p->options, key, val);
 }
 
-static int ZOOM_connection_exec_task(ZOOM_connection c)
+ZOOM_API(void)
+    ZOOM_package_option_setl(ZOOM_package p, const char *key,
+                             const char *val, int len)
+{
+    ZOOM_options_setl(p->options, key, val, len);
+}
+
+ZOOM_API(int)
+    ZOOM_connection_exec_task(ZOOM_connection c)
 {
     ZOOM_task task = c->tasks;
     zoom_ret ret = zoom_complete;
@@ -3419,7 +3746,10 @@ static int ZOOM_connection_exec_task(ZOOM_connection c)
             ret = do_connect(c);
             break;
         case ZOOM_TASK_SCAN:
-            ret = send_scan(c);
+            if (c->proto == PROTO_HTTP)
+                ret = ZOOM_connection_srw_send_scan(c);
+            else
+                ret = ZOOM_connection_send_scan(c);
             break;
         case ZOOM_TASK_PACKAGE:
             ret = send_package(c);
@@ -3460,22 +3790,90 @@ static zoom_ret send_sort_present(ZOOM_connection c)
     return r;
 }
 
+static int es_response_taskpackage_update(ZOOM_connection c,
+               Z_IUUpdateTaskPackage *utp)
+{
+       if (utp && utp->targetPart)
+       {
+               Z_IUTargetPart *targetPart = utp->targetPart;
+               switch ( *targetPart->updateStatus ) {
+                       case Z_IUTargetPart_success:
+                               ZOOM_options_set(c->tasks->u.package->options,"updateStatus", "success");
+                               break;
+                       case Z_IUTargetPart_partial:
+                               ZOOM_options_set(c->tasks->u.package->options,"updateStatus", "partial");
+                               break;
+                       case Z_IUTargetPart_failure:
+                               ZOOM_options_set(c->tasks->u.package->options,"updateStatus", "failure");
+                               if (targetPart->globalDiagnostics && targetPart->num_globalDiagnostics > 0)
+                                       response_diag(c, targetPart->globalDiagnostics[0]);
+                               break;
+               }
+               // NOTE: Individual record status, surrogate diagnostics, and supplemental diagnostics ARE NOT REPORTED.
+       }
+    return 1;
+}
+
+static int es_response_taskpackage(ZOOM_connection c,
+                                   Z_TaskPackage *taskPackage)
+{
+       // targetReference
+       Odr_oct *id = taskPackage->targetReference;
+       if (id)
+               ZOOM_options_setl(c->tasks->u.package->options,
+                                                       "targetReference", (char*) id->buf, id->len);
+       
+       // taskStatus
+       switch ( *taskPackage->taskStatus ) {
+               case Z_TaskPackage_pending:
+                       ZOOM_options_set(c->tasks->u.package->options,"taskStatus", "pending");
+                       break;
+               case Z_TaskPackage_active:
+                       ZOOM_options_set(c->tasks->u.package->options,"taskStatus", "active");
+                       break;
+               case Z_TaskPackage_complete:
+                       ZOOM_options_set(c->tasks->u.package->options,"taskStatus", "complete");
+                       break;
+               case Z_TaskPackage_aborted:
+                       ZOOM_options_set(c->tasks->u.package->options,"taskStatus", "aborted");
+                       if ( taskPackage->num_packageDiagnostics && taskPackage->packageDiagnostics )
+                               response_diag(c, taskPackage->packageDiagnostics[0]);
+                       break;
+       }
+       
+       // taskSpecificParameters
+       // NOTE: Only Update implemented, no others.
+       if ( taskPackage->taskSpecificParameters->which == Z_External_update ) {
+                       Z_IUUpdateTaskPackage *utp = taskPackage->taskSpecificParameters->u.update->u.taskPackage;
+                       es_response_taskpackage_update(c, utp);
+       }
+       return 1;
+}
+
+
 static int es_response(ZOOM_connection c,
                        Z_ExtendedServicesResponse *res)
 {
     if (!c->tasks || c->tasks->which != ZOOM_TASK_PACKAGE)
         return 0;
-    if (res->diagnostics && res->num_diagnostics > 0)
-        response_diag(c, res->diagnostics[0]);
+    switch (*res->operationStatus) {
+        case Z_ExtendedServicesResponse_done:
+            ZOOM_options_set(c->tasks->u.package->options,"operationStatus", "done");
+            break;
+        case Z_ExtendedServicesResponse_accepted:
+            ZOOM_options_set(c->tasks->u.package->options,"operationStatus", "accepted");
+            break;
+        case Z_ExtendedServicesResponse_failure:
+            ZOOM_options_set(c->tasks->u.package->options,"operationStatus", "failure");
+            if (res->diagnostics && res->num_diagnostics > 0)
+                response_diag(c, res->diagnostics[0]);
+            break;
+    }
     if (res->taskPackage &&
         res->taskPackage->which == Z_External_extendedService)
     {
         Z_TaskPackage *taskPackage = res->taskPackage->u.extendedService;
-        Odr_oct *id = taskPackage->targetReference;
-        
-        if (id)
-            ZOOM_options_setl(c->tasks->u.package->options,
-                              "targetReference", (char*) id->buf, id->len);
+        es_response_taskpackage(c, taskPackage);
     }
     if (res->taskPackage && 
         res->taskPackage->which == Z_External_octet)
@@ -3535,7 +3933,7 @@ static void recv_apdu(ZOOM_connection c, Z_APDU *apdu)
     switch(apdu->which)
     {
     case Z_APDU_initResponse:
-        yaz_log(log_api, "%p recv_apd: Received Init response", c);
+        yaz_log(log_api, "%p recv_apdu: Received Init response", c);
         initrs = apdu->u.initResponse;
         ZOOM_connection_option_set(c, "serverImplementationId",
                                    initrs->implementationId ?
@@ -3662,137 +4060,286 @@ static void recv_apdu(ZOOM_connection c, Z_APDU *apdu)
 }
 
 #if YAZ_HAVE_XML2
-static void handle_srw_response(ZOOM_connection c,
-                                Z_SRW_searchRetrieveResponse *res)
+static zoom_ret handle_srw_response(ZOOM_connection c,
+                                    Z_SRW_searchRetrieveResponse *res)
 {
     ZOOM_resultset resultset = 0;
     int i;
     NMEM nmem;
     ZOOM_Event event;
-    int *start;
+    int *start, *count;
     const char *syntax, *elementSetName;
 
     if (!c->tasks)
-        return;
+        return zoom_complete;
 
     switch(c->tasks->which)
     {
     case ZOOM_TASK_SEARCH:
         resultset = c->tasks->u.search.resultset;
         start = &c->tasks->u.search.start;
+        count = &c->tasks->u.search.count;
         syntax = c->tasks->u.search.syntax;
         elementSetName = c->tasks->u.search.elementSetName;        
+
+        if (!c->tasks->u.search.recv_search_fired)
+        {
+            event = ZOOM_Event_create(ZOOM_EVENT_RECV_SEARCH);
+            ZOOM_connection_put_event(c, event);
+            c->tasks->u.search.recv_search_fired = 1;
+        }
         break;
     case ZOOM_TASK_RETRIEVE:
         resultset = c->tasks->u.retrieve.resultset;
         start = &c->tasks->u.retrieve.start;
+        count = &c->tasks->u.retrieve.count;
         syntax = c->tasks->u.retrieve.syntax;
         elementSetName = c->tasks->u.retrieve.elementSetName;
         break;
     default:
-        return;
+        return zoom_complete;
     }
-    event = ZOOM_Event_create(ZOOM_EVENT_RECV_SEARCH);
-    ZOOM_connection_put_event(c, event);
 
     resultset->size = 0;
 
+    if (res->resultSetId)
+        ZOOM_resultset_option_set(resultset, "resultSetId", res->resultSetId);
+
     yaz_log(log_details, "%p handle_srw_response got SRW response OK", c);
-    
-    if (res->numberOfRecords)
-        resultset->size = *res->numberOfRecords;
 
-    for (i = 0; i<res->num_records; i++)
+    if (res->num_diagnostics > 0)
     {
-        int pos;
-
-        Z_NamePlusRecord *npr = (Z_NamePlusRecord *)
-            odr_malloc(c->odr_in, sizeof(Z_NamePlusRecord));
-
-        if (res->records[i].recordPosition && 
-            *res->records[i].recordPosition > 0)
-            pos = *res->records[i].recordPosition - 1;
-        else
-            pos = *start + i;
-        
-        npr->databaseName = 0;
-        npr->which = Z_NamePlusRecord_databaseRecord;
-        npr->u.databaseRecord = (Z_External *)
-            odr_malloc(c->odr_in, sizeof(Z_External));
-        npr->u.databaseRecord->descriptor = 0;
-        npr->u.databaseRecord->direct_reference =
-            odr_oiddup(c->odr_in, yaz_oid_recsyn_xml);
-        npr->u.databaseRecord->which = Z_External_octet;
-
-        npr->u.databaseRecord->u.octet_aligned = (Odr_oct *)
-            odr_malloc(c->odr_in, sizeof(Odr_oct));
-        npr->u.databaseRecord->u.octet_aligned->buf = (unsigned char*)
-            res->records[i].recordData_buf;
-        npr->u.databaseRecord->u.octet_aligned->len = 
-            npr->u.databaseRecord->u.octet_aligned->size = 
-            res->records[i].recordData_len;
-        record_cache_add(resultset, npr, pos, syntax, elementSetName);
+        set_SRU_error(c, &res->diagnostics[0]);
     }
-    if (res->num_diagnostics > 0)
+    else
     {
-        const char *uri = res->diagnostics[0].uri;
-        if (uri)
+        if (res->numberOfRecords)
+            resultset->size = *res->numberOfRecords;
+        for (i = 0; i<res->num_records; i++)
         {
-            int code = 0;       
-            const char *cp;
-            if ((cp = strrchr(uri, '/')))
-                code = atoi(cp+1);
-            set_dset_error(c, code, uri,
-                           res->diagnostics[0].details, 0);
+            int pos;
+            Z_SRW_record *sru_rec;
+            Z_SRW_diagnostic *diag = 0;
+            int num_diag;
+            
+            Z_NamePlusRecord *npr = (Z_NamePlusRecord *)
+                odr_malloc(c->odr_in, sizeof(Z_NamePlusRecord));
+            
+            if (res->records[i].recordPosition && 
+                *res->records[i].recordPosition > 0)
+                pos = *res->records[i].recordPosition - 1;
+            else
+                pos = *start + i;
+            
+            sru_rec = &res->records[i];
+            
+            npr->databaseName = 0;
+            npr->which = Z_NamePlusRecord_databaseRecord;
+            npr->u.databaseRecord = (Z_External *)
+                odr_malloc(c->odr_in, sizeof(Z_External));
+            npr->u.databaseRecord->descriptor = 0;
+            npr->u.databaseRecord->direct_reference =
+                odr_oiddup(c->odr_in, yaz_oid_recsyn_xml);
+            npr->u.databaseRecord->which = Z_External_octet;
+            
+            npr->u.databaseRecord->u.octet_aligned = (Odr_oct *)
+                odr_malloc(c->odr_in, sizeof(Odr_oct));
+            npr->u.databaseRecord->u.octet_aligned->buf = (unsigned char*)
+                sru_rec->recordData_buf;
+            npr->u.databaseRecord->u.octet_aligned->len = 
+                npr->u.databaseRecord->u.octet_aligned->size = 
+                sru_rec->recordData_len;
+            
+            if (sru_rec->recordSchema 
+                && !strcmp(sru_rec->recordSchema,
+                           "info:srw/schema/1/diagnostics-v1.1"))
+            {
+                sru_decode_surrogate_diagnostics(sru_rec->recordData_buf,
+                                                 sru_rec->recordData_len,
+                                                 &diag, &num_diag,
+                                                 resultset->odr);
+            }
+            record_cache_add(resultset, npr, pos, syntax, elementSetName,
+                             sru_rec->recordSchema, diag);
         }
+        *count -= i;
+        *start += i;
+        if (*count + *start > resultset->size)
+            *count = resultset->size - *start;
+        if (*count < 0)
+            *count = 0;
+        
+        nmem = odr_extract_mem(c->odr_in);
+        nmem_transfer(odr_getmem(resultset->odr), nmem);
+        nmem_destroy(nmem);
+
+        if (*count > 0)
+            return ZOOM_connection_srw_send_search(c);
     }
-    nmem = odr_extract_mem(c->odr_in);
-    nmem_transfer(odr_getmem(resultset->odr), nmem);
+    return zoom_complete;
+}
+#endif
+
+#if YAZ_HAVE_XML2
+static void handle_srw_scan_response(ZOOM_connection c,
+                                     Z_SRW_scanResponse *res)
+{
+    NMEM nmem = odr_extract_mem(c->odr_in);
+    ZOOM_scanset scan;
+
+    if (!c->tasks || c->tasks->which != ZOOM_TASK_SCAN)
+        return;
+    scan = c->tasks->u.scan.scan;
+
+    if (res->num_diagnostics > 0)
+        set_SRU_error(c, &res->diagnostics[0]);
+
+    scan->scan_response = 0;
+    scan->srw_scan_response = res;
+    nmem_transfer(odr_getmem(scan->odr), nmem);
+
+    ZOOM_options_set_int(scan->options, "number", res->num_terms);
     nmem_destroy(nmem);
 }
 #endif
 
 #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_SRW_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);
+
+    gdu->u.HTTP_Request->method = odr_strdup(c->odr_out, "GET");
+    z_HTTP_header_add(c->odr_out, &gdu->u.HTTP_Request->headers, "Accept",
+                      "text/xml");
+    
+    for (h = cookie_hres->headers; h; h = h->next)
+    {
+        if (!strcmp(h->name, "Set-Cookie"))
+            z_HTTP_header_add(c->odr_out, &gdu->u.HTTP_Request->headers,
+                              "Cookie", h->value);
+    }
+    if (c->user && c->password)
+    {
+        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_complete;
+    if (c->odr_print)
+        z_GDU(c->odr_print, &gdu, 0, 0);
+    c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
+
+    odr_reset(c->odr_out);
+    return do_write(c);
+}
+
 static void handle_http(ZOOM_connection c, Z_HTTP_Response *hres)
 {
+    zoom_ret cret = zoom_complete;
     int ret = -1;
     const 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(log_details, "%p handle_http", c);
     
-    if (!yaz_srw_check_content_type(hres))
-        addinfo = "content-type";
-    else
+    if ((hres->code == 301 || hres->code == 302) && c->sru_mode == zoom_sru_get
+        && (location = z_HTTP_header_lookup(hres->headers, "Location")))
     {
-        Z_SOAP *soap_package = 0;
-        ODR o = c->odr_in;
-        Z_SOAP_Handler soap_handlers[2] = {
-            {YAZ_XMLNS_SRU_v1_1, 0, (Z_SOAP_fun) yaz_srw_codec},
-            {0, 0, 0}
-        };
-        ret = z_soap_codec(o, &soap_package,
-                           &hres->content_buf, &hres->content_len,
-                           soap_handlers);
-        if (!ret && soap_package->which == Z_SOAP_generic &&
-            soap_package->u.generic->no == 0)
+        c->no_redirects++;
+        if (c->no_redirects > 10)
         {
-            Z_SRW_PDU *sr = (Z_SRW_PDU*) soap_package->u.generic->p;
-            if (sr->which == Z_SRW_searchRetrieve_response)
-                handle_srw_response(c, sr->u.response);
-            else
-                ret = -1;
+            set_HTTP_error(c, hres->code, 0, 0);
+            c->no_redirects = 0;
+            do_close(c);
         }
-        else if (!ret && (soap_package->which == Z_SOAP_fault
-                          || soap_package->which == Z_SOAP_error))
+        else
         {
-            set_HTTP_error(c, hres->code,
-                           soap_package->u.fault->fault_code,
-                           soap_package->u.fault->fault_string);
+            /* 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);
+            send_SRW_redirect(c, location, hres);
+            /* we're OK for now. Operation is not really complete */
+            ret = 0;
+            cret = zoom_pending;
         }
+    }
+    else 
+    {   /* not redirect (normal response) */
+        if (!yaz_srw_check_content_type(hres))
+            addinfo = "content-type";
         else
-            ret = -1;
+        {
+            Z_SOAP *soap_package = 0;
+            ODR o = c->odr_in;
+            Z_SOAP_Handler soap_handlers[2] = {
+                {YAZ_XMLNS_SRU_v1_1, 0, (Z_SOAP_fun) yaz_srw_codec},
+                {0, 0, 0}
+            };
+            ret = z_soap_codec(o, &soap_package,
+                               &hres->content_buf, &hres->content_len,
+                               soap_handlers);
+            if (!ret && soap_package->which == Z_SOAP_generic &&
+                soap_package->u.generic->no == 0)
+            {
+                Z_SRW_PDU *sr = (Z_SRW_PDU*) soap_package->u.generic->p;
+                
+                ZOOM_options_set(c->options, "sru_version", sr->srw_version);
+                ZOOM_options_setl(c->options, "sru_extra_response_data",
+                                  sr->extraResponseData_buf, sr->extraResponseData_len);
+                if (sr->which == Z_SRW_searchRetrieve_response)
+                    cret = handle_srw_response(c, sr->u.response);
+                else if (sr->which == Z_SRW_scan_response)
+                    handle_srw_scan_response(c, sr->u.scan_response);
+                else
+                    ret = -1;
+            }
+            else if (!ret && (soap_package->which == Z_SOAP_fault
+                              || soap_package->which == Z_SOAP_error))
+            {
+                set_HTTP_error(c, hres->code,
+                               soap_package->u.fault->fault_code,
+                               soap_package->u.fault->fault_string);
+            }
+            else
+                ret = -1;
+        }   
+        if (ret == 0)
+        {
+            if (c->no_redirects) /* end of redirect. change hosts again */
+                do_close(c);
+        }
+        c->no_redirects = 0;
     }
     if (ret)
     {
@@ -3802,18 +4349,35 @@ static void handle_http(ZOOM_connection c, Z_HTTP_Response *hres)
             set_ZOOM_error(c, ZOOM_ERROR_DECODE, addinfo);
         do_close(c);
     }
-    ZOOM_connection_remove_task(c);
-    if (!strcmp(hres->version, "1.0"))
+    if (cret == zoom_complete)
     {
-        /* HTTP 1.0: only if Keep-Alive we stay alive.. */
-        if (!connection_head || strcmp(connection_head, "Keep-Alive"))
-            do_close(c);
+        yaz_log(YLOG_LOG, "removing tasks in handle_http");
+        ZOOM_connection_remove_task(c);
     }
-    else 
     {
-        /* HTTP 1.1: only if no close we stay alive .. */
-        if (connection_head && !strcmp(connection_head, "close"))
+        int must_close = 0;
+        if (!strcmp(hres->version, "1.0"))
+        {
+            /* HTTP 1.0: only if Keep-Alive we stay alive.. */
+            if (!connection_head || strcmp(connection_head, "Keep-Alive"))
+                must_close = 1;
+        }
+        else
+        {
+            /* HTTP 1.1: only if no close we stay alive.. */
+            if (connection_head && !strcmp(connection_head, "close"))
+                must_close = 1;
+        }
+        if (must_close)
+        {
             do_close(c);
+            if (c->tasks)
+            {
+                c->tasks->running = 0;
+                ZOOM_connection_insert_task(c, ZOOM_TASK_CONNECT);
+                c->reconnect_ok = 0;
+            }
+        }
     }
 }
 #endif
@@ -3853,23 +4417,36 @@ static int do_read(ZOOM_connection c)
         {
             int x;
             int err = odr_geterrorx(c->odr_in, &x);
-            char msg[60];
+            char msg[100];
             const char *element = odr_getelement(c->odr_in);
-            sprintf(msg, "ODR code %d:%d element=%-20s",
-                    err, x, element ? element : "<unknown>");
+            yaz_snprintf(msg, sizeof(msg),
+                    "ODR code %d:%d element=%s offset=%d",
+                    err, x, element ? element : "<unknown>",
+                    odr_offset(c->odr_in));
             set_ZOOM_error(c, ZOOM_ERROR_DECODE, msg);
+            if (log_api)
+            {
+                FILE *ber_file = yaz_log_file();
+                if (ber_file)
+                    odr_dumpBER(ber_file, c->buf_in, r);
+            }
             do_close(c);
         }
-        else if (gdu->which == Z_GDU_Z3950)
-            recv_apdu(c, gdu->u.z3950);
-        else if (gdu->which == Z_GDU_HTTP_Response)
+        else
         {
+            if (c->odr_print)
+                z_GDU(c->odr_print, &gdu, 0, 0);
+            if (gdu->which == Z_GDU_Z3950)
+                recv_apdu(c, gdu->u.z3950);
+            else if (gdu->which == Z_GDU_HTTP_Response)
+            {
 #if YAZ_HAVE_XML2
-            handle_http(c, gdu->u.HTTP_Response);
+                handle_http(c, gdu->u.HTTP_Response);
 #else
-            set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
-            do_close(c);
+                set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
+                do_close(c);
 #endif
+            }
         }
         c->reconnect_ok = 0;
     }
@@ -4299,9 +4876,15 @@ ZOOM_API(int) ZOOM_connection_get_timeout(ZOOM_connection c)
     return ZOOM_options_get_int(c->options, "timeout", 30);
 }
 
+ZOOM_API(void) ZOOM_connection_close(ZOOM_connection c)
+{
+    do_close(c);
+}
+
 /*
  * Local variables:
  * c-basic-offset: 4
+ * c-file-style: "Stroustrup"
  * indent-tabs-mode: nil
  * End:
  * vim: shiftwidth=4 tabstop=8 expandtab