Changed ZOOM_query_ccl2rpn function so that it takes a CCL config
[yaz-moved-to-github.git] / src / zoom-c.c
1 /*
2  * Copyright (C) 1995-2005, Index Data ApS
3  * See the file LICENSE for details.
4  *
5  * $Id: zoom-c.c,v 1.78 2006-06-15 10:34:17 adam Exp $
6  */
7 /**
8  * \file zoom-c.c
9  * \brief Implements ZOOM C interface.
10  */
11
12 #include <assert.h>
13 #include <string.h>
14 #include <errno.h>
15 #include "zoom-p.h"
16
17 #include <yaz/yaz-util.h>
18 #include <yaz/xmalloc.h>
19 #include <yaz/otherinfo.h>
20 #include <yaz/log.h>
21 #include <yaz/pquery.h>
22 #include <yaz/marcdisp.h>
23 #include <yaz/diagbib1.h>
24 #include <yaz/charneg.h>
25 #include <yaz/ill.h>
26 #include <yaz/srw.h>
27 #include <yaz/cql.h>
28 #include <yaz/ccl.h>
29
30 #if HAVE_SYS_TYPES_H
31 #include <sys/types.h>
32 #endif
33 #if HAVE_SYS_TIME_H
34 #include <sys/time.h>
35 #endif
36 #if HAVE_SYS_POLL_H
37 #include <sys/poll.h>
38 #endif
39 #if HAVE_SYS_SELECT_H
40 #include <sys/select.h>
41 #endif
42 #ifdef WIN32
43 #include <winsock.h>
44 #endif
45
46 static int log_api = 0;
47 static int log_details = 0;
48
49 typedef enum {
50     zoom_pending,
51     zoom_complete
52 } zoom_ret;
53
54 static zoom_ret ZOOM_connection_send_init (ZOOM_connection c);
55 static zoom_ret do_write_ex (ZOOM_connection c, char *buf_out, int len_out);
56 static char *cql2pqf(ZOOM_connection c, const char *cql);
57
58 static void initlog()
59 {
60     static int log_level_initialized = 0;
61     if (!log_level_initialized)
62     {
63         log_api = yaz_log_module_level("zoom");
64         log_details = yaz_log_module_level("zoomdetails");
65         log_level_initialized = 1;
66     }
67 }
68
69 static ZOOM_Event ZOOM_Event_create (int kind)
70 {
71     ZOOM_Event event = (ZOOM_Event) xmalloc (sizeof(*event));
72     event->kind = kind;
73     event->next = 0;
74     event->prev = 0;
75     return event;
76 }
77
78 static void ZOOM_Event_destroy (ZOOM_Event event)
79 {
80     xfree (event);
81 }
82
83 static void ZOOM_connection_put_event (ZOOM_connection c, ZOOM_Event event)
84 {
85     if (c->m_queue_back)
86     {
87         c->m_queue_back->prev = event;
88         assert (c->m_queue_front);
89     }
90     else
91     {
92         assert (!c->m_queue_front);
93         c->m_queue_front = event;
94     }
95     event->next = c->m_queue_back;
96     event->prev = 0;
97     c->m_queue_back = event;
98 }
99
100 static ZOOM_Event ZOOM_connection_get_event(ZOOM_connection c)
101 {
102     ZOOM_Event event = c->m_queue_front;
103     if (!event)
104     {
105         c->last_event = ZOOM_EVENT_NONE;
106         return 0;
107     }
108     assert (c->m_queue_back);
109     c->m_queue_front = event->prev;
110     if (c->m_queue_front)
111     {
112         assert (c->m_queue_back);
113         c->m_queue_front->next = 0;
114     }
115     else
116         c->m_queue_back = 0;
117     c->last_event = event->kind;
118     return event;
119 }
120
121
122 static void set_dset_error (ZOOM_connection c, int error,
123                             const char *dset,
124                             const char *addinfo, const char *addinfo2)
125 {
126     char *cp;
127
128     xfree (c->addinfo);
129     c->addinfo = 0;
130     c->error = error;
131     if (!c->diagset || strcmp(dset, c->diagset))
132     {
133         xfree(c->diagset);
134         c->diagset = xstrdup(dset);
135         /* remove integer part from SRW diagset .. */
136         if ((cp = strrchr(c->diagset, '/')))
137            *cp = '\0';
138     }
139     if (addinfo && addinfo2)
140     {
141         c->addinfo = (char*) xmalloc(strlen(addinfo) + strlen(addinfo2) + 2);
142         strcpy(c->addinfo, addinfo);
143         strcat(c->addinfo, addinfo2);
144     }
145     else if (addinfo)
146         c->addinfo = xstrdup(addinfo);
147     if (error)
148         yaz_log(log_api, "%p set_dset_error %s %s:%d %s %s",
149                 c, c->host_port ? c->host_port : "<>", dset, error,
150                 addinfo ? addinfo : "",
151                 addinfo2 ? addinfo2 : "");
152 }
153
154 #if HAVE_XML2
155 static void set_HTTP_error (ZOOM_connection c, int error,
156                             const char *addinfo, const char *addinfo2)
157 {
158     set_dset_error(c, error, "HTTP", addinfo, addinfo2);
159 }
160 #endif
161
162 static void set_ZOOM_error (ZOOM_connection c, int error,
163                             const char *addinfo)
164 {
165     set_dset_error(c, error, "ZOOM", addinfo, 0);
166 }
167
168 static void clear_error (ZOOM_connection c)
169 {
170     /*
171      * If an error is tied to an operation then it's ok to clear: for
172      * example, a diagnostic returned from a search is cleared by a
173      * subsequent search.  However, problems such as Connection Lost
174      * or Init Refused are not cleared, because they are not
175      * recoverable: doing another search doesn't help.
176      */
177     switch (c->error)
178     {
179     case ZOOM_ERROR_CONNECT:
180     case ZOOM_ERROR_MEMORY:
181     case ZOOM_ERROR_DECODE:
182     case ZOOM_ERROR_CONNECTION_LOST:
183     case ZOOM_ERROR_INIT:
184     case ZOOM_ERROR_INTERNAL:
185     case ZOOM_ERROR_UNSUPPORTED_PROTOCOL:
186         break;
187     default:
188         set_ZOOM_error(c, ZOOM_ERROR_NONE, 0);
189     }
190 }
191
192 ZOOM_task ZOOM_connection_add_task (ZOOM_connection c, int which)
193 {
194     ZOOM_task *taskp = &c->tasks;
195     while (*taskp)
196         taskp = &(*taskp)->next;
197     *taskp = (ZOOM_task) xmalloc (sizeof(**taskp));
198     (*taskp)->running = 0;
199     (*taskp)->which = which;
200     (*taskp)->next = 0;
201     clear_error (c);
202     return *taskp;
203 }
204
205 ZOOM_task ZOOM_connection_insert_task (ZOOM_connection c, int which)
206 {
207     ZOOM_task task = (ZOOM_task) xmalloc (sizeof(*task));
208
209     task->next = c->tasks;
210     c->tasks = task;
211
212     task->running = 0;
213     task->which = which;
214     clear_error (c);
215     return task;
216 }
217
218 void ZOOM_connection_remove_task (ZOOM_connection c)
219 {
220     ZOOM_task task = c->tasks;
221
222     if (task)
223     {
224         c->tasks = task->next;
225         switch (task->which)
226         {
227         case ZOOM_TASK_SEARCH:
228             ZOOM_resultset_destroy (task->u.search.resultset);
229             break;
230         case ZOOM_TASK_RETRIEVE:
231             ZOOM_resultset_destroy (task->u.retrieve.resultset);
232             break;
233         case ZOOM_TASK_CONNECT:
234             break;
235         case ZOOM_TASK_SCAN:
236             ZOOM_scanset_destroy (task->u.scan.scan);
237             break;
238         case ZOOM_TASK_PACKAGE:
239             ZOOM_package_destroy (task->u.package);
240             break;
241         case ZOOM_TASK_SORT:
242             ZOOM_resultset_destroy (task->u.sort.resultset);
243             ZOOM_query_destroy(task->u.sort.q);
244             break;
245         default:
246             assert (0);
247         }
248         xfree (task);
249
250         if (!c->tasks)
251         {
252             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_END);
253             ZOOM_connection_put_event(c, event);
254         }
255     }
256 }
257
258 static int ZOOM_connection_exec_task (ZOOM_connection c);
259
260 void ZOOM_connection_remove_tasks (ZOOM_connection c)
261 {
262     while (c->tasks)
263         ZOOM_connection_remove_task(c);
264 }
265
266 static ZOOM_record record_cache_lookup (ZOOM_resultset r, int pos);
267
268 ZOOM_API(ZOOM_connection)
269 ZOOM_connection_create (ZOOM_options options)
270 {
271     ZOOM_connection c = (ZOOM_connection) xmalloc (sizeof(*c));
272
273     initlog();
274
275     yaz_log(log_api, "%p ZOOM_connection_create", c);
276
277     c->proto = PROTO_Z3950;
278     c->cs = 0;
279     c->mask = 0;
280     c->reconnect_ok = 0;
281     c->state = STATE_IDLE;
282     c->addinfo = 0;
283     c->diagset = 0;
284     set_ZOOM_error(c, ZOOM_ERROR_NONE, 0);
285     c->buf_in = 0;
286     c->len_in = 0;
287     c->buf_out = 0;
288     c->len_out = 0;
289     c->resultsets = 0;
290
291     c->options = ZOOM_options_create_with_parent(options);
292
293     c->host_port = 0;
294     c->path = 0;
295     c->proxy = 0;
296     
297     c->charset = c->lang = 0;
298
299     c->cookie_out = 0;
300     c->cookie_in = 0;
301     c->client_IP = 0;
302     c->tasks = 0;
303
304     c->odr_in = odr_createmem (ODR_DECODE);
305     c->odr_out = odr_createmem (ODR_ENCODE);
306
307     c->async = 0;
308     c->support_named_resultsets = 0;
309     c->last_event = ZOOM_EVENT_NONE;
310
311     c->m_queue_front = 0;
312     c->m_queue_back = 0;
313     return c;
314 }
315
316 /* set database names. Take local databases (if set); otherwise
317    take databases given in ZURL (if set); otherwise use Default */
318 static char **set_DatabaseNames (ZOOM_connection con, ZOOM_options options,
319                                  int *num)
320 {
321     char **databaseNames;
322     const char *cp = ZOOM_options_get (options, "databaseName");
323     
324     if ((!cp || !*cp) && con->host_port)
325     {
326         if (strncmp (con->host_port, "unix:", 5) == 0)
327             cp = strchr(con->host_port+5, ':');
328         else
329             cp = strchr(con->host_port, '/');
330         if (cp)
331             cp++;
332     }
333     if (!cp)
334         cp = "Default";
335     nmem_strsplit(con->odr_out->mem, "+", cp,  &databaseNames, num);
336     return databaseNames;
337 }
338
339 ZOOM_API(ZOOM_connection)
340 ZOOM_connection_new (const char *host, int portnum)
341 {
342     ZOOM_connection c = ZOOM_connection_create (0);
343
344     ZOOM_connection_connect (c, host, portnum);
345     return c;
346 }
347
348 static zoom_sru_mode get_sru_mode_from_string(const char *s)
349 {
350     if (!s || !*s)
351         return zoom_sru_soap;
352     if (!yaz_matchstr(s, "soap"))
353         return zoom_sru_soap;
354     else if (!yaz_matchstr(s, "get"))
355         return zoom_sru_get;
356     else if (!yaz_matchstr(s, "post"))
357         return zoom_sru_post;
358     return zoom_sru_error;
359 }
360
361 ZOOM_API(void)
362 ZOOM_connection_connect(ZOOM_connection c,
363                         const char *host, int portnum)
364 {
365     const char *val;
366     ZOOM_task task;
367
368     initlog();
369
370     yaz_log(log_api, "%p ZOOM_connection_connect host=%s portnum=%d",
371             c, host, portnum);
372
373     if (c->cs)
374     {
375         yaz_log(log_details, "%p ZOOM_connection_connect reconnect ok", c);
376         c->reconnect_ok = 1;
377         return;
378     }
379     yaz_log(log_details, "%p ZOOM_connection_connect connect", c);
380     xfree (c->proxy);
381     val = ZOOM_options_get (c->options, "proxy");
382     if (val && *val)
383     {
384         yaz_log(log_details, "%p ZOOM_connection_connect proxy=%s", c, val);
385         c->proxy = xstrdup (val);
386     }
387     else
388         c->proxy = 0;
389
390     xfree (c->charset);
391     val = ZOOM_options_get (c->options, "charset");
392     if (val && *val)
393     {
394         yaz_log(log_details, "%p ZOOM_connection_connect charset=%s", c, val);
395         c->charset = xstrdup (val);
396     }
397     else
398         c->charset = 0;
399
400     xfree (c->lang);
401     val = ZOOM_options_get (c->options, "lang");
402     if (val && *val)
403     {
404         yaz_log(log_details, "%p ZOOM_connection_connect lang=%s", c, val);
405         c->lang = xstrdup (val);
406     }
407     else
408         c->lang = 0;
409
410     val = ZOOM_options_get (c->options, "sru");
411     c->sru_mode = get_sru_mode_from_string(val);
412
413     xfree (c->host_port);
414     if (portnum)
415     {
416         char hostn[128];
417         sprintf (hostn, "%.80s:%d", host, portnum);
418         c->host_port = xstrdup(hostn);
419     }
420     else
421         c->host_port = xstrdup(host);
422
423     {
424         /*
425          * If the "<scheme>:" part of the host string is preceded by one
426          * or more comma-separated <name>=<value> pairs, these are taken
427          * to be options to be set on the connection object.  Among other
428          * applications, this facility can be used to embed authentication
429          * in a host string:
430          *          user=admin,password=secret,tcp:localhost:9999
431          */
432         char *remainder = c->host_port;
433         char *pcolon = strchr(remainder, ':');
434         char *pcomma;
435         char *pequals;
436         while ((pcomma = strchr(remainder, ',')) != 0 &&
437                (pcolon == 0 || pcomma < pcolon)) {
438             *pcomma = '\0';
439             if ((pequals = strchr(remainder, '=')) != 0) {
440                 *pequals = '\0';
441                 /*printf("# setting '%s'='%s'\n", remainder, pequals+1);*/
442                 ZOOM_connection_option_set(c, remainder, pequals+1);
443             }
444             remainder = pcomma+1;
445         }
446
447         if (remainder != c->host_port) {
448             xfree(c->host_port);
449             c->host_port = xstrdup(remainder);
450             /*printf("# reset hp='%s'\n", remainder);*/
451         }
452     }
453
454     ZOOM_options_set(c->options, "host", c->host_port);
455
456     val = ZOOM_options_get (c->options, "cookie");
457     if (val && *val)
458     { 
459         yaz_log(log_details, "%p ZOOM_connection_connect cookie=%s", c, val);
460         c->cookie_out = xstrdup (val);
461     }
462
463     val = ZOOM_options_get (c->options, "clientIP");
464     if (val && *val)
465     {
466         yaz_log(log_details, "%p ZOOM_connection_connect clientIP=%s",
467                 c, val);
468         c->client_IP = xstrdup (val);
469     }
470
471     c->async = ZOOM_options_get_bool (c->options, "async", 0);
472     yaz_log(log_details, "%p ZOOM_connection_connect async=%d", c, c->async);
473  
474     set_ZOOM_error(c, ZOOM_ERROR_NONE, 0);
475
476     task = ZOOM_connection_add_task (c, ZOOM_TASK_CONNECT);
477
478     if (!c->async)
479     {
480         while (ZOOM_event (1, &c))
481             ;
482     }
483 }
484
485 ZOOM_API(ZOOM_query)
486 ZOOM_query_create(void)
487 {
488     ZOOM_query s = (ZOOM_query) xmalloc (sizeof(*s));
489
490     yaz_log(log_details, "%p ZOOM_query_create", s);
491     s->refcount = 1;
492     s->z_query = 0;
493     s->sort_spec = 0;
494     s->odr = odr_createmem (ODR_ENCODE);
495     s->query_string = 0;
496
497     return s;
498 }
499
500 ZOOM_API(void)
501 ZOOM_query_destroy(ZOOM_query s)
502 {
503     if (!s)
504         return;
505
506     (s->refcount)--;
507     yaz_log(log_details, "%p ZOOM_query_destroy count=%d", s, s->refcount);
508     if (s->refcount == 0)
509     {
510         odr_destroy (s->odr);
511         xfree (s);
512     }
513 }
514
515 ZOOM_API(int)
516 ZOOM_query_prefix(ZOOM_query s, const char *str)
517 {
518     s->query_string = odr_strdup(s->odr, str);
519     s->z_query = (Z_Query *) odr_malloc (s->odr, sizeof(*s->z_query));
520     s->z_query->which = Z_Query_type_1;
521     s->z_query->u.type_1 =  p_query_rpn(s->odr, PROTO_Z3950, str);
522     if (!s->z_query->u.type_1)
523     {
524         yaz_log(log_details, "%p ZOOM_query_prefix str=%s failed", s, str);
525         s->z_query = 0;
526         return -1;
527     }
528     yaz_log(log_details, "%p ZOOM_query_prefix str=%s", s, str);
529     return 0;
530 }
531
532 ZOOM_API(int)
533 ZOOM_query_cql(ZOOM_query s, const char *str)
534 {
535     Z_External *ext;
536
537     s->query_string = odr_strdup(s->odr, str);
538
539     ext = (Z_External *) odr_malloc(s->odr, sizeof(*ext));
540     ext->direct_reference = odr_getoidbystr(s->odr, "1.2.840.10003.16.2");
541     ext->indirect_reference = 0;
542     ext->descriptor = 0;
543     ext->which = Z_External_CQL;
544     ext->u.cql = s->query_string;
545     
546     s->z_query = (Z_Query *) odr_malloc (s->odr, sizeof(*s->z_query));
547     s->z_query->which = Z_Query_type_104;
548     s->z_query->u.type_104 =  ext;
549
550     yaz_log(log_details, "%p ZOOM_query_cql str=%s", s, str);
551
552     return 0;
553 }
554
555 /*
556  * Translate the CQL string client-side into RPN which is passed to
557  * the server.  This is useful for server's that don't themselves
558  * support CQL, for which ZOOM_query_cql() is useless.  `conn' is used
559  * only as a place to stash diagnostics if compilation fails; if this
560  * information is not needed, a null pointer may be used.
561  */
562 ZOOM_API(int)
563 ZOOM_query_cql2rpn(ZOOM_query s, const char *str, ZOOM_connection conn)
564 {
565     char *rpn;
566     int ret;
567     ZOOM_connection freeme = 0;
568
569     yaz_log(log_details, "%p ZOOM_query_cql2rpn str=%s conn=%p", s, str, conn);
570     if (conn == 0)
571         conn = freeme = ZOOM_connection_create(0);
572
573     rpn = cql2pqf(conn, str);
574     if (freeme != 0)
575         ZOOM_connection_destroy(freeme);
576     if (rpn == 0)
577         return -1;
578
579     ret = ZOOM_query_prefix(s, rpn);
580     xfree(rpn);
581     return ret;
582 }
583
584 /*
585  * Analogous in every way to ZOOM_query_cql2rpn(), except that there
586  * is no analogous ZOOM_query_ccl() that just sends uninterpreted CCL
587  * to the server, as the YAZ GFS doesn't know how to handle this.
588  */
589 ZOOM_API(int)
590     ZOOM_query_ccl2rpn(ZOOM_query s, const char *str, const char *config,
591                        int *ccl_error, const char **error_string,
592                        int *error_pos)
593 {
594     int ret;
595     struct ccl_rpn_node *rpn;
596     CCL_bibset bibset = ccl_qual_mk();
597
598     if (config)
599         ccl_qual_buf(bibset, config);
600
601     rpn = ccl_find_str(bibset, str, ccl_error, error_pos);
602     if (!rpn)
603     {
604         *error_string = ccl_err_msg(*ccl_error);
605         ret = -1;
606     }
607     else
608     {
609         WRBUF wr = wrbuf_alloc();
610         ccl_pquery(wr, rpn);
611         ccl_rpn_delete(rpn);
612         ret = ZOOM_query_prefix(s, wrbuf_buf(wr));
613         wrbuf_free(wr, 1);
614     }
615     ccl_qual_rm(&bibset);
616     return ret;
617 }
618
619 ZOOM_API(int)
620 ZOOM_query_sortby(ZOOM_query s, const char *criteria)
621 {
622     s->sort_spec = yaz_sort_spec (s->odr, criteria);
623     if (!s->sort_spec)
624     {
625         yaz_log(log_details, "%p ZOOM_query_sortby criteria=%s failed",
626                 s, criteria);
627         return -1;
628     }
629     yaz_log(log_details, "%p ZOOM_query_sortby criteria=%s", s, criteria);
630     return 0;
631 }
632
633 static zoom_ret do_write(ZOOM_connection c);
634
635 ZOOM_API(void)
636 ZOOM_connection_destroy(ZOOM_connection c)
637 {
638     ZOOM_resultset r;
639     if (!c)
640         return;
641     yaz_log(log_api, "%p ZOOM_connection_destroy", c);
642     if (c->cs)
643         cs_close (c->cs);
644     for (r = c->resultsets; r; r = r->next)
645         r->connection = 0;
646
647     xfree (c->buf_in);
648     xfree (c->addinfo);
649     xfree (c->diagset);
650     odr_destroy (c->odr_in);
651     odr_destroy (c->odr_out);
652     ZOOM_options_destroy (c->options);
653     ZOOM_connection_remove_tasks (c);
654     xfree (c->host_port);
655     xfree (c->path);
656     xfree (c->proxy);
657     xfree (c->charset);
658     xfree (c->lang);
659     xfree (c->cookie_out);
660     xfree (c->cookie_in);
661     xfree (c->client_IP);
662     xfree (c);
663 }
664
665 void ZOOM_resultset_addref (ZOOM_resultset r)
666 {
667     if (r)
668     {
669         (r->refcount)++;
670         yaz_log(log_details, "%p ZOOM_resultset_addref count=%d",
671                 r, r->refcount);
672     }
673 }
674
675 ZOOM_resultset ZOOM_resultset_create ()
676 {
677     int i;
678     ZOOM_resultset r = (ZOOM_resultset) xmalloc (sizeof(*r));
679
680     initlog();
681
682     yaz_log(log_details, "%p ZOOM_resultset_create", r);
683     r->refcount = 1;
684     r->size = 0;
685     r->odr = odr_createmem (ODR_ENCODE);
686     r->start = 0;
687     r->piggyback = 1;
688     r->setname = 0;
689     r->schema = 0;
690     r->count = 0;
691     r->step = 0;
692     for (i = 0; i<RECORD_HASH_SIZE; i++)
693         r->record_hash[i] = 0;
694     r->r_sort_spec = 0;
695     r->query = 0;
696     r->connection = 0;
697     r->next = 0;
698     return r;
699 }
700
701 ZOOM_API(ZOOM_resultset)
702 ZOOM_connection_search_pqf(ZOOM_connection c, const char *q)
703 {
704     ZOOM_resultset r;
705     ZOOM_query s = ZOOM_query_create();
706
707     ZOOM_query_prefix (s, q);
708
709     r = ZOOM_connection_search (c, s);
710     ZOOM_query_destroy (s);
711     return r;
712 }
713
714 ZOOM_API(ZOOM_resultset)
715 ZOOM_connection_search(ZOOM_connection c, ZOOM_query q)
716 {
717     ZOOM_resultset r = ZOOM_resultset_create ();
718     ZOOM_task task;
719     const char *cp;
720
721     yaz_log(log_api, "%p ZOOM_connection_search set %p query %p", c, r, q);
722     r->r_sort_spec = q->sort_spec;
723     r->query = q;
724
725     r->options = ZOOM_options_create_with_parent(c->options);
726
727     r->start = ZOOM_options_get_int(r->options, "start", 0);
728     r->count = ZOOM_options_get_int(r->options, "count", 0);
729     {
730         /* If "presentChunk" is defined use that; otherwise "step" */
731         const char *cp = ZOOM_options_get (r->options, "presentChunk");
732         r->step = ZOOM_options_get_int(r->options,
733                                        (cp != 0 ? "presentChunk": "step"), 0);
734     }
735     r->piggyback = ZOOM_options_get_bool (r->options, "piggyback", 1);
736     cp = ZOOM_options_get (r->options, "setname");
737     if (cp)
738         r->setname = xstrdup(cp);
739     cp = ZOOM_options_get (r->options, "schema");
740     if (cp)
741         r->schema = xstrdup(cp);
742     
743     r->connection = c;
744
745     r->next = c->resultsets;
746     c->resultsets = r;
747
748     if (c->host_port && c->proto == PROTO_HTTP)
749     {
750         if (!c->cs)
751         {
752             yaz_log(log_details, "ZOOM_connection_search: no comstack");
753             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
754         }
755         else
756         {
757             yaz_log(log_details, "ZOOM_connection_search: reconnect");
758             c->reconnect_ok = 1;
759         }
760     }
761
762     task = ZOOM_connection_add_task (c, ZOOM_TASK_SEARCH);
763     task->u.search.resultset = r;
764     ZOOM_resultset_addref (r);  
765
766     (q->refcount)++;
767
768     if (!c->async)
769     {
770         while (ZOOM_event (1, &c))
771             ;
772     }
773     return r;
774 }
775
776 /*
777  * This is the old result-set sorting API, which is maintained only
778  * for the sake of binary compatibility.  There is no reason ever to
779  * use this rather than ZOOM_resultset_sort1().
780  */
781 ZOOM_API(void)
782 ZOOM_resultset_sort(ZOOM_resultset r,
783                     const char *sort_type, const char *sort_spec)
784 {
785     (void) ZOOM_resultset_sort1(r, sort_type, sort_spec);
786 }
787
788 ZOOM_API(int)
789 ZOOM_resultset_sort1(ZOOM_resultset r,
790                      const char *sort_type, const char *sort_spec)
791 {
792     ZOOM_connection c = r->connection;
793     ZOOM_task task;
794     ZOOM_query newq;
795
796     newq = ZOOM_query_create();
797     if (ZOOM_query_sortby(newq, sort_spec) < 0)
798         return -1;
799
800     yaz_log(log_api, "%p ZOOM_resultset_sort r=%p sort_type=%s sort_spec=%s",
801             r, r, sort_type, sort_spec);
802     if (!c)
803         return 0;
804
805     if (c->host_port && c->proto == PROTO_HTTP)
806     {
807         if (!c->cs)
808         {
809             yaz_log(log_details, "%p ZOOM_resultset_sort: no comstack", r);
810             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
811         }
812         else
813         {
814             yaz_log(log_details, "%p ZOOM_resultset_sort: prepare reconnect",
815                     r);
816             c->reconnect_ok = 1;
817         }
818     }
819     
820     ZOOM_resultset_cache_reset(r);
821     task = ZOOM_connection_add_task (c, ZOOM_TASK_SORT);
822     task->u.sort.resultset = r;
823     task->u.sort.q = newq;
824
825     ZOOM_resultset_addref (r);  
826
827     if (!c->async)
828     {
829         while (ZOOM_event (1, &c))
830             ;
831     }
832
833     return 0;
834 }
835
836 ZOOM_API(void)
837     ZOOM_resultset_cache_reset(ZOOM_resultset r)
838 {
839     int i;
840     for (i = 0; i<RECORD_HASH_SIZE; i++)
841     {
842         ZOOM_record_cache rc;
843         for (rc = r->record_hash[i]; rc; rc = rc->next)
844         {
845             if (rc->rec.wrbuf_marc)
846                 wrbuf_free (rc->rec.wrbuf_marc, 1);
847             if (rc->rec.wrbuf_iconv)
848                 wrbuf_free (rc->rec.wrbuf_iconv, 1);
849             if (rc->rec.wrbuf_opac)
850                 wrbuf_free (rc->rec.wrbuf_opac, 1);
851         }
852         r->record_hash[i] = 0;
853     }
854 }
855
856 ZOOM_API(void)
857 ZOOM_resultset_destroy(ZOOM_resultset r)
858 {
859     if (!r)
860         return;
861     (r->refcount)--;
862     yaz_log(log_details, "%p ZOOM_resultset_destroy r=%p count=%d",
863             r, r, r->refcount);
864     if (r->refcount == 0)
865     {
866         ZOOM_resultset_cache_reset(r);
867
868         if (r->connection)
869         {
870             /* remove ourselves from the resultsets in connection */
871             ZOOM_resultset *rp = &r->connection->resultsets;
872             while (1)
873             {
874                 assert (*rp);   /* we must be in this list!! */
875                 if (*rp == r)
876                 {   /* OK, we're here - take us out of it */
877                     *rp = (*rp)->next;
878                     break;
879                 }
880                 rp = &(*rp)->next;
881             }
882         }
883         ZOOM_query_destroy (r->query);
884         ZOOM_options_destroy (r->options);
885         odr_destroy (r->odr);
886         xfree (r->setname);
887         xfree (r->schema);
888         xfree (r);
889     }
890 }
891
892 ZOOM_API(size_t)
893 ZOOM_resultset_size (ZOOM_resultset r)
894 {
895     return r->size;
896 }
897
898 static void do_close (ZOOM_connection c)
899 {
900     if (c->cs)
901         cs_close(c->cs);
902     c->cs = 0;
903     c->mask = 0;
904     c->state = STATE_IDLE;
905 }
906
907 static void ZOOM_resultset_retrieve (ZOOM_resultset r,
908                                      int force_sync, int start, int count)
909 {
910     ZOOM_task task;
911     ZOOM_connection c;
912     const char *cp;
913
914     if (!r)
915         return;
916     yaz_log(log_details, "%p ZOOM_resultset_retrieve force_sync=%d start=%d"
917             " count=%d", r, force_sync, start, count);
918     c = r->connection;
919     if (!c)
920         return;
921
922     if (c->host_port && c->proto == PROTO_HTTP)
923     {
924         if (!c->cs)
925         {
926             yaz_log(log_details, "%p ZOOM_resultset_retrieve: no comstack", r);
927             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
928         }
929         else
930         {
931             yaz_log(log_details, "%p ZOOM_resultset_retrieve: prepare "
932                     "reconnect", r);
933             c->reconnect_ok = 1;
934         }
935     }
936     task = ZOOM_connection_add_task (c, ZOOM_TASK_RETRIEVE);
937     task->u.retrieve.resultset = r;
938     task->u.retrieve.start = start;
939     task->u.retrieve.count = count;
940
941     cp = ZOOM_options_get (r->options, "schema");
942     if (cp)
943     {
944         if (!r->schema || strcmp(r->schema, cp))
945         {
946             xfree(r->schema);
947             r->schema = xstrdup(cp);
948         }
949     }
950
951     ZOOM_resultset_addref (r);
952
953     if (!r->connection->async || force_sync)
954         while (r->connection && ZOOM_event (1, &r->connection))
955             ;
956 }
957
958 ZOOM_API(void)
959 ZOOM_resultset_records (ZOOM_resultset r, ZOOM_record *recs,
960                         size_t start, size_t count)
961 {
962     int force_present = 0;
963
964     if (!r)
965         return ;
966     yaz_log(log_api, "%p ZOOM_resultset_records r=%p start=%ld count=%ld",
967             r, r, (long) start, (long) count);
968     if (count && recs)
969         force_present = 1;
970     ZOOM_resultset_retrieve (r, force_present, start, count);
971     if (force_present)
972     {
973         size_t i;
974         for (i = 0; i< count; i++)
975             recs[i] = ZOOM_resultset_record_immediate (r, i+start);
976     }
977 }
978
979 static void get_cert(ZOOM_connection c)
980 {
981     char *cert_buf;
982     int cert_len;
983     
984     if (cs_get_peer_certificate_x509(c->cs, &cert_buf, &cert_len))
985     {
986         ZOOM_connection_option_setl(c, "sslPeerCert",
987                                     cert_buf, cert_len);
988         xfree(cert_buf);
989     }
990 }
991
992 static zoom_ret do_connect (ZOOM_connection c)
993 {
994     void *add;
995     const char *effective_host;
996
997     if (c->proxy)
998         effective_host = c->proxy;
999     else
1000         effective_host = c->host_port;
1001
1002     yaz_log(log_details, "%p do_connect effective_host=%s", c, effective_host);
1003
1004     if (c->cs)
1005         cs_close(c->cs);
1006     c->cs = cs_create_host (effective_host, 0, &add);
1007
1008     if (c->cs && c->cs->protocol == PROTO_HTTP)
1009     {
1010 #if HAVE_XML2
1011         const char *path = 0;
1012
1013         c->proto = PROTO_HTTP;
1014         cs_get_host_args(c->host_port, &path);
1015         xfree(c->path);
1016         c->path = (char*) xmalloc(strlen(path)+2);
1017         c->path[0] = '/';
1018         strcpy (c->path+1, path);
1019 #else
1020         set_ZOOM_error(c, ZOOM_ERROR_UNSUPPORTED_PROTOCOL, "SRW");
1021         do_close(c);
1022         return zoom_complete;
1023 #endif
1024     }
1025     if (c->cs)
1026     {
1027         int ret = cs_connect (c->cs, add);
1028         if (ret == 0)
1029         {
1030             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
1031             ZOOM_connection_put_event(c, event);
1032             get_cert(c);
1033             if (c->proto == PROTO_Z3950)
1034                 ZOOM_connection_send_init(c);
1035             else
1036             {
1037                 /* no init request for SRW .. */
1038                 assert (c->tasks->which == ZOOM_TASK_CONNECT);
1039                 ZOOM_connection_remove_task (c);
1040                 c->mask = 0;
1041                 ZOOM_connection_exec_task (c);
1042             }
1043             c->state = STATE_ESTABLISHED;
1044             return zoom_pending;
1045         }
1046         else if (ret > 0)
1047         {
1048             c->state = STATE_CONNECTING; 
1049             c->mask = ZOOM_SELECT_EXCEPT;
1050             if (c->cs->io_pending & CS_WANT_WRITE)
1051                 c->mask += ZOOM_SELECT_WRITE;
1052             if (c->cs->io_pending & CS_WANT_READ)
1053                 c->mask += ZOOM_SELECT_READ;
1054             return zoom_pending;
1055         }
1056     }
1057     c->state = STATE_IDLE;
1058     set_ZOOM_error(c, ZOOM_ERROR_CONNECT, c->host_port);
1059     return zoom_complete;
1060 }
1061
1062 int z3950_connection_socket(ZOOM_connection c)
1063 {
1064     if (c->cs)
1065         return cs_fileno(c->cs);
1066     return -1;
1067 }
1068
1069 int z3950_connection_mask(ZOOM_connection c)
1070 {
1071     if (c->cs)
1072         return c->mask;
1073     return 0;
1074 }
1075
1076 static void otherInfo_attach (ZOOM_connection c, Z_APDU *a, ODR out)
1077 {
1078     int i;
1079     for (i = 0; i<200; i++)
1080     {
1081         size_t len;
1082         Z_OtherInformation **oi;
1083         char buf[80];
1084         const char *val;
1085         const char *cp;
1086         int oidval;
1087
1088         sprintf (buf, "otherInfo%d", i);
1089         val = ZOOM_options_get (c->options, buf);
1090         if (!val)
1091             break;
1092         cp = strchr(val, ':');
1093         if (!cp)
1094             continue;
1095         len = cp - val;
1096         if (len >= sizeof(buf))
1097             len = sizeof(buf)-1;
1098         memcpy (buf, val, len);
1099         buf[len] = '\0';
1100         oidval = oid_getvalbyname (buf);
1101         if (oidval == VAL_NONE)
1102             continue;
1103         
1104         yaz_oi_APDU(a, &oi);
1105         yaz_oi_set_string_oidval(oi, out, oidval, 1, cp+1);
1106     }
1107 }
1108
1109 static int encode_APDU(ZOOM_connection c, Z_APDU *a, ODR out)
1110 {
1111     assert (a);
1112     if (c->cookie_out)
1113     {
1114         Z_OtherInformation **oi;
1115         yaz_oi_APDU(a, &oi);
1116         yaz_oi_set_string_oidval(oi, out, VAL_COOKIE, 1, c->cookie_out);
1117     }
1118     if (c->client_IP)
1119     {
1120         Z_OtherInformation **oi;
1121         yaz_oi_APDU(a, &oi);
1122         yaz_oi_set_string_oidval(oi, out, VAL_CLIENT_IP, 1, c->client_IP);
1123     }
1124     otherInfo_attach (c, a, out);
1125     if (!z_APDU(out, &a, 0, 0))
1126     {
1127         FILE *outf = fopen("/tmp/apdu.txt", "a");
1128         if (a && outf)
1129         {
1130             ODR odr_pr = odr_createmem(ODR_PRINT);
1131             fprintf (outf, "a=%p\n", a);
1132             odr_setprint(odr_pr, outf);
1133             z_APDU(odr_pr, &a, 0, 0);
1134             odr_destroy(odr_pr);
1135         }
1136         yaz_log(log_api, "%p encoding_APDU: encoding failed", c);
1137         set_ZOOM_error(c, ZOOM_ERROR_ENCODE, 0);
1138         odr_reset(out);
1139         return -1;
1140     }
1141     yaz_log(log_details, "%p encoding_APDU encoding OK", c);
1142     return 0;
1143 }
1144
1145 static zoom_ret send_APDU (ZOOM_connection c, Z_APDU *a)
1146 {
1147     ZOOM_Event event;
1148     assert (a);
1149     if (encode_APDU(c, a, c->odr_out))
1150         return zoom_complete;
1151     yaz_log(log_details, "%p send APDU type=%d", c, a->which);
1152     c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
1153     event = ZOOM_Event_create (ZOOM_EVENT_SEND_APDU);
1154     ZOOM_connection_put_event (c, event);
1155     odr_reset(c->odr_out);
1156     return do_write (c);
1157 }
1158
1159 /* returns 1 if PDU was sent OK (still pending )
1160            0 if PDU was not sent OK (nothing to wait for) 
1161 */
1162
1163 static zoom_ret ZOOM_connection_send_init (ZOOM_connection c)
1164 {
1165     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_initRequest);
1166     Z_InitRequest *ireq = apdu->u.initRequest;
1167     Z_IdAuthentication *auth = (Z_IdAuthentication *)
1168         odr_malloc(c->odr_out, sizeof(*auth));
1169     const char *auth_groupId = ZOOM_options_get (c->options, "group");
1170     const char *auth_userId = ZOOM_options_get (c->options, "user");
1171     const char *auth_password = ZOOM_options_get (c->options, "password");
1172     char *version;
1173
1174     /* support the pass for backwards compatibility */
1175     if (!auth_password)
1176         auth_password = ZOOM_options_get (c->options, "pass");
1177         
1178     ODR_MASK_SET(ireq->options, Z_Options_search);
1179     ODR_MASK_SET(ireq->options, Z_Options_present);
1180     ODR_MASK_SET(ireq->options, Z_Options_scan);
1181     ODR_MASK_SET(ireq->options, Z_Options_sort);
1182     ODR_MASK_SET(ireq->options, Z_Options_extendedServices);
1183     ODR_MASK_SET(ireq->options, Z_Options_namedResultSets);
1184     
1185     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_1);
1186     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_2);
1187     ODR_MASK_SET(ireq->protocolVersion, Z_ProtocolVersion_3);
1188
1189     /* Index Data's Z39.50 Implementor Id is 81 */
1190     ireq->implementationId = odr_prepend(c->odr_out,
1191         ZOOM_options_get(c->options, "implementationId"),
1192         odr_prepend(c->odr_out, "81", ireq->implementationId));
1193
1194     ireq->implementationName = odr_prepend(c->odr_out,
1195         ZOOM_options_get(c->options, "implementationName"),
1196         odr_prepend(c->odr_out, "ZOOM-C", ireq->implementationName));
1197
1198     version = odr_strdup(c->odr_out, "$Revision: 1.78 $");
1199     if (strlen(version) > 10)   /* check for unexpanded CVS strings */
1200         version[strlen(version)-2] = '\0';
1201     ireq->implementationVersion = odr_prepend(c->odr_out,
1202         ZOOM_options_get(c->options, "implementationVersion"),
1203         odr_prepend(c->odr_out, &version[11], ireq->implementationVersion));
1204
1205     *ireq->maximumRecordSize =
1206         ZOOM_options_get_int (c->options, "maximumRecordSize", 1024*1024);
1207     *ireq->preferredMessageSize =
1208         ZOOM_options_get_int (c->options, "preferredMessageSize", 1024*1024);
1209     
1210     if (auth_groupId || auth_password)
1211     {
1212         Z_IdPass *pass = (Z_IdPass *) odr_malloc(c->odr_out, sizeof(*pass));
1213         int i = 0;
1214         pass->groupId = 0;
1215         if (auth_groupId && *auth_groupId)
1216         {
1217             pass->groupId = (char *)
1218                 odr_malloc(c->odr_out, strlen(auth_groupId)+1);
1219             strcpy(pass->groupId, auth_groupId);
1220             i++;
1221         }
1222         pass->userId = 0;
1223         if (auth_userId && *auth_userId)
1224         {
1225             pass->userId = (char *)
1226                 odr_malloc(c->odr_out, strlen(auth_userId)+1);
1227             strcpy(pass->userId, auth_userId);
1228             i++;
1229         }
1230         pass->password = 0;
1231         if (auth_password && *auth_password)
1232         {
1233             pass->password = (char *)
1234                 odr_malloc(c->odr_out, strlen(auth_password)+1);
1235             strcpy(pass->password, auth_password);
1236             i++;
1237         }
1238         if (i)
1239         {
1240             auth->which = Z_IdAuthentication_idPass;
1241             auth->u.idPass = pass;
1242             ireq->idAuthentication = auth;
1243         }
1244     }
1245     else if (auth_userId)
1246     {
1247         auth->which = Z_IdAuthentication_open;
1248         auth->u.open = (char *)
1249             odr_malloc(c->odr_out, strlen(auth_userId)+1);
1250         strcpy(auth->u.open, auth_userId);
1251         ireq->idAuthentication = auth;
1252     }
1253     if (c->proxy)
1254         yaz_oi_set_string_oidval(&ireq->otherInfo, c->odr_out,
1255                                  VAL_PROXY, 1, c->host_port);
1256     if (c->charset || c->lang)
1257     {
1258         Z_OtherInformation **oi;
1259         Z_OtherInformationUnit *oi_unit;
1260         
1261         yaz_oi_APDU(apdu, &oi);
1262         
1263         if ((oi_unit = yaz_oi_update(oi, c->odr_out, NULL, 0, 0)))
1264         {
1265             ODR_MASK_SET(ireq->options, Z_Options_negotiationModel);
1266             oi_unit->which = Z_OtherInfo_externallyDefinedInfo;
1267             oi_unit->information.externallyDefinedInfo =
1268                 yaz_set_proposal_charneg_list(c->odr_out, " ",
1269                                               c->charset, c->lang, 1);
1270         }
1271     }
1272     assert (apdu);
1273     return send_APDU (c, apdu);
1274 }
1275
1276 #if HAVE_XML2
1277 static zoom_ret send_srw (ZOOM_connection c, Z_SRW_PDU *sr)
1278 {
1279     Z_GDU *gdu;
1280     ZOOM_Event event;
1281
1282     gdu = z_get_HTTP_Request_host_path(c->odr_out, c->host_port, c->path);
1283
1284     if (c->sru_mode == zoom_sru_get)
1285     {
1286         yaz_sru_get_encode(gdu->u.HTTP_Request, sr, c->odr_out, c->charset);
1287     }
1288     else if (c->sru_mode == zoom_sru_post)
1289     {
1290         yaz_sru_post_encode(gdu->u.HTTP_Request, sr, c->odr_out, c->charset);
1291     }
1292     else if (c->sru_mode == zoom_sru_soap)
1293     {
1294         yaz_sru_post_encode(gdu->u.HTTP_Request, sr, c->odr_out, c->charset);
1295     }
1296     if (!z_GDU(c->odr_out, &gdu, 0, 0))
1297         return zoom_complete;
1298     c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
1299         
1300     event = ZOOM_Event_create (ZOOM_EVENT_SEND_APDU);
1301     ZOOM_connection_put_event (c, event);
1302     odr_reset(c->odr_out);
1303     return do_write (c);
1304 }
1305 #endif
1306
1307 #if HAVE_XML2
1308 static zoom_ret ZOOM_connection_srw_send_search(ZOOM_connection c)
1309 {
1310     int i;
1311     ZOOM_resultset resultset = 0;
1312     Z_SRW_PDU *sr = 0;
1313     const char *recordPacking = 0;
1314
1315     if (c->error)                  /* don't continue on error */
1316         return zoom_complete;
1317     assert (c->tasks);
1318     if (c->tasks->which == ZOOM_TASK_SEARCH)
1319     {
1320         resultset = c->tasks->u.search.resultset;
1321         resultset->setname = xstrdup ("default");
1322         ZOOM_options_set (resultset->options, "setname", resultset->setname);
1323     }
1324     else if(c->tasks->which == ZOOM_TASK_RETRIEVE)
1325     {
1326         resultset = c->tasks->u.retrieve.resultset;
1327
1328         resultset->start = c->tasks->u.retrieve.start;
1329         resultset->count = c->tasks->u.retrieve.count;
1330         
1331         if (resultset->start >= resultset->size)
1332             return zoom_complete;
1333         if (resultset->start + resultset->count > resultset->size)
1334             resultset->count = resultset->size - resultset->start;
1335
1336         for (i = 0; i<resultset->count; i++)
1337         {
1338             ZOOM_record rec =
1339                 record_cache_lookup (resultset, i + resultset->start);
1340             if (!rec)
1341                 break;
1342         }
1343         if (i == resultset->count)
1344             return zoom_complete;
1345     }
1346     assert(resultset->query);
1347         
1348     sr = yaz_srw_get(c->odr_out, Z_SRW_searchRetrieve_request);
1349
1350     if (resultset->query->z_query->which == Z_Query_type_104
1351         && resultset->query->z_query->u.type_104->which == Z_External_CQL)
1352     {
1353
1354         sr->u.request->query_type = Z_SRW_query_type_cql;
1355         sr->u.request->query.cql =resultset->query->z_query->u.type_104->u.cql;
1356     }
1357     else if (resultset->query->z_query->which == Z_Query_type_1 &&
1358              resultset->query->z_query->u.type_1)
1359     {
1360         sr->u.request->query_type = Z_SRW_query_type_pqf;
1361         sr->u.request->query.pqf = resultset->query->query_string;
1362     }
1363     else
1364     {
1365         set_ZOOM_error(c, ZOOM_ERROR_UNSUPPORTED_QUERY, 0);
1366         return zoom_complete;
1367     }
1368     sr->u.request->startRecord = odr_intdup (c->odr_out, resultset->start + 1);
1369     sr->u.request->maximumRecords = odr_intdup (
1370         c->odr_out, resultset->step>0 ? resultset->step : resultset->count);
1371     sr->u.request->recordSchema = resultset->schema;
1372
1373     recordPacking = ZOOM_resultset_option_get (resultset, "recordPacking");
1374
1375     if (recordPacking)
1376         sr->u.request->recordPacking = odr_strdup(c->odr_out, recordPacking);
1377     
1378     return send_srw(c, sr);
1379 }
1380 #else
1381 static zoom_ret ZOOM_connection_srw_send_search(ZOOM_connection c)
1382 {
1383     return zoom_complete;
1384 }
1385 #endif
1386
1387 static zoom_ret ZOOM_connection_send_search (ZOOM_connection c)
1388 {
1389     ZOOM_resultset r;
1390     int lslb, ssub, mspn;
1391     const char *syntax;
1392     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_searchRequest);
1393     Z_SearchRequest *search_req = apdu->u.searchRequest;
1394     const char *elementSetName;
1395     const char *smallSetElementSetName;
1396     const char *mediumSetElementSetName;
1397
1398     assert (c->tasks);
1399     assert (c->tasks->which == ZOOM_TASK_SEARCH);
1400
1401     r = c->tasks->u.search.resultset;
1402
1403     yaz_log(log_details, "%p ZOOM_connection_send_search set=%p", c, r);
1404
1405     elementSetName =
1406         ZOOM_options_get (r->options, "elementSetName");
1407     smallSetElementSetName  =
1408         ZOOM_options_get (r->options, "smallSetElementSetName");
1409     mediumSetElementSetName =
1410         ZOOM_options_get (r->options, "mediumSetElementSetName");
1411
1412     if (!smallSetElementSetName)
1413         smallSetElementSetName = elementSetName;
1414
1415     if (!mediumSetElementSetName)
1416         mediumSetElementSetName = elementSetName;
1417
1418     assert (r);
1419     assert (r->query);
1420
1421     /* prepare query for the search request */
1422     search_req->query = r->query->z_query;
1423     if (!search_req->query)
1424     {
1425         set_ZOOM_error(c, ZOOM_ERROR_INVALID_QUERY, 0);
1426         return zoom_complete;
1427     }
1428
1429     search_req->databaseNames =
1430         set_DatabaseNames (c, r->options, &search_req->num_databaseNames);
1431
1432     /* get syntax (no need to provide unless piggyback is in effect) */
1433     syntax = ZOOM_options_get (r->options, "preferredRecordSyntax");
1434
1435     lslb = ZOOM_options_get_int (r->options, "largeSetLowerBound", -1);
1436     ssub = ZOOM_options_get_int (r->options, "smallSetUpperBound", -1);
1437     mspn = ZOOM_options_get_int (r->options, "mediumSetPresentNumber", -1);
1438     if (lslb != -1 && ssub != -1 && mspn != -1)
1439     {
1440         /* So're a Z39.50 expert? Let's hope you don't do sort */
1441         *search_req->largeSetLowerBound = lslb;
1442         *search_req->smallSetUpperBound = ssub;
1443         *search_req->mediumSetPresentNumber = mspn;
1444     }
1445     else if (r->start == 0 && r->count > 0
1446              && r->piggyback && !r->r_sort_spec && !r->schema)
1447     {
1448         /* Regular piggyback - do it unless we're going to do sort */
1449         *search_req->largeSetLowerBound = 2000000000;
1450         *search_req->smallSetUpperBound = 1;
1451         *search_req->mediumSetPresentNumber = r->step>0 ? r->step : r->count;
1452     }
1453     else
1454     {
1455         /* non-piggyback. Need not provide elementsets or syntaxes .. */
1456         smallSetElementSetName = 0;
1457         mediumSetElementSetName = 0;
1458         syntax = 0;
1459     }
1460     if (smallSetElementSetName && *smallSetElementSetName)
1461     {
1462         Z_ElementSetNames *esn = (Z_ElementSetNames *)
1463             odr_malloc (c->odr_out, sizeof(*esn));
1464         
1465         esn->which = Z_ElementSetNames_generic;
1466         esn->u.generic = odr_strdup (c->odr_out, smallSetElementSetName);
1467         search_req->smallSetElementSetNames = esn;
1468     }
1469     if (mediumSetElementSetName && *mediumSetElementSetName)
1470     {
1471         Z_ElementSetNames *esn = (Z_ElementSetNames *)
1472             odr_malloc (c->odr_out, sizeof(*esn));
1473         
1474         esn->which = Z_ElementSetNames_generic;
1475         esn->u.generic = odr_strdup (c->odr_out, mediumSetElementSetName);
1476         search_req->mediumSetElementSetNames = esn;
1477     }
1478     if (syntax)
1479         search_req->preferredRecordSyntax =
1480             yaz_str_to_z3950oid (c->odr_out, CLASS_RECSYN, syntax);
1481     
1482     if (!r->setname)
1483     {
1484         if (c->support_named_resultsets)
1485         {
1486             char setname[14];
1487             int ord;
1488             /* find the lowest unused ordinal so that we re-use
1489                result sets on the server. */
1490             for (ord = 1; ; ord++)
1491             {
1492                 ZOOM_resultset rp;
1493                 sprintf (setname, "%d", ord);
1494                 for (rp = c->resultsets; rp; rp = rp->next)
1495                     if (rp->setname && !strcmp (rp->setname, setname))
1496                         break;
1497                 if (!rp)
1498                     break;
1499             }
1500             r->setname = xstrdup (setname);
1501             yaz_log(log_details, "%p ZOOM_connection_send_search: allocating "
1502                     "set %s", c, r->setname);
1503         }
1504         else
1505         {
1506             yaz_log(log_details, "%p ZOOM_connection_send_search: using "
1507                     "default set", c);
1508             r->setname = xstrdup ("default");
1509         }
1510         ZOOM_options_set (r->options, "setname", r->setname);
1511     }
1512     search_req->resultSetName = odr_strdup(c->odr_out, r->setname);
1513     return send_APDU (c, apdu);
1514 }
1515
1516 static void response_default_diag(ZOOM_connection c, Z_DefaultDiagFormat *r)
1517 {
1518     int oclass;
1519     char *addinfo = 0;
1520
1521     switch (r->which)
1522     {
1523     case Z_DefaultDiagFormat_v2Addinfo:
1524         addinfo = r->u.v2Addinfo;
1525         break;
1526     case Z_DefaultDiagFormat_v3Addinfo:
1527         addinfo = r->u.v3Addinfo;
1528         break;
1529     }
1530     xfree (c->addinfo);
1531     c->addinfo = 0;
1532     set_dset_error(c, *r->condition,
1533                    yaz_z3950oid_to_str(r->diagnosticSetId, &oclass),
1534                    addinfo, 0);
1535 }
1536
1537 static void response_diag(ZOOM_connection c, Z_DiagRec *p)
1538 {
1539     if (p->which != Z_DiagRec_defaultFormat)
1540         set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
1541     else
1542         response_default_diag(c, p->u.defaultFormat);
1543 }
1544
1545 ZOOM_API(ZOOM_record)
1546 ZOOM_record_clone (ZOOM_record srec)
1547 {
1548     char *buf;
1549     int size;
1550     ODR odr_enc;
1551     ZOOM_record nrec;
1552
1553     odr_enc = odr_createmem(ODR_ENCODE);
1554     if (!z_NamePlusRecord (odr_enc, &srec->npr, 0, 0))
1555         return 0;
1556     buf = odr_getbuf (odr_enc, &size, 0);
1557     
1558     nrec = (ZOOM_record) xmalloc (sizeof(*nrec));
1559     nrec->odr = odr_createmem(ODR_DECODE);
1560     nrec->wrbuf_marc = 0;
1561     nrec->wrbuf_iconv = 0;
1562     nrec->wrbuf_opac = 0;
1563     odr_setbuf (nrec->odr, buf, size, 0);
1564     z_NamePlusRecord (nrec->odr, &nrec->npr, 0, 0);
1565     
1566     odr_destroy (odr_enc);
1567     return nrec;
1568 }
1569
1570 ZOOM_API(ZOOM_record)
1571 ZOOM_resultset_record_immediate (ZOOM_resultset s,size_t pos)
1572 {
1573     return record_cache_lookup (s, pos);
1574 }
1575
1576 ZOOM_API(ZOOM_record)
1577 ZOOM_resultset_record (ZOOM_resultset r, size_t pos)
1578 {
1579     ZOOM_record rec = ZOOM_resultset_record_immediate(r, pos);
1580
1581     if (!rec)
1582     {
1583         /*
1584          * MIKE: I think force_sync should always be zero, but I don't
1585          * want to make this change until I get the go-ahead from
1586          * Adam, in case something depends on the old synchronous
1587          * behaviour.
1588          */
1589         int force_sync = 1;
1590         if (getenv("ZOOM_RECORD_NO_FORCE_SYNC")) force_sync = 0;
1591         ZOOM_resultset_retrieve (r, force_sync, pos, 1);
1592         rec = ZOOM_resultset_record_immediate (r, pos);
1593     }
1594     return rec;
1595 }
1596
1597 ZOOM_API(void)
1598 ZOOM_record_destroy (ZOOM_record rec)
1599 {
1600     if (!rec)
1601         return;
1602     if (rec->wrbuf_marc)
1603         wrbuf_free (rec->wrbuf_marc, 1);
1604     if (rec->wrbuf_iconv)
1605         wrbuf_free (rec->wrbuf_iconv, 1);
1606     if (rec->wrbuf_opac)
1607         wrbuf_free (rec->wrbuf_opac, 1);
1608     odr_destroy (rec->odr);
1609     xfree (rec);
1610 }
1611
1612 static const char *marc_iconv_return(ZOOM_record rec, int marc_type,
1613                                      int *len,
1614                                      const char *buf, int sz,
1615                                      const char *record_charset)
1616 {
1617     char to[40];
1618     char from[40];
1619     yaz_iconv_t cd = 0;
1620     yaz_marc_t mt = yaz_marc_create();
1621
1622     *from = '\0';
1623     strcpy(to, "UTF-8");
1624     if (record_charset && *record_charset)
1625     {
1626         /* Use "from,to" or just "from" */
1627         const char *cp = strchr(record_charset, ',');
1628         int clen = strlen(record_charset);
1629         if (cp && cp[1])
1630         {
1631             strncpy( to, cp+1, sizeof(to)-1);
1632             to[sizeof(to)-1] = '\0';
1633             clen = cp - record_charset;
1634         }
1635         if (clen > sizeof(from)-1)
1636             clen = sizeof(from)-1;
1637         
1638         if (clen)
1639             strncpy(from, record_charset, clen);
1640         from[clen] = '\0';
1641     }
1642
1643     if (*from && *to)
1644     {
1645         cd = yaz_iconv_open(to, from);
1646         yaz_marc_iconv(mt, cd);
1647     }
1648
1649     yaz_marc_xml(mt, marc_type);
1650     if (!rec->wrbuf_marc)
1651         rec->wrbuf_marc = wrbuf_alloc();
1652     wrbuf_rewind (rec->wrbuf_marc);
1653     if (yaz_marc_decode_wrbuf (mt, buf, sz, rec->wrbuf_marc) > 0)
1654     {
1655         yaz_marc_destroy(mt);
1656         if (cd)
1657             yaz_iconv_close(cd);
1658         if (len)
1659             *len = wrbuf_len(rec->wrbuf_marc);
1660         return wrbuf_buf(rec->wrbuf_marc);
1661     }
1662     yaz_marc_destroy(mt);
1663     if (cd)
1664         yaz_iconv_close(cd);
1665     return 0;
1666 }
1667
1668 static const char *record_iconv_return(ZOOM_record rec, int *len,
1669                                        const char *buf, int sz,
1670                                        const char *record_charset)
1671 {
1672     char to[40];
1673     char from[40];
1674     yaz_iconv_t cd = 0;
1675
1676     *from = '\0';
1677     strcpy(to, "UTF-8");
1678     if (record_charset && *record_charset)
1679     {
1680         /* Use "from,to" or just "from" */
1681         const char *cp = strchr(record_charset, ',');
1682         int clen = strlen(record_charset);
1683         if (cp && cp[1])
1684         {
1685             strncpy( to, cp+1, sizeof(to)-1);
1686             to[sizeof(to)-1] = '\0';
1687             clen = cp - record_charset;
1688         }
1689         if (clen > sizeof(from)-1)
1690             clen = sizeof(from)-1;
1691         
1692         if (clen)
1693             strncpy(from, record_charset, clen);
1694         from[clen] = '\0';
1695     }
1696
1697     if (*from && *to && (cd = yaz_iconv_open(to, from)))
1698     {
1699         char outbuf[12];
1700         size_t inbytesleft = sz;
1701         const char *inp = buf;
1702         
1703         if (!rec->wrbuf_iconv)
1704             rec->wrbuf_iconv = wrbuf_alloc();
1705
1706         wrbuf_rewind(rec->wrbuf_iconv);
1707
1708         while (inbytesleft)
1709         {
1710             size_t outbytesleft = sizeof(outbuf);
1711             char *outp = outbuf;
1712             size_t r = yaz_iconv (cd, (char**) &inp,
1713                                   &inbytesleft, 
1714                                   &outp, &outbytesleft);
1715             if (r == (size_t) (-1))
1716             {
1717                 int e = yaz_iconv_error(cd);
1718                 if (e != YAZ_ICONV_E2BIG)
1719                     break;
1720             }
1721             wrbuf_write(rec->wrbuf_iconv, outbuf, outp - outbuf);
1722         }
1723         wrbuf_puts(rec->wrbuf_iconv, "");
1724         buf = wrbuf_buf(rec->wrbuf_iconv);
1725         sz = wrbuf_len(rec->wrbuf_iconv);
1726         yaz_iconv_close(cd);
1727     }
1728     if (len)
1729         *len = sz;
1730     return buf;
1731 }
1732
1733 ZOOM_API(const char *)
1734 ZOOM_record_get (ZOOM_record rec, const char *type_spec, int *len)
1735 {
1736     char type[40];
1737     char charset[40];
1738     char xpath[512];
1739     const char *cp;
1740     int i;
1741     Z_NamePlusRecord *npr;
1742     
1743     if (len)
1744         *len = 0; /* default return */
1745         
1746     if (!rec)
1747         return 0;
1748     npr = rec->npr;
1749     if (!npr)
1750         return 0;
1751
1752     cp = type_spec;
1753     for (i = 0; cp[i] && i < sizeof(type)-1; i++)
1754     {
1755         if (cp[i] == ';' || cp[i] == ' ')
1756             break;
1757         type[i] = cp[i];
1758     }
1759     type[i] = '\0';
1760     charset[0] = '\0';
1761     while (type_spec[i] == ';')
1762     {
1763         i++;
1764         while (type_spec[i] == ' ')
1765             i++;
1766         if (!strncmp(type_spec+i, "charset=", 8))
1767         {
1768             int j = 0;
1769             i = i + 8; /* skip charset= */
1770             for (j = 0; type_spec[i]  && j < sizeof(charset)-1; i++, j++)
1771             {
1772                 if (type_spec[i] == ';' || type_spec[i] == ' ')
1773                     break;
1774                 charset[j] = cp[i];
1775             }
1776             charset[j] = '\0';
1777         }
1778         else if (!strncmp(type_spec+i, "xpath=", 6))
1779         {
1780             int j = 0; 
1781             i = i + 6;
1782             for (j = 0; type_spec[i] && j < sizeof(xpath)-1; i++, j++)
1783                 xpath[j] = cp[i];
1784             xpath[j] = '\0';
1785         } 
1786         while (type_spec[i] == ' ')
1787             i++;
1788     }
1789     if (!strcmp (type, "database"))
1790     {
1791         if (len)
1792             *len = (npr->databaseName ? strlen(npr->databaseName) : 0);
1793         return npr->databaseName;
1794     }
1795     else if (!strcmp (type, "syntax"))
1796     {
1797         const char *desc = 0;   
1798         if (npr->which == Z_NamePlusRecord_databaseRecord)
1799         {
1800             Z_External *r = (Z_External *) npr->u.databaseRecord;
1801             oident *ent = oid_getentbyoid(r->direct_reference);
1802             if (ent)
1803                 desc = ent->desc;
1804         }
1805         if (!desc)
1806             desc = "none";
1807         if (len)
1808             *len = strlen(desc);
1809         return desc;
1810     }
1811     if (npr->which != Z_NamePlusRecord_databaseRecord)
1812         return 0;
1813
1814     /* from now on - we have a database record .. */
1815     if (!strcmp (type, "render"))
1816     {
1817         Z_External *r = (Z_External *) npr->u.databaseRecord;
1818         oident *ent = oid_getentbyoid(r->direct_reference);
1819
1820         /* render bibliographic record .. */
1821         if (r->which == Z_External_OPAC)
1822         {
1823             r = r->u.opac->bibliographicRecord;
1824             if (!r)
1825                 return 0;
1826             ent = oid_getentbyoid(r->direct_reference);
1827         }
1828         if (r->which == Z_External_sutrs)
1829             return record_iconv_return(rec, len,
1830                                        (char*) r->u.sutrs->buf,
1831                                        r->u.sutrs->len,
1832                                        charset);
1833         else if (r->which == Z_External_octet)
1834         {
1835             const char *ret_buf;
1836             switch (ent->value)
1837             {
1838             case VAL_SOIF:
1839             case VAL_HTML:
1840             case VAL_SUTRS:
1841                 break;
1842             case VAL_TEXT_XML:
1843             case VAL_APPLICATION_XML:
1844                 break;
1845             default:
1846                 ret_buf = marc_iconv_return(
1847                     rec, YAZ_MARC_LINE, len,
1848                     (const char *) r->u.octet_aligned->buf,
1849                     r->u.octet_aligned->len,
1850                     charset);
1851                 if (ret_buf)
1852                     return ret_buf;
1853             }
1854             return record_iconv_return(rec, len,
1855                                        (const char *) r->u.octet_aligned->buf,
1856                                        r->u.octet_aligned->len,
1857                                        charset);
1858         }
1859         else if (r->which == Z_External_grs1)
1860         {
1861             if (!rec->wrbuf_marc)
1862                 rec->wrbuf_marc = wrbuf_alloc();
1863             wrbuf_rewind (rec->wrbuf_marc);
1864             yaz_display_grs1(rec->wrbuf_marc, r->u.grs1, 0);
1865             return record_iconv_return(rec, len,
1866                                        wrbuf_buf(rec->wrbuf_marc),
1867                                        wrbuf_len(rec->wrbuf_marc),
1868                                        charset);
1869         }
1870         return 0;
1871     }
1872     else if (!strcmp (type, "xml"))
1873     {
1874         Z_External *r = (Z_External *) npr->u.databaseRecord;
1875         oident *ent = oid_getentbyoid(r->direct_reference);
1876
1877         /* render bibliographic record .. */
1878         if (r->which == Z_External_OPAC)
1879         {
1880             r = r->u.opac->bibliographicRecord;
1881             if (!r)
1882                 return 0;
1883             ent = oid_getentbyoid(r->direct_reference);
1884         }
1885         
1886         if (r->which == Z_External_sutrs)
1887             return record_iconv_return(rec, len,
1888                                        (const char *) r->u.sutrs->buf,
1889                                        r->u.sutrs->len,
1890                                        charset);
1891         else if (r->which == Z_External_octet)
1892         {
1893             const char *ret_buf;
1894             int marc_decode_type = YAZ_MARC_MARCXML;
1895
1896             switch (ent->value)
1897             {
1898             case VAL_SOIF:
1899             case VAL_HTML:
1900             case VAL_SUTRS:
1901                 break;
1902             case VAL_TEXT_XML:
1903             case VAL_APPLICATION_XML:
1904                 break;
1905             default:
1906                 ret_buf = marc_iconv_return(
1907                     rec, marc_decode_type, len,
1908                     (const char *) r->u.octet_aligned->buf,
1909                     r->u.octet_aligned->len,
1910                     charset);
1911                 if (ret_buf)
1912                     return ret_buf;
1913             }
1914             return record_iconv_return(rec, len,
1915                                        (const char *) r->u.octet_aligned->buf,
1916                                        r->u.octet_aligned->len,
1917                                        charset);
1918         }
1919         else if (r->which == Z_External_grs1)
1920         {
1921             if (len) *len = 5;
1922             return "GRS-1";
1923         }
1924         return 0;
1925     }
1926     else if (!strcmp (type, "raw"))
1927     {
1928         Z_External *r = (Z_External *) npr->u.databaseRecord;
1929         
1930         if (r->which == Z_External_sutrs)
1931         {
1932             if (len) *len = r->u.sutrs->len;
1933             return (const char *) r->u.sutrs->buf;
1934         }
1935         else if (r->which == Z_External_octet)
1936         {
1937             if (len) *len = r->u.octet_aligned->len;
1938             return (const char *) r->u.octet_aligned->buf;
1939         }
1940         else /* grs-1, explain, OPAC, ... */
1941         {
1942             if (len) *len = -1;
1943             return (const char *) npr->u.databaseRecord;
1944         }
1945         return 0;
1946     }
1947     else if (!strcmp (type, "ext"))
1948     {
1949         if (len) *len = -1;
1950         return (const char *) npr->u.databaseRecord;
1951     }
1952     else if (!strcmp (type, "opac"))
1953              
1954     {
1955         Z_External *r = (Z_External *) npr->u.databaseRecord;
1956         if (r->which == Z_External_OPAC)
1957         {
1958             if (!rec->wrbuf_opac)
1959                 rec->wrbuf_opac = wrbuf_alloc();
1960             wrbuf_rewind (rec->wrbuf_opac);
1961             yaz_display_OPAC(rec->wrbuf_opac, r->u.opac, 0);
1962             return record_iconv_return(rec, len,
1963                                        wrbuf_buf(rec->wrbuf_opac),
1964                                        wrbuf_len(rec->wrbuf_opac),
1965                                        charset);
1966         }
1967     }
1968     return 0;
1969 }
1970
1971 static int strcmp_null(const char *v1, const char *v2)
1972 {
1973     if (!v1 && !v2)
1974         return 0;
1975     if (!v1 || !v2)
1976         return -1;
1977     return strcmp(v1, v2);
1978 }
1979
1980 static size_t record_hash(int pos)
1981 {
1982     if (pos < 0)
1983         pos = 0;
1984     return pos % RECORD_HASH_SIZE;
1985 }
1986
1987 static void record_cache_add (ZOOM_resultset r, Z_NamePlusRecord *npr, 
1988                               int pos)
1989 {
1990     ZOOM_record_cache rc;
1991     const char *elementSetName =
1992         ZOOM_resultset_option_get (r, "elementSetName");
1993     const char *syntax = 
1994         ZOOM_resultset_option_get (r, "preferredRecordSyntax");
1995     
1996     ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_RECV_RECORD);
1997     ZOOM_connection_put_event(r->connection, event);
1998
1999     for (rc = r->record_hash[record_hash(pos)]; rc; rc = rc->next)
2000     {
2001         if (pos == rc->pos)
2002         {
2003             if (strcmp_null(r->schema, rc->schema))
2004                 continue;
2005             if (strcmp_null(elementSetName,rc->elementSetName))
2006                 continue;
2007             if (strcmp_null(syntax, rc->syntax))
2008                 continue;
2009             /* not destroying rc->npr (it's handled by nmem )*/
2010             rc->rec.npr = npr;
2011             /* keeping wrbuf_marc too */
2012             return;
2013         }
2014     }
2015     rc = (ZOOM_record_cache) odr_malloc (r->odr, sizeof(*rc));
2016     rc->rec.npr = npr; 
2017     rc->rec.odr = 0;
2018     rc->rec.wrbuf_marc = 0;
2019     rc->rec.wrbuf_iconv = 0;
2020     rc->rec.wrbuf_opac = 0;
2021     if (elementSetName)
2022         rc->elementSetName = odr_strdup (r->odr, elementSetName);
2023     else
2024         rc->elementSetName = 0;
2025
2026     if (syntax)
2027         rc->syntax = odr_strdup (r->odr, syntax);
2028     else
2029         rc->syntax = 0;
2030
2031     if (r->schema)
2032         rc->schema = odr_strdup (r->odr, r->schema);
2033     else
2034         rc->schema = 0;
2035
2036     rc->pos = pos;
2037     rc->next = r->record_hash[record_hash(pos)];
2038     r->record_hash[record_hash(pos)] = rc;
2039 }
2040
2041 static ZOOM_record record_cache_lookup (ZOOM_resultset r, int pos)
2042 {
2043     ZOOM_record_cache rc;
2044     const char *elementSetName =
2045         ZOOM_resultset_option_get (r, "elementSetName");
2046     const char *syntax = 
2047         ZOOM_resultset_option_get (r, "preferredRecordSyntax");
2048     
2049     for (rc = r->record_hash[record_hash(pos)]; rc; rc = rc->next)
2050     {
2051         if (pos == rc->pos)
2052         {
2053             if (strcmp_null(r->schema, rc->schema))
2054                 continue;
2055             if (strcmp_null(elementSetName,rc->elementSetName))
2056                 continue;
2057             if (strcmp_null(syntax, rc->syntax))
2058                 continue;
2059             return &rc->rec;
2060         }
2061     }
2062     return 0;
2063 }
2064                                              
2065 static void handle_records (ZOOM_connection c, Z_Records *sr,
2066                             int present_phase)
2067 {
2068     ZOOM_resultset resultset;
2069
2070     if (!c->tasks)
2071         return ;
2072     switch (c->tasks->which)
2073     {
2074     case ZOOM_TASK_SEARCH:
2075         resultset = c->tasks->u.search.resultset;
2076         break;
2077     case ZOOM_TASK_RETRIEVE:
2078         resultset = c->tasks->u.retrieve.resultset;        
2079         break;
2080     default:
2081         return;
2082     }
2083     if (sr && sr->which == Z_Records_NSD)
2084         response_default_diag(c, sr->u.nonSurrogateDiagnostic);
2085     else if (sr && sr->which == Z_Records_multipleNSD)
2086     {
2087         if (sr->u.multipleNonSurDiagnostics->num_diagRecs >= 1)
2088             response_diag(c, sr->u.multipleNonSurDiagnostics->diagRecs[0]);
2089         else
2090             set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
2091     }
2092     else 
2093     {
2094         if (resultset->count + resultset->start > resultset->size)
2095             resultset->count = resultset->size - resultset->start;
2096         if (resultset->count < 0)
2097             resultset->count = 0;
2098         if (sr && sr->which == Z_Records_DBOSD)
2099         {
2100             int i;
2101             NMEM nmem = odr_extract_mem (c->odr_in);
2102             Z_NamePlusRecordList *p =
2103                 sr->u.databaseOrSurDiagnostics;
2104             for (i = 0; i<p->num_records; i++)
2105             {
2106                 record_cache_add (resultset, p->records[i],
2107                                   i+ resultset->start);
2108             }
2109             /* transfer our response to search_nmem .. we need it later */
2110             nmem_transfer (resultset->odr->mem, nmem);
2111             nmem_destroy (nmem);
2112             if (present_phase && p->num_records == 0)
2113             {
2114                 /* present response and we didn't get any records! */
2115                 Z_NamePlusRecord *myrec = 
2116                     zget_surrogateDiagRec(resultset->odr, 0, 14, 0);
2117                 record_cache_add(resultset, myrec, resultset->start);
2118             }
2119         }
2120         else if (present_phase)
2121         {
2122             /* present response and we didn't get any records! */
2123             Z_NamePlusRecord *myrec = 
2124                 zget_surrogateDiagRec(resultset->odr, 0, 14, 0);
2125             record_cache_add(resultset, myrec, resultset->start);
2126         }
2127     }
2128 }
2129
2130 static void handle_present_response (ZOOM_connection c, Z_PresentResponse *pr)
2131 {
2132     handle_records (c, pr->records, 1);
2133 }
2134
2135 static void handle_queryExpressionTerm(ZOOM_options opt, const char *name,
2136                                        Z_Term *term)
2137 {
2138     switch (term->which)
2139     {
2140     case Z_Term_general:
2141         ZOOM_options_setl(opt, name,
2142                           (const char *)(term->u.general->buf), 
2143                           term->u.general->len);
2144         break;
2145     case Z_Term_characterString:
2146         ZOOM_options_set(opt, name, term->u.characterString);
2147         break;
2148     case Z_Term_numeric:
2149         ZOOM_options_set_int(opt, name, *term->u.numeric);
2150         break;
2151     }
2152 }
2153
2154 static void handle_queryExpression(ZOOM_options opt, const char *name,
2155                                    Z_QueryExpression *exp)
2156 {
2157     char opt_name[80];
2158     
2159     switch (exp->which)
2160     {
2161     case Z_QueryExpression_term:
2162         if (exp->u.term && exp->u.term->queryTerm)
2163         {
2164             sprintf(opt_name, "%s.term", name);
2165             handle_queryExpressionTerm(opt, opt_name, exp->u.term->queryTerm);
2166         }
2167         break;
2168     case Z_QueryExpression_query:
2169         break;
2170     }
2171 }
2172
2173 static void handle_searchResult(ZOOM_connection c, ZOOM_resultset resultset,
2174                                 Z_OtherInformation *o)
2175 {
2176     int i;
2177     for (i = 0; o && i < o->num_elements; i++)
2178     {
2179         if (o->list[i]->which == Z_OtherInfo_externallyDefinedInfo)
2180         {
2181             Z_External *ext = o->list[i]->information.externallyDefinedInfo;
2182             
2183             if (ext->which == Z_External_searchResult1)
2184             {
2185                 int j;
2186                 Z_SearchInfoReport *sr = ext->u.searchResult1;
2187                 
2188                 if (sr->num)
2189                     ZOOM_options_set_int(
2190                         resultset->options, "searchresult.size", sr->num);
2191
2192                 for (j = 0; j < sr->num; j++)
2193                 {
2194                     Z_SearchInfoReport_s *ent =
2195                         ext->u.searchResult1->elements[j];
2196                     char pref[80];
2197                     
2198                     sprintf(pref, "searchresult.%d", j);
2199
2200                     if (ent->subqueryId)
2201                     {
2202                         char opt_name[80];
2203                         sprintf(opt_name, "%s.id", pref);
2204                         ZOOM_options_set(resultset->options, opt_name,
2205                                          ent->subqueryId);
2206                     }
2207                     if (ent->subqueryExpression)
2208                     {
2209                         char opt_name[80];
2210                         sprintf(opt_name, "%s.subquery", pref);
2211                         handle_queryExpression(resultset->options, opt_name,
2212                                                ent->subqueryExpression);
2213                     }
2214                     if (ent->subqueryInterpretation)
2215                     {
2216                         char opt_name[80];
2217                         sprintf(opt_name, "%s.interpretation", pref);
2218                         handle_queryExpression(resultset->options, opt_name,
2219                                                ent->subqueryInterpretation);
2220                     }
2221                     if (ent->subqueryRecommendation)
2222                     {
2223                         char opt_name[80];
2224                         sprintf(opt_name, "%s.recommendation", pref);
2225                         handle_queryExpression(resultset->options, opt_name,
2226                                                ent->subqueryRecommendation);
2227                     }
2228                     if (ent->subqueryCount)
2229                     {
2230                         char opt_name[80];
2231                         sprintf(opt_name, "%s.count", pref);
2232                         ZOOM_options_set_int(resultset->options, opt_name,
2233                                              *ent->subqueryCount);
2234                     }                                             
2235                 }
2236             }
2237         }
2238     }
2239 }
2240
2241 static void handle_search_response (ZOOM_connection c, Z_SearchResponse *sr)
2242 {
2243     ZOOM_resultset resultset;
2244     ZOOM_Event event;
2245
2246     if (!c->tasks || c->tasks->which != ZOOM_TASK_SEARCH)
2247         return ;
2248
2249     event = ZOOM_Event_create(ZOOM_EVENT_RECV_SEARCH);
2250     ZOOM_connection_put_event(c, event);
2251
2252     resultset = c->tasks->u.search.resultset;
2253
2254     handle_searchResult(c, resultset, sr->additionalSearchInfo);
2255
2256     resultset->size = *sr->resultCount;
2257     handle_records (c, sr->records, 0);
2258 }
2259
2260 static void sort_response (ZOOM_connection c, Z_SortResponse *res)
2261 {
2262     if (res->diagnostics && res->num_diagnostics > 0)
2263         response_diag (c, res->diagnostics[0]);
2264 }
2265
2266 static int scan_response (ZOOM_connection c, Z_ScanResponse *res)
2267 {
2268     NMEM nmem = odr_extract_mem (c->odr_in);
2269     ZOOM_scanset scan;
2270
2271     if (!c->tasks || c->tasks->which != ZOOM_TASK_SCAN)
2272         return 0;
2273     scan = c->tasks->u.scan.scan;
2274
2275     if (res->entries && res->entries->nonsurrogateDiagnostics)
2276         response_diag(c, res->entries->nonsurrogateDiagnostics[0]);
2277     scan->scan_response = res;
2278     nmem_transfer (scan->odr->mem, nmem);
2279     if (res->stepSize)
2280         ZOOM_options_set_int (scan->options, "stepSize", *res->stepSize);
2281     if (res->positionOfTerm)
2282         ZOOM_options_set_int (scan->options, "position", *res->positionOfTerm);
2283     if (res->scanStatus)
2284         ZOOM_options_set_int (scan->options, "scanStatus", *res->scanStatus);
2285     if (res->numberOfEntriesReturned)
2286         ZOOM_options_set_int (scan->options, "number",
2287                               *res->numberOfEntriesReturned);
2288     nmem_destroy (nmem);
2289     return 1;
2290 }
2291
2292 static zoom_ret send_sort (ZOOM_connection c,
2293                            ZOOM_resultset resultset)
2294 {
2295     if (c->error)
2296         resultset->r_sort_spec = 0;
2297     if (resultset->r_sort_spec)
2298     {
2299         Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_sortRequest);
2300         Z_SortRequest *req = apdu->u.sortRequest;
2301         
2302         req->num_inputResultSetNames = 1;
2303         req->inputResultSetNames = (Z_InternationalString **)
2304             odr_malloc (c->odr_out, sizeof(*req->inputResultSetNames));
2305         req->inputResultSetNames[0] =
2306             odr_strdup (c->odr_out, resultset->setname);
2307         req->sortedResultSetName = odr_strdup (c->odr_out, resultset->setname);
2308         req->sortSequence = resultset->r_sort_spec;
2309         resultset->r_sort_spec = 0;
2310         return send_APDU (c, apdu);
2311     }
2312     return zoom_complete;
2313 }
2314
2315 static zoom_ret send_present(ZOOM_connection c)
2316 {
2317     Z_APDU *apdu = 0;
2318     Z_PresentRequest *req = 0;
2319     int i = 0;
2320     const char *syntax = 0;
2321     const char *elementSetName = 0;
2322     ZOOM_resultset  resultset;
2323
2324     if (!c->tasks)
2325     {
2326         yaz_log(log_details, "%p send_present no tasks", c);
2327         return zoom_complete;
2328     }
2329     switch (c->tasks->which)
2330     {
2331     case ZOOM_TASK_SEARCH:
2332         resultset = c->tasks->u.search.resultset;
2333         break;
2334     case ZOOM_TASK_RETRIEVE:
2335         resultset = c->tasks->u.retrieve.resultset;
2336         resultset->start = c->tasks->u.retrieve.start;
2337         resultset->count = c->tasks->u.retrieve.count;
2338
2339         if (resultset->start >= resultset->size)
2340         {
2341             yaz_log(log_details, "%p send_present start=%d >= size=%d",
2342                     c, resultset->start, resultset->size);
2343             return zoom_complete;
2344         }
2345         if (resultset->start + resultset->count > resultset->size)
2346             resultset->count = resultset->size - resultset->start;
2347         break;
2348     default:
2349         return zoom_complete;
2350     }
2351     yaz_log(log_details, "%p send_present start=%d count=%d",
2352             c, resultset->start, resultset->count);
2353
2354     syntax = ZOOM_resultset_option_get (resultset, "preferredRecordSyntax");
2355     elementSetName = ZOOM_resultset_option_get (resultset, "elementSetName");
2356
2357     if (c->error)                  /* don't continue on error */
2358         return zoom_complete;
2359     if (resultset->start < 0)
2360         return zoom_complete;
2361     for (i = 0; i<resultset->count; i++)
2362     {
2363         ZOOM_record rec =
2364             record_cache_lookup (resultset, i + resultset->start);
2365         if (!rec)
2366             break;
2367     }
2368     if (i == resultset->count)
2369     {
2370         yaz_log(log_details, "%p send_present skip=%d no more to fetch", c, i);
2371         return zoom_complete;
2372     }
2373
2374     apdu = zget_APDU(c->odr_out, Z_APDU_presentRequest);
2375     req = apdu->u.presentRequest;
2376
2377     resultset->start += i;
2378     resultset->count -= i;
2379
2380     if (i)
2381         yaz_log(log_details, "%p send_present skip=%d", c, i);
2382
2383     *req->resultSetStartPoint = resultset->start + 1;
2384     *req->numberOfRecordsRequested = resultset->step>0 ?
2385         resultset->step : resultset->count;
2386     if (*req->numberOfRecordsRequested + resultset->start > resultset->size)
2387         *req->numberOfRecordsRequested = resultset->size -  resultset->start;
2388     assert (*req->numberOfRecordsRequested > 0);
2389
2390     if (syntax && *syntax)
2391         req->preferredRecordSyntax =
2392             yaz_str_to_z3950oid (c->odr_out, CLASS_RECSYN, syntax);
2393
2394     if (resultset->schema && *resultset->schema)
2395     {
2396         Z_RecordComposition *compo = (Z_RecordComposition *)
2397             odr_malloc (c->odr_out, sizeof(*compo));
2398
2399         req->recordComposition = compo;
2400         compo->which = Z_RecordComp_complex;
2401         compo->u.complex = (Z_CompSpec *)
2402             odr_malloc(c->odr_out, sizeof(*compo->u.complex));
2403         compo->u.complex->selectAlternativeSyntax = (bool_t *) 
2404             odr_malloc(c->odr_out, sizeof(bool_t));
2405         *compo->u.complex->selectAlternativeSyntax = 0;
2406
2407         compo->u.complex->generic = (Z_Specification *)
2408             odr_malloc(c->odr_out, sizeof(*compo->u.complex->generic));
2409
2410         compo->u.complex->generic->which = Z_Schema_oid;
2411         compo->u.complex->generic->schema.oid = (Odr_oid *)
2412             yaz_str_to_z3950oid (c->odr_out, CLASS_SCHEMA, resultset->schema);
2413
2414         if (!compo->u.complex->generic->schema.oid)
2415         {
2416             /* OID wasn't a schema! Try record syntax instead. */
2417
2418             compo->u.complex->generic->schema.oid = (Odr_oid *)
2419                 yaz_str_to_z3950oid (c->odr_out, CLASS_RECSYN, resultset->schema);
2420         }
2421         if (elementSetName && *elementSetName)
2422         {
2423             compo->u.complex->generic->elementSpec = (Z_ElementSpec *)
2424                 odr_malloc(c->odr_out, sizeof(Z_ElementSpec));
2425             compo->u.complex->generic->elementSpec->which =
2426                 Z_ElementSpec_elementSetName;
2427             compo->u.complex->generic->elementSpec->u.elementSetName =
2428                 odr_strdup (c->odr_out, elementSetName);
2429         }
2430         else
2431             compo->u.complex->generic->elementSpec = 0;
2432         compo->u.complex->num_dbSpecific = 0;
2433         compo->u.complex->dbSpecific = 0;
2434         compo->u.complex->num_recordSyntax = 0;
2435         compo->u.complex->recordSyntax = 0;
2436     }
2437     else if (elementSetName && *elementSetName)
2438     {
2439         Z_ElementSetNames *esn = (Z_ElementSetNames *)
2440             odr_malloc (c->odr_out, sizeof(*esn));
2441         Z_RecordComposition *compo = (Z_RecordComposition *)
2442             odr_malloc (c->odr_out, sizeof(*compo));
2443         
2444         esn->which = Z_ElementSetNames_generic;
2445         esn->u.generic = odr_strdup (c->odr_out, elementSetName);
2446         compo->which = Z_RecordComp_simple;
2447         compo->u.simple = esn;
2448         req->recordComposition = compo;
2449     }
2450     req->resultSetId = odr_strdup(c->odr_out, resultset->setname);
2451     return send_APDU (c, apdu);
2452 }
2453
2454 ZOOM_API(ZOOM_scanset)
2455 ZOOM_connection_scan (ZOOM_connection c, const char *start)
2456 {
2457     ZOOM_scanset s;
2458     ZOOM_query q = ZOOM_query_create();
2459
2460     ZOOM_query_prefix (q, start);
2461
2462     s = ZOOM_connection_scan1(c, q);
2463     ZOOM_query_destroy (q);
2464     return s;
2465
2466 }
2467
2468 ZOOM_API(ZOOM_scanset)
2469 ZOOM_connection_scan1 (ZOOM_connection c, ZOOM_query q)
2470 {
2471     ZOOM_scanset scan = (ZOOM_scanset) xmalloc (sizeof(*scan));
2472     char *start;
2473     char *freeme = 0;
2474
2475     scan->connection = c;
2476     scan->odr = odr_createmem (ODR_DECODE);
2477     scan->options = ZOOM_options_create_with_parent (c->options);
2478     scan->refcount = 1;
2479     scan->scan_response = 0;
2480
2481     /*
2482      * We need to check the query-type, so we can recognise CQL and
2483      * CCL and compile them into a form that we can use here.  The
2484      * ZOOM_query structure has no explicit `type' member, but
2485      * inspection of the ZOOM_query_prefix() and ZOOM_query_cql()
2486      * functions shows how the structure is set up in each case.
2487      */
2488     if (q->z_query->which == Z_Query_type_1) {
2489         yaz_log(log_api, "%p ZOOM_connection_scan1 q=%p PQF '%s'",
2490                 c, q, q->query_string);
2491         start = q->query_string;
2492     } else if (q->z_query->which == Z_Query_type_104) {
2493         yaz_log(log_api, "%p ZOOM_connection_scan1 q=%p CQL '%s'",
2494                 c, q, q->query_string);
2495         start = freeme = cql2pqf(c, q->query_string);
2496         if (start == 0)
2497             return 0;
2498     } else {
2499         yaz_log(YLOG_FATAL, "%p ZOOM_connection_scan1 q=%p unknown type '%s'",
2500                 c, q, q->query_string);
2501         abort();
2502     }
2503
2504     scan->termListAndStartPoint =
2505         p_query_scan(scan->odr, PROTO_Z3950, &scan->attributeSet, start);
2506     xfree (freeme);
2507     if (scan->termListAndStartPoint != 0)
2508     {
2509         ZOOM_task task = ZOOM_connection_add_task (c, ZOOM_TASK_SCAN);
2510         task->u.scan.scan = scan;
2511         
2512         (scan->refcount)++;
2513         if (!c->async)
2514         {
2515             while (ZOOM_event (1, &c))
2516                 ;
2517         }
2518     }
2519     return scan;
2520 }
2521
2522 ZOOM_API(void)
2523 ZOOM_scanset_destroy (ZOOM_scanset scan)
2524 {
2525     if (!scan)
2526         return;
2527     (scan->refcount)--;
2528     if (scan->refcount == 0)
2529     {
2530         odr_destroy (scan->odr);
2531         
2532         ZOOM_options_destroy (scan->options);
2533         xfree (scan);
2534     }
2535 }
2536
2537 static zoom_ret send_package (ZOOM_connection c)
2538 {
2539     ZOOM_Event event;
2540
2541     yaz_log(log_details, "%p send_package", c);
2542     if (!c->tasks)
2543         return zoom_complete;
2544     assert (c->tasks->which == ZOOM_TASK_PACKAGE);
2545     
2546     event = ZOOM_Event_create (ZOOM_EVENT_SEND_APDU);
2547     ZOOM_connection_put_event (c, event);
2548     
2549     c->buf_out = c->tasks->u.package->buf_out;
2550     c->len_out = c->tasks->u.package->len_out;
2551
2552     return do_write(c);
2553 }
2554
2555 static zoom_ret send_scan (ZOOM_connection c)
2556 {
2557     ZOOM_scanset scan;
2558     Z_APDU *apdu = zget_APDU(c->odr_out, Z_APDU_scanRequest);
2559     Z_ScanRequest *req = apdu->u.scanRequest;
2560
2561     yaz_log(log_details, "%p send_scan", c);
2562     if (!c->tasks)
2563         return zoom_complete;
2564     assert (c->tasks->which == ZOOM_TASK_SCAN);
2565     scan = c->tasks->u.scan.scan;
2566
2567     req->termListAndStartPoint = scan->termListAndStartPoint;
2568     req->attributeSet = scan->attributeSet;
2569
2570     *req->numberOfTermsRequested =
2571         ZOOM_options_get_int(scan->options, "number", 10);
2572
2573     req->preferredPositionInResponse =
2574         odr_intdup (c->odr_out,
2575                     ZOOM_options_get_int(scan->options, "position", 1));
2576
2577     req->stepSize =
2578         odr_intdup (c->odr_out,
2579                     ZOOM_options_get_int(scan->options, "stepSize", 0));
2580     
2581     req->databaseNames = set_DatabaseNames (c, scan->options, 
2582                                             &req->num_databaseNames);
2583
2584     return send_APDU (c, apdu);
2585 }
2586
2587 ZOOM_API(size_t)
2588 ZOOM_scanset_size (ZOOM_scanset scan)
2589 {
2590     if (!scan || !scan->scan_response || !scan->scan_response->entries)
2591         return 0;
2592     return scan->scan_response->entries->num_entries;
2593 }
2594
2595 ZOOM_API(const char *)
2596 ZOOM_scanset_term (ZOOM_scanset scan, size_t pos,
2597                    int *occ, int *len)
2598 {
2599     const char *term = 0;
2600     size_t noent = ZOOM_scanset_size (scan);
2601     Z_ScanResponse *res = scan->scan_response;
2602     
2603     *len = 0;
2604     *occ = 0;
2605     if (pos >= noent)
2606         return 0;
2607     if (res->entries->entries[pos]->which == Z_Entry_termInfo)
2608     {
2609         Z_TermInfo *t = res->entries->entries[pos]->u.termInfo;
2610         
2611         if (t->term->which == Z_Term_general)
2612         {
2613             term = (const char *) t->term->u.general->buf;
2614             *len = t->term->u.general->len;
2615         }
2616         *occ = t->globalOccurrences ? *t->globalOccurrences : 0;
2617     }
2618     return term;
2619 }
2620
2621 ZOOM_API(const char *)
2622 ZOOM_scanset_display_term (ZOOM_scanset scan, size_t pos,
2623                            int *occ, int *len)
2624 {
2625     const char *term = 0;
2626     size_t noent = ZOOM_scanset_size (scan);
2627     Z_ScanResponse *res = scan->scan_response;
2628     
2629     *len = 0;
2630     *occ = 0;
2631     if (pos >= noent)
2632         return 0;
2633     if (res->entries->entries[pos]->which == Z_Entry_termInfo)
2634     {
2635         Z_TermInfo *t = res->entries->entries[pos]->u.termInfo;
2636
2637         if (t->displayTerm)
2638         {
2639             term = t->displayTerm;
2640             *len = strlen(term);
2641         }
2642         else if (t->term->which == Z_Term_general)
2643         {
2644             term = (const char *) t->term->u.general->buf;
2645             *len = t->term->u.general->len;
2646         }
2647         *occ = t->globalOccurrences ? *t->globalOccurrences : 0;
2648     }
2649     return term;
2650 }
2651
2652 ZOOM_API(const char *)
2653 ZOOM_scanset_option_get (ZOOM_scanset scan, const char *key)
2654 {
2655     return ZOOM_options_get (scan->options, key);
2656 }
2657
2658 ZOOM_API(void)
2659 ZOOM_scanset_option_set (ZOOM_scanset scan, const char *key,
2660                               const char *val)
2661 {
2662     ZOOM_options_set (scan->options, key, val);
2663 }
2664
2665 static Z_APDU *create_es_package (ZOOM_package p, int type)
2666 {
2667     const char *str;
2668     Z_APDU *apdu = zget_APDU(p->odr_out, Z_APDU_extendedServicesRequest);
2669     Z_ExtendedServicesRequest *req = apdu->u.extendedServicesRequest;
2670     
2671     *req->function = Z_ExtendedServicesRequest_create;
2672     
2673     str = ZOOM_options_get(p->options, "package-name");
2674     if (str && *str)
2675         req->packageName = nmem_strdup (p->odr_out->mem, str);
2676     
2677     str = ZOOM_options_get(p->options, "user-id");
2678     if (str)
2679         req->userId = nmem_strdup (p->odr_out->mem, str);
2680     
2681     req->packageType = yaz_oidval_to_z3950oid(p->odr_out, CLASS_EXTSERV,
2682                                               type);
2683
2684     str = ZOOM_options_get(p->options, "function");
2685     if (str)
2686     {
2687         if (!strcmp (str, "create"))
2688             *req->function = 1;
2689         if (!strcmp (str, "delete"))
2690             *req->function = 2;
2691         if (!strcmp (str, "modify"))
2692             *req->function = 3;
2693     }
2694     return apdu;
2695 }
2696
2697 static const char *ill_array_lookup (void *clientData, const char *idx)
2698 {
2699     ZOOM_package p = (ZOOM_package) clientData;
2700     return ZOOM_options_get (p->options, idx+4);
2701 }
2702
2703 static Z_External *encode_ill_request (ZOOM_package p)
2704 {
2705     ODR out = p->odr_out;
2706     ILL_Request *req;
2707     Z_External *r = 0;
2708     struct ill_get_ctl ctl;
2709         
2710     ctl.odr = p->odr_out;
2711     ctl.clientData = p;
2712     ctl.f = ill_array_lookup;
2713         
2714     req = ill_get_ILLRequest(&ctl, "ill", 0);
2715         
2716     if (!ill_Request (out, &req, 0, 0))
2717     {
2718         int ill_request_size;
2719         char *ill_request_buf = odr_getbuf (out, &ill_request_size, 0);
2720         if (ill_request_buf)
2721             odr_setbuf (out, ill_request_buf, ill_request_size, 1);
2722         return 0;
2723     }
2724     else
2725     {
2726         oident oid;
2727         int illRequest_size = 0;
2728         char *illRequest_buf = odr_getbuf (out, &illRequest_size, 0);
2729                 
2730         oid.proto = PROTO_GENERAL;
2731         oid.oclass = CLASS_GENERAL;
2732         oid.value = VAL_ISO_ILL_1;
2733                 
2734         r = (Z_External *) odr_malloc (out, sizeof(*r));
2735         r->direct_reference = odr_oiddup(out,oid_getoidbyent(&oid)); 
2736         r->indirect_reference = 0;
2737         r->descriptor = 0;
2738         r->which = Z_External_single;
2739                 
2740         r->u.single_ASN1_type =
2741             odr_create_Odr_oct(out,
2742                                (unsigned char *)illRequest_buf,
2743                                illRequest_size);
2744     }
2745     return r;
2746 }
2747
2748 static Z_ItemOrder *encode_item_order(ZOOM_package p)
2749 {
2750     Z_ItemOrder *req = (Z_ItemOrder *) odr_malloc (p->odr_out, sizeof(*req));
2751     const char *str;
2752     
2753     req->which = Z_IOItemOrder_esRequest;
2754     req->u.esRequest = (Z_IORequest *) 
2755         odr_malloc(p->odr_out,sizeof(Z_IORequest));
2756
2757     /* to keep part ... */
2758     req->u.esRequest->toKeep = (Z_IOOriginPartToKeep *)
2759         odr_malloc(p->odr_out,sizeof(Z_IOOriginPartToKeep));
2760     req->u.esRequest->toKeep->supplDescription = 0;
2761     req->u.esRequest->toKeep->contact = (Z_IOContact *)
2762         odr_malloc (p->odr_out, sizeof(*req->u.esRequest->toKeep->contact));
2763         
2764     str = ZOOM_options_get(p->options, "contact-name");
2765     req->u.esRequest->toKeep->contact->name = str ?
2766         nmem_strdup (p->odr_out->mem, str) : 0;
2767         
2768     str = ZOOM_options_get(p->options, "contact-phone");
2769     req->u.esRequest->toKeep->contact->phone = str ?
2770         nmem_strdup (p->odr_out->mem, str) : 0;
2771         
2772     str = ZOOM_options_get(p->options, "contact-email");
2773     req->u.esRequest->toKeep->contact->email = str ?
2774         nmem_strdup (p->odr_out->mem, str) : 0;
2775         
2776     req->u.esRequest->toKeep->addlBilling = 0;
2777         
2778     /* not to keep part ... */
2779     req->u.esRequest->notToKeep = (Z_IOOriginPartNotToKeep *)
2780         odr_malloc(p->odr_out,sizeof(Z_IOOriginPartNotToKeep));
2781         
2782     str = ZOOM_options_get(p->options, "itemorder-setname");
2783     if (!str)
2784         str = "default";
2785
2786     if (!*str) 
2787         req->u.esRequest->notToKeep->resultSetItem = 0;
2788     else
2789     {
2790         req->u.esRequest->notToKeep->resultSetItem = (Z_IOResultSetItem *)
2791            odr_malloc(p->odr_out, sizeof(Z_IOResultSetItem));
2792
2793         req->u.esRequest->notToKeep->resultSetItem->resultSetId =
2794            nmem_strdup (p->odr_out->mem, str);
2795         req->u.esRequest->notToKeep->resultSetItem->item =
2796             (int *) odr_malloc(p->odr_out, sizeof(int));
2797         
2798         str = ZOOM_options_get(p->options, "itemorder-item");
2799         *req->u.esRequest->notToKeep->resultSetItem->item =
2800             (str ? atoi(str) : 1);
2801     }
2802     req->u.esRequest->notToKeep->itemRequest = encode_ill_request(p);
2803     
2804     return req;
2805 }
2806
2807 Z_APDU *create_admin_package(ZOOM_package p, int type, 
2808                              Z_ESAdminOriginPartToKeep **toKeepP,
2809                              Z_ESAdminOriginPartNotToKeep **notToKeepP)
2810 {
2811     Z_APDU *apdu = create_es_package (p, VAL_ADMINSERVICE);
2812     if (apdu)
2813     {
2814         Z_ESAdminOriginPartToKeep  *toKeep;
2815         Z_ESAdminOriginPartNotToKeep  *notToKeep;
2816         Z_External *r = (Z_External *) odr_malloc (p->odr_out, sizeof(*r));
2817         const char *first_db = "Default";
2818         int num_db;
2819         char **db = set_DatabaseNames(p->connection, p->options, &num_db);
2820         if (num_db > 0)
2821             first_db = db[0];
2822             
2823         r->direct_reference =
2824             yaz_oidval_to_z3950oid(p->odr_out, CLASS_EXTSERV,
2825                                    VAL_ADMINSERVICE);
2826         r->descriptor = 0;
2827         r->indirect_reference = 0;
2828         r->which = Z_External_ESAdmin;
2829         
2830         r->u.adminService = (Z_Admin *)
2831             odr_malloc(p->odr_out, sizeof(*r->u.adminService));
2832         r->u.adminService->which = Z_Admin_esRequest;
2833         r->u.adminService->u.esRequest = (Z_AdminEsRequest *)
2834             odr_malloc(p->odr_out, sizeof(*r->u.adminService->u.esRequest));
2835         
2836         toKeep = r->u.adminService->u.esRequest->toKeep =
2837             (Z_ESAdminOriginPartToKeep *) 
2838             odr_malloc(p->odr_out, sizeof(*r->u.adminService->u.esRequest->toKeep));
2839         toKeep->which = type;
2840         toKeep->databaseName = odr_strdup(p->odr_out, first_db);
2841         toKeep->u.create = odr_nullval();
2842         apdu->u.extendedServicesRequest->taskSpecificParameters = r;
2843         
2844         r->u.adminService->u.esRequest->notToKeep = notToKeep =
2845             (Z_ESAdminOriginPartNotToKeep *)
2846             odr_malloc(p->odr_out,
2847                        sizeof(*r->u.adminService->u.esRequest->notToKeep));
2848         notToKeep->which = Z_ESAdminOriginPartNotToKeep_recordsWillFollow;
2849         notToKeep->u.recordsWillFollow = odr_nullval();
2850         if (toKeepP)
2851             *toKeepP = toKeep;
2852         if (notToKeepP)
2853             *notToKeepP = notToKeep;
2854     }
2855     return apdu;
2856 }
2857
2858 static Z_APDU *create_xmlupdate_package(ZOOM_package p)
2859 {
2860     Z_APDU *apdu = create_es_package(p, VAL_XMLES);
2861     Z_ExtendedServicesRequest *req = apdu->u.extendedServicesRequest;
2862     Z_External *ext = (Z_External *) odr_malloc(p->odr_out, sizeof(*ext));
2863     const char *doc = ZOOM_options_get(p->options, "doc");
2864
2865     if (!doc)
2866         doc = "";
2867
2868     req->taskSpecificParameters = ext;
2869     ext->direct_reference = req->packageType;
2870     ext->descriptor = 0;
2871     ext->indirect_reference = 0;
2872     
2873     ext->which = Z_External_octet;
2874     ext->u.single_ASN1_type =
2875         odr_create_Odr_oct(p->odr_out, (const unsigned char *) doc,
2876                            strlen(doc));
2877     return apdu;
2878 }
2879
2880 static Z_APDU *create_update_package(ZOOM_package p)
2881 {
2882     Z_APDU *apdu = 0;
2883     const char *first_db = "Default";
2884     int num_db;
2885     char **db = set_DatabaseNames(p->connection, p->options, &num_db);
2886     const char *action = ZOOM_options_get(p->options, "action");
2887     const char *recordIdOpaque = ZOOM_options_get(p->options, "recordIdOpaque");
2888     const char *recordIdNumber = ZOOM_options_get(p->options, "recordIdNumber");
2889     const char *record_buf = ZOOM_options_get(p->options, "record");
2890     const char *syntax_str = ZOOM_options_get(p->options, "syntax");
2891     int syntax_oid = VAL_NONE;
2892     int action_no = -1;
2893     
2894     if (syntax_str)
2895         syntax_oid = oid_getvalbyname(syntax_str);
2896     if (!record_buf)
2897     {
2898         record_buf = "void";
2899         syntax_oid = VAL_SUTRS;
2900     }
2901     if (syntax_oid == VAL_NONE)
2902         syntax_oid = VAL_TEXT_XML;
2903     
2904     if (num_db > 0)
2905         first_db = db[0];
2906     
2907     if (!action)
2908         action = "specialUpdate";
2909     
2910     if (!strcmp(action, "recordInsert"))
2911         action_no = Z_IUOriginPartToKeep_recordInsert;
2912     else if (!strcmp(action, "recordReplace"))
2913         action_no = Z_IUOriginPartToKeep_recordReplace;
2914     else if (!strcmp(action, "recordDelete"))
2915         action_no = Z_IUOriginPartToKeep_recordDelete;
2916     else if (!strcmp(action, "elementUpdate"))
2917         action_no = Z_IUOriginPartToKeep_elementUpdate;
2918     else if (!strcmp(action, "specialUpdate"))
2919         action_no = Z_IUOriginPartToKeep_specialUpdate;
2920     else
2921         return 0;
2922
2923     apdu = create_es_package (p, VAL_DBUPDATE);
2924     if (apdu)
2925     {
2926         Z_IUOriginPartToKeep *toKeep;
2927         Z_IUSuppliedRecords *notToKeep;
2928         Z_External *r = (Z_External *)
2929             odr_malloc (p->odr_out, sizeof(*r));
2930         
2931         apdu->u.extendedServicesRequest->taskSpecificParameters = r;
2932         
2933         r->direct_reference =
2934             yaz_oidval_to_z3950oid(p->odr_out, CLASS_EXTSERV,
2935                                    VAL_DBUPDATE);
2936         r->descriptor = 0;
2937         r->which = Z_External_update;
2938         r->indirect_reference = 0;
2939         r->u.update = (Z_IUUpdate *)
2940             odr_malloc(p->odr_out, sizeof(*r->u.update));
2941         
2942         r->u.update->which = Z_IUUpdate_esRequest;
2943         r->u.update->u.esRequest = (Z_IUUpdateEsRequest *)
2944             odr_malloc(p->odr_out, sizeof(*r->u.update->u.esRequest));
2945         toKeep = r->u.update->u.esRequest->toKeep = 
2946             (Z_IUOriginPartToKeep *)
2947             odr_malloc(p->odr_out, sizeof(*toKeep));
2948         
2949         toKeep->databaseName = odr_strdup(p->odr_out, first_db);
2950         toKeep->schema = 0;
2951         toKeep->elementSetName = 0;
2952         toKeep->actionQualifier = 0;
2953         toKeep->action = odr_intdup(p->odr_out, action_no);
2954         
2955         notToKeep = r->u.update->u.esRequest->notToKeep = 
2956             (Z_IUSuppliedRecords *)
2957             odr_malloc(p->odr_out, sizeof(*notToKeep));
2958         notToKeep->num = 1;
2959         notToKeep->elements = (Z_IUSuppliedRecords_elem **)
2960             odr_malloc(p->odr_out, sizeof(*notToKeep->elements));
2961         notToKeep->elements[0] = (Z_IUSuppliedRecords_elem *)
2962             odr_malloc(p->odr_out, sizeof(**notToKeep->elements));
2963         notToKeep->elements[0]->which = Z_IUSuppliedRecords_elem_opaque;
2964         if (recordIdOpaque)
2965         {
2966             notToKeep->elements[0]->u.opaque = 
2967                 odr_create_Odr_oct(p->odr_out,
2968                                    (const unsigned char *) recordIdOpaque,
2969                                    strlen(recordIdOpaque));
2970         }
2971         else if (recordIdNumber)
2972         {
2973             notToKeep->elements[0]->which = Z_IUSuppliedRecords_elem_number;
2974             
2975             notToKeep->elements[0]->u.number =
2976                 odr_intdup(p->odr_out, atoi(recordIdNumber));
2977         }
2978         else
2979             notToKeep->elements[0]->u.opaque = 0;
2980         notToKeep->elements[0]->supplementalId = 0;
2981         notToKeep->elements[0]->correlationInfo = 0;
2982         notToKeep->elements[0]->record =
2983             z_ext_record(p->odr_out, syntax_oid,
2984                          record_buf, strlen(record_buf));
2985     }
2986     if (0 && apdu)
2987     {
2988        ODR print = odr_createmem(ODR_PRINT);
2989
2990        z_APDU(print, &apdu, 0, 0);
2991        odr_destroy(print);
2992     }
2993     return apdu;
2994 }
2995
2996 ZOOM_API(void)
2997     ZOOM_package_send (ZOOM_package p, const char *type)
2998 {
2999     Z_APDU *apdu = 0;
3000     ZOOM_connection c;
3001     if (!p)
3002         return;
3003     c = p->connection;
3004     odr_reset (p->odr_out);
3005     xfree (p->buf_out);
3006     p->buf_out = 0;
3007     if (!strcmp(type, "itemorder"))
3008     {
3009         apdu = create_es_package (p, VAL_ITEMORDER);
3010         if (apdu)
3011         {
3012             Z_External *r = (Z_External *) odr_malloc (p->odr_out, sizeof(*r));
3013             
3014             r->direct_reference =
3015                 yaz_oidval_to_z3950oid(p->odr_out, CLASS_EXTSERV,
3016                                        VAL_ITEMORDER);
3017             r->descriptor = 0;
3018             r->which = Z_External_itemOrder;
3019             r->indirect_reference = 0;
3020             r->u.itemOrder = encode_item_order (p);
3021
3022             apdu->u.extendedServicesRequest->taskSpecificParameters = r;
3023         }
3024     }
3025     else if (!strcmp(type, "create"))  /* create database */
3026     {
3027         apdu = create_admin_package(p, Z_ESAdminOriginPartToKeep_create,
3028                                     0, 0);
3029     }   
3030     else if (!strcmp(type, "drop"))  /* drop database */
3031     {
3032         apdu = create_admin_package(p, Z_ESAdminOriginPartToKeep_drop,
3033                                     0, 0);
3034     }
3035     else if (!strcmp(type, "commit"))  /* commit changes */
3036     {
3037         apdu = create_admin_package(p, Z_ESAdminOriginPartToKeep_commit,
3038                                     0, 0);
3039     }
3040     else if (!strcmp(type, "update")) /* update record(s) */
3041     {
3042         apdu = create_update_package(p);
3043     }
3044     else if (!strcmp(type, "xmlupdate"))
3045     {
3046         apdu = create_xmlupdate_package(p);
3047     }
3048     if (apdu)
3049     {
3050         if (encode_APDU(p->connection, apdu, p->odr_out) == 0)
3051         {
3052             char *buf;
3053
3054             ZOOM_task task = ZOOM_connection_add_task (c, ZOOM_TASK_PACKAGE);
3055             task->u.package = p;
3056             buf = odr_getbuf(p->odr_out, &p->len_out, 0);
3057             p->buf_out = (char *) xmalloc (p->len_out);
3058             memcpy (p->buf_out, buf, p->len_out);
3059             
3060             (p->refcount)++;
3061             if (!c->async)
3062             {
3063                 while (ZOOM_event (1, &c))
3064                     ;
3065             }
3066         }
3067     }
3068 }
3069
3070 ZOOM_API(ZOOM_package)
3071     ZOOM_connection_package (ZOOM_connection c, ZOOM_options options)
3072 {
3073     ZOOM_package p = (ZOOM_package) xmalloc (sizeof(*p));
3074
3075     p->connection = c;
3076     p->odr_out = odr_createmem (ODR_ENCODE);
3077     p->options = ZOOM_options_create_with_parent2 (options, c->options);
3078     p->refcount = 1;
3079     p->buf_out = 0;
3080     p->len_out = 0;
3081     return p;
3082 }
3083
3084 ZOOM_API(void)
3085     ZOOM_package_destroy(ZOOM_package p)
3086 {
3087     if (!p)
3088         return;
3089     (p->refcount)--;
3090     if (p->refcount == 0)
3091     {
3092         odr_destroy (p->odr_out);
3093         xfree (p->buf_out);
3094         
3095         ZOOM_options_destroy (p->options);
3096         xfree (p);
3097     }
3098 }
3099
3100 ZOOM_API(const char *)
3101 ZOOM_package_option_get (ZOOM_package p, const char *key)
3102 {
3103     return ZOOM_options_get (p->options, key);
3104 }
3105
3106
3107 ZOOM_API(void)
3108 ZOOM_package_option_set (ZOOM_package p, const char *key,
3109                               const char *val)
3110 {
3111     ZOOM_options_set (p->options, key, val);
3112 }
3113
3114 static int ZOOM_connection_exec_task (ZOOM_connection c)
3115 {
3116     ZOOM_task task = c->tasks;
3117     zoom_ret ret = zoom_complete;
3118
3119     if (!task)
3120         return 0;
3121     yaz_log(log_details, "%p ZOOM_connection_exec_task type=%d run=%d",
3122              c, task->which, task->running);
3123     if (c->error != ZOOM_ERROR_NONE)
3124     {
3125         yaz_log(log_details, "%p ZOOM_connection_exec_task "
3126                 "removing tasks because of error = %d", c, c->error);
3127         ZOOM_connection_remove_tasks (c);
3128         return 0;
3129     }
3130     if (task->running)
3131     {
3132         yaz_log(log_details, "%p ZOOM_connection_exec_task "
3133                  "task already running", c);
3134         return 0;
3135     }
3136     task->running = 1;
3137     ret = zoom_complete;
3138     if (c->cs || task->which == ZOOM_TASK_CONNECT)
3139     {
3140         switch (task->which)
3141         {
3142         case ZOOM_TASK_SEARCH:
3143             if (c->proto == PROTO_HTTP)
3144                 ret = ZOOM_connection_srw_send_search(c);
3145             else
3146                 ret = ZOOM_connection_send_search(c);
3147             break;
3148         case ZOOM_TASK_RETRIEVE:
3149             if (c->proto == PROTO_HTTP)
3150                 ret = ZOOM_connection_srw_send_search(c);
3151             else
3152                 ret = send_present (c);
3153             break;
3154         case ZOOM_TASK_CONNECT:
3155             ret = do_connect(c);
3156             break;
3157         case ZOOM_TASK_SCAN:
3158             ret = send_scan(c);
3159             break;
3160         case ZOOM_TASK_PACKAGE:
3161             ret = send_package(c);
3162             break;
3163         case ZOOM_TASK_SORT:
3164             c->tasks->u.sort.resultset->r_sort_spec = 
3165                 c->tasks->u.sort.q->sort_spec;
3166             ret = send_sort(c, c->tasks->u.sort.resultset);
3167             break;
3168         }
3169     }
3170     else
3171     {
3172         yaz_log(log_details, "%p ZOOM_connection_exec_task "
3173                 "remove tasks because no connection exist", c);
3174         ZOOM_connection_remove_tasks (c);
3175     }
3176     if (ret == zoom_complete)
3177     {
3178         yaz_log(log_details, "%p ZOOM_connection_exec_task "
3179                  "task removed (complete)", c);
3180         ZOOM_connection_remove_task (c);
3181         return 0;
3182     }
3183     yaz_log(log_details, "%p ZOOM_connection_exec_task "
3184             "task pending", c);
3185     return 1;
3186 }
3187
3188 static zoom_ret send_sort_present (ZOOM_connection c)
3189 {
3190     zoom_ret r = zoom_complete;
3191
3192     if (c->tasks && c->tasks->which == ZOOM_TASK_SEARCH)
3193         r = send_sort (c, c->tasks->u.search.resultset);
3194     if (r == zoom_complete)
3195         r = send_present (c);
3196     return r;
3197 }
3198
3199 static int es_response (ZOOM_connection c,
3200                         Z_ExtendedServicesResponse *res)
3201 {
3202     if (!c->tasks || c->tasks->which != ZOOM_TASK_PACKAGE)
3203         return 0;
3204     if (res->diagnostics && res->num_diagnostics > 0)
3205         response_diag(c, res->diagnostics[0]);
3206     if (res->taskPackage &&
3207         res->taskPackage->which == Z_External_extendedService)
3208     {
3209         Z_TaskPackage *taskPackage = res->taskPackage->u.extendedService;
3210         Odr_oct *id = taskPackage->targetReference;
3211         
3212         if (id)
3213             ZOOM_options_setl (c->tasks->u.package->options,
3214                                "targetReference", (char*) id->buf, id->len);
3215     }
3216     if (res->taskPackage && 
3217         res->taskPackage->which == Z_External_octet)
3218     {
3219         Odr_oct *doc = res->taskPackage->u.octet_aligned;
3220         ZOOM_options_setl (c->tasks->u.package->options,
3221                            "xmlUpdateDoc", (char*) doc->buf, doc->len);
3222     }
3223     return 1;
3224 }
3225
3226 static void interpret_init_diag(ZOOM_connection c,
3227                                 Z_DiagnosticFormat *diag)
3228 {
3229     if (diag->num > 0)
3230     {
3231         Z_DiagnosticFormat_s *ds = diag->elements[0];
3232         if (ds->which == Z_DiagnosticFormat_s_defaultDiagRec)
3233             response_default_diag(c, ds->u.defaultDiagRec);
3234     }
3235 }
3236
3237
3238 static void interpret_otherinformation_field(ZOOM_connection c,
3239                                              Z_OtherInformation *ui)
3240 {
3241     int i;
3242     for (i = 0; i < ui->num_elements; i++)
3243     {
3244         Z_OtherInformationUnit *unit = ui->list[i];
3245         if (unit->which == Z_OtherInfo_externallyDefinedInfo &&
3246             unit->information.externallyDefinedInfo &&
3247             unit->information.externallyDefinedInfo->which ==
3248             Z_External_diag1) 
3249         {
3250             interpret_init_diag(c, unit->information.externallyDefinedInfo->u.diag1);
3251         } 
3252     }
3253 }
3254
3255 static void recv_apdu (ZOOM_connection c, Z_APDU *apdu)
3256 {
3257     Z_InitResponse *initrs;
3258     
3259     c->mask = 0;
3260     yaz_log(log_details, "%p recv_apdu apdu->which=%d", c, apdu->which);
3261     switch(apdu->which)
3262     {
3263     case Z_APDU_initResponse:
3264         yaz_log(log_api, "%p recv_apd: Received Init response", c);
3265         initrs = apdu->u.initResponse;
3266         ZOOM_connection_option_set(c, "serverImplementationId",
3267                                    initrs->implementationId ?
3268                                    initrs->implementationId : "");
3269         ZOOM_connection_option_set(c, "serverImplementationName",
3270                                    initrs->implementationName ?
3271                                    initrs->implementationName : "");
3272         ZOOM_connection_option_set(c, "serverImplementationVersion",
3273                                    initrs->implementationVersion ?
3274                                    initrs->implementationVersion : "");
3275         /* Set the three old options too, for old applications */
3276         ZOOM_connection_option_set(c, "targetImplementationId",
3277                                    initrs->implementationId ?
3278                                    initrs->implementationId : "");
3279         ZOOM_connection_option_set(c, "targetImplementationName",
3280                                    initrs->implementationName ?
3281                                    initrs->implementationName : "");
3282         ZOOM_connection_option_set(c, "targetImplementationVersion",
3283                                    initrs->implementationVersion ?
3284                                    initrs->implementationVersion : "");
3285         if (!*initrs->result)
3286         {
3287             Z_External *uif = initrs->userInformationField;
3288
3289             set_ZOOM_error(c, ZOOM_ERROR_INIT, 0); /* default error */
3290
3291             if (uif && uif->which == Z_External_userInfo1)
3292                 interpret_otherinformation_field(c, uif->u.userInfo1);
3293         }
3294         else
3295         {
3296             char *cookie =
3297                 yaz_oi_get_string_oidval (&apdu->u.initResponse->otherInfo,
3298                                           VAL_COOKIE, 1, 0);
3299             xfree (c->cookie_in);
3300             c->cookie_in = 0;
3301             if (cookie)
3302                 c->cookie_in = xstrdup(cookie);
3303             if (ODR_MASK_GET(initrs->options, Z_Options_namedResultSets) &&
3304                 ODR_MASK_GET(initrs->protocolVersion, Z_ProtocolVersion_3))
3305                 c->support_named_resultsets = 1;
3306             if (c->tasks)
3307             {
3308                 assert (c->tasks->which == ZOOM_TASK_CONNECT);
3309                 ZOOM_connection_remove_task (c);
3310             }
3311             ZOOM_connection_exec_task (c);
3312         }
3313         if (ODR_MASK_GET(initrs->options, Z_Options_negotiationModel))
3314         {
3315             NMEM tmpmem = nmem_create();
3316             Z_CharSetandLanguageNegotiation *p =
3317                 yaz_get_charneg_record(initrs->otherInfo);
3318             
3319             if (p)
3320             {
3321                 char *charset = NULL, *lang = NULL;
3322                 int sel;
3323                 
3324                 yaz_get_response_charneg(tmpmem, p, &charset, &lang, &sel);
3325                 yaz_log(log_details, "%p recv_apdu target accepted: "
3326                         "charset %s, language %s, select %d",
3327                         c,
3328                         charset ? charset : "none", lang ? lang : "none", sel);
3329                 if (charset)
3330                     ZOOM_connection_option_set (c, "negotiation-charset",
3331                                                 charset);
3332                 if (lang)
3333                     ZOOM_connection_option_set (c, "negotiation-lang",
3334                                                 lang);
3335
3336                 ZOOM_connection_option_set (
3337                     c,  "negotiation-charset-in-effect-for-records",
3338                     (sel != 0) ? "1" : "0");
3339                 nmem_destroy(tmpmem);
3340             }
3341         }       
3342         break;
3343     case Z_APDU_searchResponse:
3344         yaz_log(log_api, "%p recv_apdu Search response", c);
3345         handle_search_response (c, apdu->u.searchResponse);
3346         if (send_sort_present (c) == zoom_complete)
3347             ZOOM_connection_remove_task (c);
3348         break;
3349     case Z_APDU_presentResponse:
3350         yaz_log(log_api, "%p recv_apdu Present response", c);
3351         handle_present_response (c, apdu->u.presentResponse);
3352         if (send_present (c) == zoom_complete)
3353             ZOOM_connection_remove_task (c);
3354         break;
3355     case Z_APDU_sortResponse:
3356         yaz_log(log_api, "%p recv_apdu Sort response", c);
3357         sort_response (c, apdu->u.sortResponse);
3358         if (send_present (c) == zoom_complete)
3359             ZOOM_connection_remove_task (c);
3360         break;
3361     case Z_APDU_scanResponse:
3362         yaz_log(log_api, "%p recv_apdu Scan response", c);
3363         scan_response (c, apdu->u.scanResponse);
3364         ZOOM_connection_remove_task (c);
3365         break;
3366     case Z_APDU_extendedServicesResponse:
3367         yaz_log(log_api, "%p recv_apdu Extended Services response", c);
3368         es_response (c, apdu->u.extendedServicesResponse);
3369         ZOOM_connection_remove_task (c);
3370         break;
3371     case Z_APDU_close:
3372         yaz_log(log_api, "%p recv_apdu Close PDU", c);
3373         if (c->reconnect_ok)
3374         {
3375             do_close(c);
3376             c->tasks->running = 0;
3377             ZOOM_connection_insert_task (c, ZOOM_TASK_CONNECT);
3378         }
3379         else
3380         {
3381             set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
3382             do_close(c);
3383         }
3384         break;
3385     default:
3386         yaz_log(log_api, "%p Received unknown PDU", c);
3387         set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
3388         do_close(c);
3389     }
3390 }
3391
3392 #if HAVE_XML2
3393 static void handle_srw_response(ZOOM_connection c,
3394                                 Z_SRW_searchRetrieveResponse *res)
3395 {
3396     ZOOM_resultset resultset = 0;
3397     int i;
3398     NMEM nmem;
3399     ZOOM_Event event;
3400
3401     if (!c->tasks)
3402         return;
3403
3404     if (c->tasks->which == ZOOM_TASK_SEARCH)
3405         resultset = c->tasks->u.search.resultset;
3406     else if (c->tasks->which == ZOOM_TASK_RETRIEVE)
3407         resultset = c->tasks->u.retrieve.resultset;
3408     else
3409         return ;
3410
3411     event = ZOOM_Event_create(ZOOM_EVENT_RECV_SEARCH);
3412     ZOOM_connection_put_event(c, event);
3413
3414     resultset->size = 0;
3415
3416     yaz_log(log_details, "%p handle_srw_response got SRW response OK", c);
3417     
3418     if (res->numberOfRecords)
3419         resultset->size = *res->numberOfRecords;
3420
3421     for (i = 0; i<res->num_records; i++)
3422     {
3423         int pos;
3424
3425         Z_NamePlusRecord *npr = (Z_NamePlusRecord *)
3426             odr_malloc(c->odr_in, sizeof(Z_NamePlusRecord));
3427
3428         if (res->records[i].recordPosition && 
3429             *res->records[i].recordPosition > 0)
3430             pos = *res->records[i].recordPosition - 1;
3431         else
3432             pos = resultset->start + i;
3433         
3434         npr->databaseName = 0;
3435         npr->which = Z_NamePlusRecord_databaseRecord;
3436         npr->u.databaseRecord = (Z_External *)
3437             odr_malloc(c->odr_in, sizeof(Z_External));
3438         npr->u.databaseRecord->descriptor = 0;
3439         npr->u.databaseRecord->direct_reference =
3440             yaz_oidval_to_z3950oid(c->odr_in, CLASS_RECSYN, VAL_TEXT_XML);
3441         npr->u.databaseRecord->which = Z_External_octet;
3442
3443         npr->u.databaseRecord->u.octet_aligned = (Odr_oct *)
3444             odr_malloc(c->odr_in, sizeof(Odr_oct));
3445         npr->u.databaseRecord->u.octet_aligned->buf = (unsigned char*)
3446             res->records[i].recordData_buf;
3447         npr->u.databaseRecord->u.octet_aligned->len = 
3448             npr->u.databaseRecord->u.octet_aligned->size = 
3449             res->records[i].recordData_len;
3450         record_cache_add (resultset, npr, pos);
3451     }
3452     if (res->num_diagnostics > 0)
3453     {
3454         const char *uri = res->diagnostics[0].uri;
3455         if (uri)
3456         {
3457             int code = 0;       
3458             const char *cp;
3459             if ((cp = strrchr(uri, '/')))
3460                 code = atoi(cp+1);
3461             set_dset_error(c, code, uri,
3462                            res->diagnostics[0].details, 0);
3463         }
3464     }
3465     nmem = odr_extract_mem(c->odr_in);
3466     nmem_transfer(resultset->odr->mem, nmem);
3467     nmem_destroy(nmem);
3468 }
3469 #endif
3470
3471 #if HAVE_XML2
3472 static void handle_http(ZOOM_connection c, Z_HTTP_Response *hres)
3473 {
3474     int ret = -1;
3475     const char *content_type = z_HTTP_header_lookup(hres->headers,
3476                                                     "Content-Type");
3477     const char *connection_head = z_HTTP_header_lookup(hres->headers,
3478                                                        "Connection");
3479     c->mask = 0;
3480     yaz_log(log_details, "%p handle_http", c);
3481
3482     if (content_type && !yaz_strcmp_del("text/xml", content_type, "; "))
3483     {
3484         Z_SOAP *soap_package = 0;
3485         ODR o = c->odr_in;
3486         Z_SOAP_Handler soap_handlers[2] = {
3487             {"http://www.loc.gov/zing/srw/", 0,
3488              (Z_SOAP_fun) yaz_srw_codec},
3489             {0, 0, 0}
3490         };
3491         ret = z_soap_codec(o, &soap_package,
3492                            &hres->content_buf, &hres->content_len,
3493                            soap_handlers);
3494         if (!ret && soap_package->which == Z_SOAP_generic &&
3495             soap_package->u.generic->no == 0)
3496         {
3497             Z_SRW_PDU *sr = (Z_SRW_PDU*) soap_package->u.generic->p;
3498             if (sr->which == Z_SRW_searchRetrieve_response)
3499                 handle_srw_response(c, sr->u.response);
3500             else
3501                 ret = -1;
3502         }
3503         else if (!ret && (soap_package->which == Z_SOAP_fault
3504                           || soap_package->which == Z_SOAP_error))
3505         {
3506             set_HTTP_error(c, hres->code,
3507                            soap_package->u.fault->fault_code,
3508                            soap_package->u.fault->fault_string);
3509         }
3510         else
3511             ret = -1;
3512     }
3513     if (ret)
3514     {
3515         if (hres->code != 200)
3516             set_HTTP_error(c, hres->code, 0, 0);
3517         else
3518             set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
3519         do_close (c);
3520     }
3521     ZOOM_connection_remove_task(c);
3522     if (!strcmp(hres->version, "1.0"))
3523     {
3524         /* HTTP 1.0: only if Keep-Alive we stay alive.. */
3525         if (!connection_head || strcmp(connection_head, "Keep-Alive"))
3526             do_close(c);
3527     }
3528     else 
3529     {
3530         /* HTTP 1.1: only if no close we stay alive .. */
3531         if (connection_head && !strcmp(connection_head, "close"))
3532             do_close(c);
3533     }
3534 }
3535 #endif
3536
3537 static int do_read (ZOOM_connection c)
3538 {
3539     int r, more;
3540     ZOOM_Event event;
3541     
3542     event = ZOOM_Event_create (ZOOM_EVENT_RECV_DATA);
3543     ZOOM_connection_put_event (c, event);
3544     
3545     r = cs_get (c->cs, &c->buf_in, &c->len_in);
3546     more = cs_more(c->cs);
3547     yaz_log(log_details, "%p do_read len=%d more=%d", c, r, more);
3548     if (r == 1)
3549         return 0;
3550     if (r <= 0)
3551     {
3552         if (c->reconnect_ok)
3553         {
3554             do_close (c);
3555             c->reconnect_ok = 0;
3556             yaz_log(log_details, "%p do_read reconnect read", c);
3557             c->tasks->running = 0;
3558             ZOOM_connection_insert_task (c, ZOOM_TASK_CONNECT);
3559         }
3560         else
3561         {
3562             set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
3563             do_close (c);
3564         }
3565     }
3566     else
3567     {
3568         Z_GDU *gdu;
3569         ZOOM_Event event;
3570
3571         odr_reset (c->odr_in);
3572         odr_setbuf (c->odr_in, c->buf_in, r, 0);
3573         event = ZOOM_Event_create (ZOOM_EVENT_RECV_APDU);
3574         ZOOM_connection_put_event (c, event);
3575
3576         if (!z_GDU (c->odr_in, &gdu, 0, 0))
3577         {
3578             int x;
3579             int err = odr_geterrorx(c->odr_in, &x);
3580             char msg[60];
3581             const char *element = odr_getelement(c->odr_in);
3582             sprintf (msg, "ODR code %d:%d element=%-20s",
3583                      err, x, element ? element : "<unknown>");
3584             set_ZOOM_error(c, ZOOM_ERROR_DECODE, msg);
3585             do_close (c);
3586         }
3587         else if (gdu->which == Z_GDU_Z3950)
3588             recv_apdu (c, gdu->u.z3950);
3589         else if (gdu->which == Z_GDU_HTTP_Response)
3590         {
3591 #if HAVE_XML2
3592             handle_http (c, gdu->u.HTTP_Response);
3593 #else
3594             set_ZOOM_error(c, ZOOM_ERROR_DECODE, 0);
3595             do_close (c);
3596 #endif
3597         }
3598         c->reconnect_ok = 0;
3599     }
3600     return 1;
3601 }
3602
3603 static zoom_ret do_write_ex (ZOOM_connection c, char *buf_out, int len_out)
3604 {
3605     int r;
3606     ZOOM_Event event;
3607     
3608     event = ZOOM_Event_create(ZOOM_EVENT_SEND_DATA);
3609     ZOOM_connection_put_event (c, event);
3610
3611     yaz_log(log_details, "%p do_write_ex len=%d", c, len_out);
3612     if ((r = cs_put(c->cs, buf_out, len_out)) < 0)
3613     {
3614         yaz_log(log_details, "%p do_write_ex write failed", c);
3615         if (c->reconnect_ok)
3616         {
3617             do_close (c);
3618             c->reconnect_ok = 0;
3619             yaz_log(log_details, "%p do_write_ex reconnect write", c);
3620             c->tasks->running = 0;
3621             ZOOM_connection_insert_task (c, ZOOM_TASK_CONNECT);
3622             return zoom_pending;
3623         }
3624         if (c->state == STATE_CONNECTING)
3625             set_ZOOM_error(c, ZOOM_ERROR_CONNECT, c->host_port);
3626         else
3627             set_ZOOM_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
3628         do_close (c);
3629         return zoom_complete;
3630     }
3631     else if (r == 1)
3632     {    
3633         c->mask = ZOOM_SELECT_EXCEPT;
3634         if (c->cs->io_pending & CS_WANT_WRITE)
3635             c->mask += ZOOM_SELECT_WRITE;
3636         if (c->cs->io_pending & CS_WANT_READ)
3637             c->mask += ZOOM_SELECT_READ;
3638         yaz_log(log_details, "%p do_write_ex write incomplete mask=%d",
3639                 c, c->mask);
3640     }
3641     else
3642     {
3643         c->mask = ZOOM_SELECT_READ|ZOOM_SELECT_EXCEPT;
3644         yaz_log(log_details, "%p do_write_ex write complete mask=%d",
3645                 c, c->mask);
3646     }
3647     return zoom_pending;
3648 }
3649
3650 static zoom_ret do_write(ZOOM_connection c)
3651 {
3652     return do_write_ex (c, c->buf_out, c->len_out);
3653 }
3654
3655
3656 ZOOM_API(const char *)
3657 ZOOM_connection_option_get (ZOOM_connection c, const char *key)