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