Refactor ZOOM event, hide more definitions
[yaz-moved-to-github.git] / src / zoom-c.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 1995-2010 Index Data
3  * See the file LICENSE for details.
4  */
5 /**
6  * \file zoom-c.c
7  * \brief Implements ZOOM C interface.
8  */
9
10 #include <assert.h>
11 #include <string.h>
12 #include <errno.h>
13 #include "zoom-p.h"
14
15 #include <yaz/yaz-util.h>
16 #include <yaz/xmalloc.h>
17 #include <yaz/otherinfo.h>
18 #include <yaz/log.h>
19 #include <yaz/diagbib1.h>
20 #include <yaz/charneg.h>
21 #include <yaz/query-charset.h>
22 #include <yaz/snprintf.h>
23 #include <yaz/facet.h>
24
25 #include <yaz/shptr.h>
26
27 #if SHPTR
28 YAZ_SHPTR_TYPE(WRBUF)
29 #endif
30
31 static int log_api0 = 0;
32 static int log_details0 = 0;
33
34 static void resultset_destroy(ZOOM_resultset r);
35 static zoom_ret do_write_ex(ZOOM_connection c, char *buf_out, int len_out);
36
37 static void initlog(void)
38 {
39     static int log_level_initialized = 0;
40     if (!log_level_initialized)
41     {
42         log_api0 = yaz_log_module_level("zoom");
43         log_details0 = yaz_log_module_level("zoomdetails");
44         log_level_initialized = 1;
45     }
46 }
47
48 void ZOOM_connection_remove_tasks(ZOOM_connection c);
49
50 void ZOOM_set_dset_error(ZOOM_connection c, int error,
51                          const char *dset,
52                          const char *addinfo, const char *addinfo2)
53 {
54     char *cp;
55
56     xfree(c->addinfo);
57     c->addinfo = 0;
58     c->error = error;
59     if (!c->diagset || strcmp(dset, c->diagset))
60     {
61         xfree(c->diagset);
62         c->diagset = xstrdup(dset);
63         /* remove integer part from SRW diagset .. */
64         if ((cp = strrchr(c->diagset, '/')))
65             *cp = '\0';
66     }
67     if (addinfo && addinfo2)
68     {
69         c->addinfo = (char*) xmalloc(strlen(addinfo) + strlen(addinfo2) + 2);
70         strcpy(c->addinfo, addinfo);
71         strcat(c->addinfo, addinfo2);
72     }
73     else if (addinfo)
74         c->addinfo = xstrdup(addinfo);
75     if (error != ZOOM_ERROR_NONE)
76     {
77         yaz_log(c->log_api, "%p set_dset_error %s %s:%d %s %s",
78                 c, c->host_port ? c->host_port : "<>", dset, error,
79                 addinfo ? addinfo : "",
80                 addinfo2 ? addinfo2 : "");
81         ZOOM_connection_remove_tasks(c);
82     }
83 }
84
85 int ZOOM_uri_to_code(const char *uri)
86 {
87     int code = 0;       
88     const char *cp;
89     if ((cp = strrchr(uri, '/')))
90         code = atoi(cp+1);
91     return code;
92 }
93
94
95
96 void ZOOM_set_error(ZOOM_connection c, int error, const char *addinfo)
97 {
98     ZOOM_set_dset_error(c, error, "ZOOM", addinfo, 0);
99 }
100
101 static void clear_error(ZOOM_connection c)
102 {
103     /*
104      * If an error is tied to an operation then it's ok to clear: for
105      * example, a diagnostic returned from a search is cleared by a
106      * subsequent search.  However, problems such as Connection Lost
107      * or Init Refused are not cleared, because they are not
108      * recoverable: doing another search doesn't help.
109      */
110
111     ZOOM_connection_remove_events(c);
112     switch (c->error)
113     {
114     case ZOOM_ERROR_CONNECT:
115     case ZOOM_ERROR_MEMORY:
116     case ZOOM_ERROR_DECODE:
117     case ZOOM_ERROR_CONNECTION_LOST:
118     case ZOOM_ERROR_INIT:
119     case ZOOM_ERROR_INTERNAL:
120     case ZOOM_ERROR_UNSUPPORTED_PROTOCOL:
121         break;
122     default:
123         ZOOM_set_error(c, ZOOM_ERROR_NONE, 0);
124     }
125 }
126
127 void ZOOM_connection_show_task(ZOOM_task task)
128 {
129     switch(task->which)
130     {
131     case ZOOM_TASK_SEARCH:
132         yaz_log(YLOG_LOG, "search p=%p", task);
133         break;
134     case ZOOM_TASK_RETRIEVE:
135         yaz_log(YLOG_LOG, "retrieve p=%p", task);
136         break;
137     case ZOOM_TASK_CONNECT:
138         yaz_log(YLOG_LOG, "connect p=%p", task);
139         break;
140     case ZOOM_TASK_SCAN:
141         yaz_log(YLOG_LOG, "scan p=%p", task);
142         break;
143     }
144 }
145
146 void ZOOM_connection_show_tasks(ZOOM_connection c)
147 {
148     ZOOM_task task;
149     yaz_log(YLOG_LOG, "connection p=%p tasks", c);
150     for (task = c->tasks; task; task = task->next)
151         ZOOM_connection_show_task(task);
152 }
153
154 ZOOM_task ZOOM_connection_add_task(ZOOM_connection c, int which)
155 {
156     ZOOM_task *taskp = &c->tasks;
157     while (*taskp)
158         taskp = &(*taskp)->next;
159     *taskp = (ZOOM_task) xmalloc(sizeof(**taskp));
160     (*taskp)->running = 0;
161     (*taskp)->which = which;
162     (*taskp)->next = 0;
163     clear_error(c);
164     return *taskp;
165 }
166
167 ZOOM_API(int) ZOOM_connection_is_idle(ZOOM_connection c)
168 {
169     return c->tasks ? 0 : 1;
170 }
171
172 ZOOM_task ZOOM_connection_insert_task(ZOOM_connection c, int which)
173 {
174     ZOOM_task task = (ZOOM_task) xmalloc(sizeof(*task));
175
176     task->next = c->tasks;
177     c->tasks = task;
178
179     task->running = 0;
180     task->which = which;
181     clear_error(c);
182     return task;
183 }
184
185 void ZOOM_connection_remove_task(ZOOM_connection c)
186 {
187     ZOOM_task task = c->tasks;
188
189     if (task)
190     {
191         c->tasks = task->next;
192         switch (task->which)
193         {
194         case ZOOM_TASK_SEARCH:
195             resultset_destroy(task->u.search.resultset);
196             xfree(task->u.search.syntax);
197             xfree(task->u.search.elementSetName);
198             break;
199         case ZOOM_TASK_RETRIEVE:
200             resultset_destroy(task->u.retrieve.resultset);
201             xfree(task->u.retrieve.syntax);
202             xfree(task->u.retrieve.elementSetName);
203             break;
204         case ZOOM_TASK_CONNECT:
205             break;
206         case ZOOM_TASK_SCAN:
207             ZOOM_scanset_destroy(task->u.scan.scan);
208             break;
209         case ZOOM_TASK_PACKAGE:
210             ZOOM_package_destroy(task->u.package);
211             break;
212         case ZOOM_TASK_SORT:
213             resultset_destroy(task->u.sort.resultset);
214             ZOOM_query_destroy(task->u.sort.q);
215             break;
216         default:
217             assert(0);
218         }
219         xfree(task);
220
221         if (!c->tasks)
222         {
223             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_END);
224             ZOOM_connection_put_event(c, event);
225         }
226     }
227 }
228
229 void ZOOM_connection_remove_tasks(ZOOM_connection c)
230 {
231     while (c->tasks)
232         ZOOM_connection_remove_task(c);
233 }
234
235
236 ZOOM_API(ZOOM_connection)
237     ZOOM_connection_create(ZOOM_options options)
238 {
239     ZOOM_connection c = (ZOOM_connection) xmalloc(sizeof(*c));
240
241     initlog();
242
243     c->log_api = log_api0;
244     c->log_details = log_details0;
245
246     yaz_log(c->log_api, "%p ZOOM_connection_create", c);
247
248     c->proto = PROTO_Z3950;
249     c->cs = 0;
250     ZOOM_connection_set_mask(c, 0);
251     c->reconnect_ok = 0;
252     c->state = STATE_IDLE;
253     c->addinfo = 0;
254     c->diagset = 0;
255     ZOOM_set_error(c, ZOOM_ERROR_NONE, 0);
256     c->buf_in = 0;
257     c->len_in = 0;
258     c->buf_out = 0;
259     c->len_out = 0;
260     c->resultsets = 0;
261
262     c->options = ZOOM_options_create_with_parent(options);
263
264     c->host_port = 0;
265     c->path = 0;
266     c->proxy = 0;
267     
268     c->charset = c->lang = 0;
269
270     c->cookie_out = 0;
271     c->cookie_in = 0;
272     c->client_IP = 0;
273     c->tasks = 0;
274
275     c->user = 0;
276     c->group = 0;
277     c->password = 0;
278
279     c->maximum_record_size = 0;
280     c->preferred_message_size = 0;
281
282     c->odr_in = odr_createmem(ODR_DECODE);
283     c->odr_out = odr_createmem(ODR_ENCODE);
284     c->odr_print = 0;
285
286     c->async = 0;
287     c->support_named_resultsets = 0;
288     c->last_event = ZOOM_EVENT_NONE;
289
290     c->m_queue_front = 0;
291     c->m_queue_back = 0;
292
293     c->sru_version = 0;
294     c->no_redirects = 0;
295     return c;
296 }
297
298
299 /* set database names. Take local databases (if set); otherwise
300    take databases given in ZURL (if set); otherwise use Default */
301 char **ZOOM_connection_get_databases(ZOOM_connection con, ZOOM_options options,
302                                      int *num, ODR odr)
303 {
304     char **databaseNames;
305     const char *cp = ZOOM_options_get(options, "databaseName");
306     
307     if ((!cp || !*cp) && con->host_port)
308     {
309         if (strncmp(con->host_port, "unix:", 5) == 0)
310             cp = strchr(con->host_port+5, ':');
311         else
312             cp = strchr(con->host_port, '/');
313         if (cp)
314             cp++;
315     }
316     if (!cp)
317         cp = "Default";
318     nmem_strsplit(odr_getmem(odr), "+", cp,  &databaseNames, num);
319     return databaseNames;
320 }
321
322 ZOOM_API(ZOOM_connection)
323     ZOOM_connection_new(const char *host, int portnum)
324 {
325     ZOOM_connection c = ZOOM_connection_create(0);
326
327     ZOOM_connection_connect(c, host, portnum);
328     return c;
329 }
330
331 static zoom_sru_mode get_sru_mode_from_string(const char *s)
332 {
333     if (!s || !*s)
334         return zoom_sru_soap;
335     if (!yaz_matchstr(s, "soap"))
336         return zoom_sru_soap;
337     else if (!yaz_matchstr(s, "get"))
338         return zoom_sru_get;
339     else if (!yaz_matchstr(s, "post"))
340         return zoom_sru_post;
341     return zoom_sru_error;
342 }
343
344 ZOOM_API(void)
345     ZOOM_connection_connect(ZOOM_connection c,
346                             const char *host, int portnum)
347 {
348     const char *val;
349     ZOOM_task task;
350
351     initlog();
352
353     yaz_log(c->log_api, "%p ZOOM_connection_connect host=%s portnum=%d",
354             c, host ? host : "null", portnum);
355
356     ZOOM_set_error(c, ZOOM_ERROR_NONE, 0);
357     ZOOM_connection_remove_tasks(c);
358
359     if (c->odr_print)
360     {
361         odr_setprint(c->odr_print, 0); /* prevent destroy from fclose'ing */
362         odr_destroy(c->odr_print);
363     }
364     if (ZOOM_options_get_bool(c->options, "apdulog", 0))
365     {
366         c->odr_print = odr_createmem(ODR_PRINT);
367         odr_setprint(c->odr_print, yaz_log_file());
368     }
369     else
370         c->odr_print = 0;
371
372     if (c->cs)
373     {
374         yaz_log(c->log_details, "%p ZOOM_connection_connect reconnect ok", c);
375         c->reconnect_ok = 1;
376         return;
377     }
378     yaz_log(c->log_details, "%p ZOOM_connection_connect connect", c);
379     xfree(c->proxy);
380     c->proxy = 0;
381     val = ZOOM_options_get(c->options, "proxy");
382     if (val && *val)
383     {
384         yaz_log(c->log_details, "%p ZOOM_connection_connect proxy=%s", c, val);
385         c->proxy = xstrdup(val);
386     }
387
388     xfree(c->charset);
389     c->charset = 0;
390     val = ZOOM_options_get(c->options, "charset");
391     if (val && *val)
392     {
393         yaz_log(c->log_details, "%p ZOOM_connection_connect charset=%s", c, val);
394         c->charset = xstrdup(val);
395     }
396
397     xfree(c->lang);
398     val = ZOOM_options_get(c->options, "lang");
399     if (val && *val)
400     {
401         yaz_log(c->log_details, "%p ZOOM_connection_connect lang=%s", c, val);
402         c->lang = xstrdup(val);
403     }
404     else
405         c->lang = 0;
406
407     if (host)
408     {
409         xfree(c->host_port);
410         if (portnum)
411         {
412             char hostn[128];
413             sprintf(hostn, "%.80s:%d", host, portnum);
414             c->host_port = xstrdup(hostn);
415         }
416         else
417             c->host_port = xstrdup(host);
418     }        
419
420     {
421         /*
422          * If the "<scheme>:" part of the host string is preceded by one
423          * or more comma-separated <name>=<value> pairs, these are taken
424          * to be options to be set on the connection object.  Among other
425          * applications, this facility can be used to embed authentication
426          * in a host string:
427          *          user=admin,password=secret,tcp:localhost:9999
428          */
429         char *remainder = c->host_port;
430         char *pcolon = strchr(remainder, ':');
431         char *pcomma;
432         char *pequals;
433         while ((pcomma = strchr(remainder, ',')) != 0 &&
434                (pcolon == 0 || pcomma < pcolon)) {
435             *pcomma = '\0';
436             if ((pequals = strchr(remainder, '=')) != 0) {
437                 *pequals = '\0';
438                 /*printf("# setting '%s'='%s'\n", remainder, pequals+1);*/
439                 ZOOM_connection_option_set(c, remainder, pequals+1);
440             }
441             remainder = pcomma+1;
442         }
443
444         if (remainder != c->host_port) {
445             xfree(c->host_port);
446             c->host_port = xstrdup(remainder);
447             /*printf("# reset hp='%s'\n", remainder);*/
448         }
449     }
450
451     val = ZOOM_options_get(c->options, "sru");
452     c->sru_mode = get_sru_mode_from_string(val);
453
454     xfree(c->sru_version);
455     val = ZOOM_options_get(c->options, "sru_version");
456     c->sru_version = xstrdup(val ? val : "1.2");
457
458     ZOOM_options_set(c->options, "host", c->host_port);
459
460     xfree(c->cookie_out);
461     c->cookie_out = 0;
462     val = ZOOM_options_get(c->options, "cookie");
463     if (val && *val)
464     { 
465         yaz_log(c->log_details, "%p ZOOM_connection_connect cookie=%s", c, val);
466         c->cookie_out = xstrdup(val);
467     }
468
469     xfree(c->client_IP);
470     c->client_IP = 0;
471     val = ZOOM_options_get(c->options, "clientIP");
472     if (val && *val)
473     {
474         yaz_log(c->log_details, "%p ZOOM_connection_connect clientIP=%s",
475                 c, val);
476         c->client_IP = xstrdup(val);
477     }
478
479     xfree(c->group);
480     c->group = 0;
481     val = ZOOM_options_get(c->options, "group");
482     if (val && *val)
483         c->group = xstrdup(val);
484
485     xfree(c->user);
486     c->user = 0;
487     val = ZOOM_options_get(c->options, "user");
488     if (val && *val)
489         c->user = xstrdup(val);
490
491     xfree(c->password);
492     c->password = 0;
493     val = ZOOM_options_get(c->options, "password");
494     if (!val)
495         val = ZOOM_options_get(c->options, "pass");
496
497     if (val && *val)
498         c->password = xstrdup(val);
499     
500     c->maximum_record_size =
501         ZOOM_options_get_int(c->options, "maximumRecordSize", 1024*1024);
502     c->preferred_message_size =
503         ZOOM_options_get_int(c->options, "preferredMessageSize", 1024*1024);
504
505     c->async = ZOOM_options_get_bool(c->options, "async", 0);
506     yaz_log(c->log_details, "%p ZOOM_connection_connect async=%d", c, c->async);
507  
508     task = ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
509
510     if (!c->async)
511     {
512         while (ZOOM_event(1, &c))
513             ;
514     }
515 }
516
517 ZOOM_API(void) ZOOM_resultset_release(ZOOM_resultset r)
518 {
519 #if ZOOM_RESULT_LISTS
520 #else
521     if (r->connection)
522     {
523         /* remove ourselves from the resultsets in connection */
524         ZOOM_resultset *rp = &r->connection->resultsets;
525         while (1)
526         {
527             assert(*rp);   /* we must be in this list!! */
528             if (*rp == r)
529             {   /* OK, we're here - take us out of it */
530                 *rp = (*rp)->next;
531                 break;
532             }
533             rp = &(*rp)->next;
534         }
535         r->connection = 0;
536     }
537 #endif
538 }
539
540 ZOOM_API(void)
541     ZOOM_connection_destroy(ZOOM_connection c)
542 {
543 #if ZOOM_RESULT_LISTS
544     ZOOM_resultsets list;
545 #else
546     ZOOM_resultset r;
547 #endif
548     if (!c)
549         return;
550     yaz_log(c->log_api, "%p ZOOM_connection_destroy", c);
551     if (c->cs)
552         cs_close(c->cs);
553
554 #if ZOOM_RESULT_LISTS
555     // Remove the connection's usage of resultsets
556     list = c->resultsets;
557     while (list) {
558         ZOOM_resultsets removed = list;
559         ZOOM_resultset_destroy(list->resultset);
560         list = list->next;
561         xfree(removed);
562     }
563 #else
564     for (r = c->resultsets; r; r = r->next)
565         r->connection = 0;
566 #endif
567
568     xfree(c->buf_in);
569     xfree(c->addinfo);
570     xfree(c->diagset);
571     odr_destroy(c->odr_in);
572     odr_destroy(c->odr_out);
573     if (c->odr_print)
574     {
575         odr_setprint(c->odr_print, 0); /* prevent destroy from fclose'ing */
576         odr_destroy(c->odr_print);
577     }
578     ZOOM_options_destroy(c->options);
579     ZOOM_connection_remove_tasks(c);
580     ZOOM_connection_remove_events(c);
581     xfree(c->host_port);
582     xfree(c->path);
583     xfree(c->proxy);
584     xfree(c->charset);
585     xfree(c->lang);
586     xfree(c->cookie_out);
587     xfree(c->cookie_in);
588     xfree(c->client_IP);
589     xfree(c->user);
590     xfree(c->group);
591     xfree(c->password);
592     xfree(c->sru_version);
593     xfree(c);
594 }
595
596 void ZOOM_resultset_addref(ZOOM_resultset r)
597 {
598     if (r)
599     {
600         yaz_mutex_enter(r->mutex);
601         (r->refcount)++;
602         yaz_log(log_details0, "%p ZOOM_resultset_addref count=%d",
603                 r, r->refcount);
604         yaz_mutex_leave(r->mutex);
605     }
606 }
607
608 ZOOM_resultset ZOOM_resultset_create(void)
609 {
610     int i;
611     ZOOM_resultset r = (ZOOM_resultset) xmalloc(sizeof(*r));
612
613     initlog();
614
615     yaz_log(log_details0, "%p ZOOM_resultset_create", r);
616     r->refcount = 1;
617     r->size = 0;
618     r->odr = odr_createmem(ODR_ENCODE);
619     r->piggyback = 1;
620     r->setname = 0;
621     r->schema = 0;
622     r->step = 0;
623     for (i = 0; i<RECORD_HASH_SIZE; i++)
624         r->record_hash[i] = 0;
625     r->r_sort_spec = 0;
626     r->query = 0;
627     r->connection = 0;
628     r->databaseNames = 0;
629     r->num_databaseNames = 0;
630     r->facets = 0;
631     r->num_facets = 0;
632     r->facets_names = 0;
633     r->mutex = 0;
634     yaz_mutex_create(&r->mutex);
635 #if SHPTR
636     {
637         WRBUF w = wrbuf_alloc();
638         YAZ_SHPTR_INIT(r->record_wrbuf, w);
639     }
640 #endif
641     return r;
642 }
643
644 ZOOM_API(ZOOM_resultset)
645     ZOOM_connection_search_pqf(ZOOM_connection c, const char *q)
646 {
647     ZOOM_resultset r;
648     ZOOM_query s = ZOOM_query_create();
649
650     ZOOM_query_prefix(s, q);
651
652     r = ZOOM_connection_search(c, s);
653     ZOOM_query_destroy(s);
654     return r;
655 }
656
657 ZOOM_API(ZOOM_resultset)
658     ZOOM_connection_search(ZOOM_connection c, ZOOM_query q)
659 {
660     ZOOM_resultset r = ZOOM_resultset_create();
661     ZOOM_task task;
662     const char *cp;
663     int start, count;
664     const char *syntax, *elementSetName;
665 #if ZOOM_RESULT_LISTS
666     ZOOM_resultsets set;
667 #endif
668
669     yaz_log(c->log_api, "%p ZOOM_connection_search set %p query %p", c, r, q);
670     r->r_sort_spec = ZOOM_query_get_sortspec(q);
671     r->query = q;
672
673     r->options = ZOOM_options_create_with_parent(c->options);
674     
675     start = ZOOM_options_get_int(r->options, "start", 0);
676     count = ZOOM_options_get_int(r->options, "count", 0);
677     {
678         /* If "presentChunk" is defined use that; otherwise "step" */
679         const char *cp = ZOOM_options_get(r->options, "presentChunk");
680         r->step = ZOOM_options_get_int(r->options,
681                                        (cp != 0 ? "presentChunk": "step"), 0);
682     }
683     r->piggyback = ZOOM_options_get_bool(r->options, "piggyback", 1);
684     cp = ZOOM_options_get(r->options, "setname");
685     if (cp)
686         r->setname = xstrdup(cp);
687     cp = ZOOM_options_get(r->options, "schema");
688     if (cp)
689         r->schema = xstrdup(cp);
690
691     r->databaseNames = ZOOM_connection_get_databases(c, c->options, &r->num_databaseNames,
692                                          r->odr);
693     
694     r->connection = c;
695
696 #if ZOOM_RESULT_LISTS
697     yaz_log(log_details, "%p ZOOM_connection_search: Adding new resultset (%p) to resultsets (%p) ", c, r, c->resultsets);
698     set = xmalloc(sizeof(*set));
699     ZOOM_resultset_addref(r);
700     set->resultset = r;
701     set->next = c->resultsets;
702     c->resultsets = set;
703 #else
704     r->next = c->resultsets;
705     c->resultsets = r;
706 #endif
707     if (c->host_port && c->proto == PROTO_HTTP)
708     {
709         if (!c->cs)
710         {
711             yaz_log(c->log_details, "ZOOM_connection_search: no comstack");
712             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
713         }
714         else
715         {
716             yaz_log(c->log_details, "ZOOM_connection_search: reconnect");
717             c->reconnect_ok = 1;
718         }
719     }
720
721     task = ZOOM_connection_add_task(c, ZOOM_TASK_SEARCH);
722     task->u.search.resultset = r;
723     task->u.search.start = start;
724     task->u.search.count = count;
725     task->u.search.recv_search_fired = 0;
726
727     syntax = ZOOM_options_get(r->options, "preferredRecordSyntax"); 
728     task->u.search.syntax = syntax ? xstrdup(syntax) : 0;
729     elementSetName = ZOOM_options_get(r->options, "elementSetName");
730     task->u.search.elementSetName = elementSetName 
731         ? xstrdup(elementSetName) : 0;
732    
733     ZOOM_resultset_addref(r);
734
735     ZOOM_query_addref(q);
736
737     if (!c->async)
738     {
739         while (ZOOM_event(1, &c))
740             ;
741     }
742     return r;
743 }
744
745 ZOOM_API(void)
746     ZOOM_resultset_sort(ZOOM_resultset r,
747                          const char *sort_type, const char *sort_spec)
748 {
749     (void) ZOOM_resultset_sort1(r, sort_type, sort_spec);
750 }
751
752 ZOOM_API(int)
753     ZOOM_resultset_sort1(ZOOM_resultset r,
754                          const char *sort_type, const char *sort_spec)
755 {
756     ZOOM_connection c = r->connection;
757     ZOOM_task task;
758     ZOOM_query newq;
759
760     newq = ZOOM_query_create();
761     if (ZOOM_query_sortby(newq, sort_spec) < 0)
762         return -1;
763
764     yaz_log(c->log_api, "%p ZOOM_resultset_sort r=%p sort_type=%s sort_spec=%s",
765             r, r, sort_type, sort_spec);
766     if (!c)
767         return 0;
768
769     if (c->host_port && c->proto == PROTO_HTTP)
770     {
771         if (!c->cs)
772         {
773             yaz_log(c->log_details, "%p ZOOM_resultset_sort: no comstack", r);
774             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
775         }
776         else
777         {
778             yaz_log(c->log_details, "%p ZOOM_resultset_sort: prepare reconnect",
779                     r);
780             c->reconnect_ok = 1;
781         }
782     }
783     
784     ZOOM_resultset_cache_reset(r);
785     task = ZOOM_connection_add_task(c, ZOOM_TASK_SORT);
786     task->u.sort.resultset = r;
787     task->u.sort.q = newq;
788
789     ZOOM_resultset_addref(r);  
790
791     if (!c->async)
792     {
793         while (ZOOM_event(1, &c))
794             ;
795     }
796
797     return 0;
798 }
799
800 ZOOM_API(void)
801     ZOOM_resultset_destroy(ZOOM_resultset r)
802 {
803     resultset_destroy(r);
804 }
805
806 static void resultset_destroy(ZOOM_resultset r)
807 {
808     if (!r)
809         return;
810     yaz_mutex_enter(r->mutex);
811     (r->refcount)--;
812     yaz_log(log_details0, "%p ZOOM_resultset_destroy r=%p count=%d",
813             r, r, r->refcount);
814     if (r->refcount == 0)
815     {
816         yaz_mutex_leave(r->mutex);
817
818         yaz_log(log_details0, "%p ZOOM_connection resultset_destroy: Deleting resultset (%p) ", r->connection, r);
819         ZOOM_resultset_cache_reset(r);
820         ZOOM_resultset_release(r);
821         ZOOM_query_destroy(r->query);
822         ZOOM_options_destroy(r->options);
823         odr_destroy(r->odr);
824         xfree(r->setname);
825         xfree(r->schema);
826         yaz_mutex_destroy(&r->mutex);
827 #if SHPTR
828         YAZ_SHPTR_DEC(r->record_wrbuf, wrbuf_destroy);
829 #endif
830         xfree(r);
831     }
832     else
833         yaz_mutex_leave(r->mutex);
834 }
835
836 ZOOM_API(size_t)
837     ZOOM_resultset_size(ZOOM_resultset r)
838 {
839     return r->size;
840 }
841
842 int ZOOM_test_reconnect(ZOOM_connection c)
843 {
844     ZOOM_Event event;
845
846     if (!c->reconnect_ok)
847         return 0;
848     ZOOM_connection_close(c);
849     c->reconnect_ok = 0;
850     c->tasks->running = 0;
851     ZOOM_connection_insert_task(c, ZOOM_TASK_CONNECT);
852
853     event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
854     ZOOM_connection_put_event(c, event);
855
856     return 1;
857 }
858
859 static void ZOOM_resultset_retrieve(ZOOM_resultset r,
860                                     int force_sync, int start, int count)
861 {
862     ZOOM_task task;
863     ZOOM_connection c;
864     const char *cp;
865     const char *syntax, *elementSetName;
866
867     if (!r)
868         return;
869     yaz_log(log_details0, "%p ZOOM_resultset_retrieve force_sync=%d start=%d"
870             " count=%d", r, force_sync, start, count);
871     c = r->connection;
872     if (!c)
873         return;
874
875     if (c->host_port && c->proto == PROTO_HTTP)
876     {
877         if (!c->cs)
878         {
879             yaz_log(log_details0, "%p ZOOM_resultset_retrieve: no comstack", r);
880             ZOOM_connection_add_task(c, ZOOM_TASK_CONNECT);
881         }
882         else
883         {
884             yaz_log(log_details0, "%p ZOOM_resultset_retrieve: prepare "
885                     "reconnect", r);
886             c->reconnect_ok = 1;
887         }
888     }
889     task = ZOOM_connection_add_task(c, ZOOM_TASK_RETRIEVE);
890     task->u.retrieve.resultset = r;
891     task->u.retrieve.start = start;
892     task->u.retrieve.count = count;
893
894     syntax = ZOOM_options_get(r->options, "preferredRecordSyntax"); 
895     task->u.retrieve.syntax = syntax ? xstrdup(syntax) : 0;
896     elementSetName = ZOOM_options_get(r->options, "elementSetName");
897     task->u.retrieve.elementSetName = elementSetName 
898         ? xstrdup(elementSetName) : 0;
899
900     cp = ZOOM_options_get(r->options, "schema");
901     if (cp)
902     {
903         if (!r->schema || strcmp(r->schema, cp))
904         {
905             xfree(r->schema);
906             r->schema = xstrdup(cp);
907         }
908     }
909
910     ZOOM_resultset_addref(r);
911
912     if (!r->connection->async || force_sync)
913         while (r->connection && ZOOM_event(1, &r->connection))
914             ;
915 }
916
917 ZOOM_API(void)
918     ZOOM_resultset_records(ZOOM_resultset r, ZOOM_record *recs,
919                            size_t start, size_t count)
920 {
921     int force_present = 0;
922
923     if (!r)
924         return ;
925     yaz_log(log_api0, "%p ZOOM_resultset_records r=%p start=%ld count=%ld",
926             r, r, (long) start, (long) count);
927     if (count && recs)
928         force_present = 1;
929     ZOOM_resultset_retrieve(r, force_present, start, count);
930     if (force_present)
931     {
932         size_t i;
933         for (i = 0; i< count; i++)
934             recs[i] = ZOOM_resultset_record_immediate(r, i+start);
935     }
936 }
937
938 ZOOM_API(size_t)
939     ZOOM_resultset_facets_size(ZOOM_resultset r) {
940     return r->num_facets;
941 }
942
943 ZOOM_API(ZOOM_facet_field)
944     ZOOM_resultset_get_facet_field(ZOOM_resultset r, const char *name) {
945     int num = r->num_facets;
946     ZOOM_facet_field *facets = r->facets;
947     int index;
948     for (index = 0; index < num; index++) {
949         if (!strcmp(facets[index]->facet_name, name)) {
950             return facets[index];
951         }
952     }
953     return 0;
954 }
955
956
957 ZOOM_API(ZOOM_facet_field *)
958     ZOOM_resultset_facets(ZOOM_resultset r)
959 {
960     return r->facets;
961 }
962
963 ZOOM_API(const char**)
964     ZOOM_resultset_facet_names(ZOOM_resultset r)
965 {
966     return (const char **) r->facets_names;
967 }
968
969 ZOOM_API(const char*)
970     ZOOM_facet_field_name(ZOOM_facet_field field)
971 {
972     return field->facet_name;
973 }
974
975 ZOOM_API(size_t)
976     ZOOM_facet_field_term_count(ZOOM_facet_field field)
977 {
978     return field->num_terms;
979 }
980
981 ZOOM_API(const char*)
982     ZOOM_facet_field_get_term(ZOOM_facet_field field, size_t idx, int *freq) {
983     *freq = field->facet_terms[idx].frequency;
984     return field->facet_terms[idx].term;
985 }
986
987
988 static void get_cert(ZOOM_connection c)
989 {
990     char *cert_buf;
991     int cert_len;
992     
993     if (cs_get_peer_certificate_x509(c->cs, &cert_buf, &cert_len))
994     {
995         ZOOM_connection_option_setl(c, "sslPeerCert",
996                                     cert_buf, cert_len);
997         xfree(cert_buf);
998     }
999 }
1000
1001 static zoom_ret do_connect_host(ZOOM_connection c,
1002                                 const char *effective_host,
1003                                 const char *logical_url);
1004
1005 static zoom_ret do_connect(ZOOM_connection c)
1006 {
1007     const char *effective_host;
1008
1009     if (c->proxy)
1010         effective_host = c->proxy;
1011     else
1012         effective_host = c->host_port;
1013     return do_connect_host(c, effective_host, c->host_port);
1014 }
1015
1016 static zoom_ret do_connect_host(ZOOM_connection c, const char *effective_host,
1017     const char *logical_url)
1018 {
1019     void *add;
1020
1021     yaz_log(c->log_details, "%p do_connect effective_host=%s", c, effective_host);
1022
1023     if (c->cs)
1024         cs_close(c->cs);
1025     c->cs = cs_create_host(effective_host, 0, &add);
1026
1027     if (c->cs && c->cs->protocol == PROTO_HTTP)
1028     {
1029 #if YAZ_HAVE_XML2
1030         if (logical_url)
1031         {
1032             const char *db = 0;
1033             
1034             c->proto = PROTO_HTTP;
1035             cs_get_host_args(logical_url, &db);
1036             xfree(c->path);
1037             
1038             c->path = xmalloc(strlen(db) * 3 + 2);
1039             yaz_encode_sru_dbpath_buf(c->path, db);
1040         }
1041 #else
1042         ZOOM_set_error(c, ZOOM_ERROR_UNSUPPORTED_PROTOCOL, "SRW");
1043         ZOOM_connection_close(c);
1044         return zoom_complete;
1045 #endif
1046     }
1047     if (c->cs)
1048     {
1049         int ret = cs_connect(c->cs, add);
1050         if (ret == 0)
1051         {
1052             ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
1053             ZOOM_connection_put_event(c, event);
1054             get_cert(c);
1055             if (c->proto == PROTO_Z3950)
1056                 ZOOM_connection_Z3950_send_init(c);
1057             else
1058             {
1059                 /* no init request for SRW .. */
1060                 assert(c->tasks->which == ZOOM_TASK_CONNECT);
1061                 ZOOM_connection_remove_task(c);
1062                 ZOOM_connection_set_mask(c, 0);
1063                 ZOOM_connection_exec_task(c);
1064             }
1065             c->state = STATE_ESTABLISHED;
1066             return zoom_pending;
1067         }
1068         else if (ret > 0)
1069         {
1070             int mask = ZOOM_SELECT_EXCEPT;
1071             if (c->cs->io_pending & CS_WANT_WRITE)
1072                 mask += ZOOM_SELECT_WRITE;
1073             if (c->cs->io_pending & CS_WANT_READ)
1074                 mask += ZOOM_SELECT_READ;
1075             ZOOM_connection_set_mask(c, mask);
1076             c->state = STATE_CONNECTING; 
1077             return zoom_pending;
1078         }
1079     }
1080     c->state = STATE_IDLE;
1081     ZOOM_set_error(c, ZOOM_ERROR_CONNECT, logical_url);
1082     return zoom_complete;
1083 }
1084
1085 /* returns 1 if PDU was sent OK (still pending )
1086    0 if PDU was not sent OK (nothing to wait for) 
1087 */
1088
1089 ZOOM_API(ZOOM_record)
1090     ZOOM_resultset_record_immediate(ZOOM_resultset s,size_t pos)
1091 {
1092     const char *syntax =
1093         ZOOM_options_get(s->options, "preferredRecordSyntax"); 
1094     const char *elementSetName =
1095         ZOOM_options_get(s->options, "elementSetName");
1096
1097     return ZOOM_record_cache_lookup(s, pos, syntax, elementSetName);
1098 }
1099
1100 ZOOM_API(ZOOM_record)
1101     ZOOM_resultset_record(ZOOM_resultset r, size_t pos)
1102 {
1103     ZOOM_record rec = ZOOM_resultset_record_immediate(r, pos);
1104
1105     if (!rec)
1106     {
1107         /*
1108          * MIKE: I think force_sync should always be zero, but I don't
1109          * want to make this change until I get the go-ahead from
1110          * Adam, in case something depends on the old synchronous
1111          * behaviour.
1112          */
1113         int force_sync = 1;
1114         if (getenv("ZOOM_RECORD_NO_FORCE_SYNC")) force_sync = 0;
1115         ZOOM_resultset_retrieve(r, force_sync, pos, 1);
1116         rec = ZOOM_resultset_record_immediate(r, pos);
1117     }
1118     return rec;
1119 }
1120
1121 ZOOM_API(ZOOM_scanset)
1122     ZOOM_connection_scan(ZOOM_connection c, const char *start)
1123 {
1124     ZOOM_scanset s;
1125     ZOOM_query q = ZOOM_query_create();
1126
1127     ZOOM_query_prefix(q, start);
1128
1129     s = ZOOM_connection_scan1(c, q);
1130     ZOOM_query_destroy(q);
1131     return s;
1132
1133 }
1134
1135 ZOOM_API(ZOOM_scanset)
1136     ZOOM_connection_scan1(ZOOM_connection c, ZOOM_query q)
1137 {
1138     ZOOM_scanset scan = 0;
1139     Z_Query *z_query = ZOOM_query_get_Z_Query(q);
1140
1141     if (!z_query)
1142         return 0;
1143     scan = (ZOOM_scanset) xmalloc(sizeof(*scan));
1144     scan->connection = c;
1145     scan->odr = odr_createmem(ODR_DECODE);
1146     scan->options = ZOOM_options_create_with_parent(c->options);
1147     scan->refcount = 1;
1148     scan->scan_response = 0;
1149     scan->srw_scan_response = 0;
1150
1151     scan->query = q;
1152     ZOOM_query_addref(q);
1153     scan->databaseNames = ZOOM_connection_get_databases(c, c->options,
1154                                             &scan->num_databaseNames,
1155                                             scan->odr);
1156
1157     if (1)
1158     {
1159         ZOOM_task task = ZOOM_connection_add_task(c, ZOOM_TASK_SCAN);
1160         task->u.scan.scan = scan;
1161         
1162         (scan->refcount)++;
1163         if (!c->async)
1164         {
1165             while (ZOOM_event(1, &c))
1166                 ;
1167         }
1168     }
1169     return scan;
1170 }
1171
1172 ZOOM_API(void)
1173     ZOOM_scanset_destroy(ZOOM_scanset scan)
1174 {
1175     if (!scan)
1176         return;
1177     (scan->refcount)--;
1178     if (scan->refcount == 0)
1179     {
1180         ZOOM_query_destroy(scan->query);
1181
1182         odr_destroy(scan->odr);
1183         
1184         ZOOM_options_destroy(scan->options);
1185         xfree(scan);
1186     }
1187 }
1188
1189 static zoom_ret send_package(ZOOM_connection c)
1190 {
1191     ZOOM_Event event;
1192
1193     yaz_log(c->log_details, "%p send_package", c);
1194     if (!c->tasks)
1195         return zoom_complete;
1196     assert (c->tasks->which == ZOOM_TASK_PACKAGE);
1197     
1198     event = ZOOM_Event_create(ZOOM_EVENT_SEND_APDU);
1199     ZOOM_connection_put_event(c, event);
1200     
1201     c->buf_out = c->tasks->u.package->buf_out;
1202     c->len_out = c->tasks->u.package->len_out;
1203
1204     return ZOOM_send_buf(c);
1205 }
1206
1207 ZOOM_API(size_t)
1208     ZOOM_scanset_size(ZOOM_scanset scan)
1209 {
1210     if (!scan)
1211         return 0;
1212
1213     if (scan->scan_response && scan->scan_response->entries)
1214         return scan->scan_response->entries->num_entries;
1215     else if (scan->srw_scan_response)
1216         return scan->srw_scan_response->num_terms;
1217     return 0;
1218 }
1219
1220 static void ZOOM_scanset_term_x(ZOOM_scanset scan, size_t pos,
1221                                 size_t *occ,
1222                                 const char **value_term, size_t *value_len,
1223                                 const char **disp_term, size_t *disp_len)
1224 {
1225     size_t noent = ZOOM_scanset_size(scan);
1226     
1227     *value_term = 0;
1228     *value_len = 0;
1229
1230     *disp_term = 0;
1231     *disp_len = 0;
1232
1233     *occ = 0;
1234     if (pos >= noent)
1235         return;
1236     if (scan->scan_response)
1237     {
1238         Z_ScanResponse *res = scan->scan_response;
1239         if (res->entries->entries[pos]->which == Z_Entry_termInfo)
1240         {
1241             Z_TermInfo *t = res->entries->entries[pos]->u.termInfo;
1242             
1243             *value_term = (const char *) t->term->u.general->buf;
1244             *value_len = t->term->u.general->len;
1245             if (t->displayTerm)
1246             {
1247                 *disp_term = t->displayTerm;
1248                 *disp_len = strlen(*disp_term);
1249             }
1250             else if (t->term->which == Z_Term_general)
1251             {
1252                 *disp_term = (const char *) t->term->u.general->buf;
1253                 *disp_len = t->term->u.general->len;
1254             }
1255             *occ = t->globalOccurrences ? *t->globalOccurrences : 0;
1256         }
1257     }
1258     if (scan->srw_scan_response)
1259     {
1260         Z_SRW_scanResponse *res = scan->srw_scan_response;
1261         Z_SRW_scanTerm *t = res->terms + pos;
1262         if (t)
1263         {
1264             *value_term = t->value;
1265             *value_len = strlen(*value_term);
1266
1267             if (t->displayTerm)
1268                 *disp_term = t->displayTerm;
1269             else
1270                 *disp_term = t->value;
1271             *disp_len = strlen(*disp_term);
1272             *occ = t->numberOfRecords ? *t->numberOfRecords : 0;
1273         }
1274     }
1275 }
1276
1277 ZOOM_API(const char *)
1278     ZOOM_scanset_term(ZOOM_scanset scan, size_t pos,
1279                       size_t *occ, size_t *len)
1280 {
1281     const char *value_term = 0;
1282     size_t value_len = 0;
1283     const char *disp_term = 0;
1284     size_t disp_len = 0;
1285
1286     ZOOM_scanset_term_x(scan, pos, occ, &value_term, &value_len,
1287                         &disp_term, &disp_len);
1288     
1289     *len = value_len;
1290     return value_term;
1291 }
1292
1293 ZOOM_API(const char *)
1294     ZOOM_scanset_display_term(ZOOM_scanset scan, size_t pos,
1295                               size_t *occ, size_t *len)
1296 {
1297     const char *value_term = 0;
1298     size_t value_len = 0;
1299     const char *disp_term = 0;
1300     size_t disp_len = 0;
1301
1302     ZOOM_scanset_term_x(scan, pos, occ, &value_term, &value_len,
1303                         &disp_term, &disp_len);
1304     
1305     *len = disp_len;
1306     return disp_term;
1307 }
1308
1309 ZOOM_API(const char *)
1310     ZOOM_scanset_option_get(ZOOM_scanset scan, const char *key)
1311 {
1312     return ZOOM_options_get(scan->options, key);
1313 }
1314
1315 ZOOM_API(void)
1316     ZOOM_scanset_option_set(ZOOM_scanset scan, const char *key,
1317                             const char *val)
1318 {
1319     ZOOM_options_set(scan->options, key, val);
1320 }
1321
1322
1323 ZOOM_API(ZOOM_package)
1324     ZOOM_connection_package(ZOOM_connection c, ZOOM_options options)
1325 {
1326     ZOOM_package p = (ZOOM_package) xmalloc(sizeof(*p));
1327
1328     p->connection = c;
1329     p->odr_out = odr_createmem(ODR_ENCODE);
1330     p->options = ZOOM_options_create_with_parent2(options, c->options);
1331     p->refcount = 1;
1332     p->buf_out = 0;
1333     p->len_out = 0;
1334     return p;
1335 }
1336
1337 ZOOM_API(void)
1338     ZOOM_package_destroy(ZOOM_package p)
1339 {
1340     if (!p)
1341         return;
1342     (p->refcount)--;
1343     if (p->refcount == 0)
1344     {
1345         odr_destroy(p->odr_out);
1346         xfree(p->buf_out);
1347         
1348         ZOOM_options_destroy(p->options);
1349         xfree(p);
1350     }
1351 }
1352
1353 ZOOM_API(const char *)
1354     ZOOM_package_option_get(ZOOM_package p, const char *key)
1355 {
1356     return ZOOM_options_get(p->options, key);
1357 }
1358
1359 ZOOM_API(const char *)
1360     ZOOM_package_option_getl(ZOOM_package p, const char *key, int *lenp)
1361 {
1362     return ZOOM_options_getl(p->options, key, lenp);
1363 }
1364
1365 ZOOM_API(void)
1366     ZOOM_package_option_set(ZOOM_package p, const char *key,
1367                             const char *val)
1368 {
1369     ZOOM_options_set(p->options, key, val);
1370 }
1371
1372 ZOOM_API(void)
1373     ZOOM_package_option_setl(ZOOM_package p, const char *key,
1374                              const char *val, int len)
1375 {
1376     ZOOM_options_setl(p->options, key, val, len);
1377 }
1378
1379 ZOOM_API(int)
1380     ZOOM_connection_exec_task(ZOOM_connection c)
1381 {
1382     ZOOM_task task = c->tasks;
1383     zoom_ret ret = zoom_complete;
1384
1385     if (!task)
1386         return 0;
1387     yaz_log(c->log_details, "%p ZOOM_connection_exec_task type=%d run=%d",
1388             c, task->which, task->running);
1389     if (c->error != ZOOM_ERROR_NONE)
1390     {
1391         yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
1392                 "removing tasks because of error = %d", c, c->error);
1393         ZOOM_connection_remove_tasks(c);
1394         return 0;
1395     }
1396     if (task->running)
1397     {
1398         yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
1399                 "task already running", c);
1400         return 0;
1401     }
1402     task->running = 1;
1403     ret = zoom_complete;
1404     if (c->cs || task->which == ZOOM_TASK_CONNECT)
1405     {
1406         switch (task->which)
1407         {
1408         case ZOOM_TASK_SEARCH:
1409             if (c->proto == PROTO_HTTP)
1410                 ret = ZOOM_connection_srw_send_search(c);
1411             else
1412                 ret = ZOOM_connection_Z3950_send_search(c);
1413             break;
1414         case ZOOM_TASK_RETRIEVE:
1415             if (c->proto == PROTO_HTTP)
1416                 ret = ZOOM_connection_srw_send_search(c);
1417             else
1418                 ret = send_Z3950_present(c);
1419             break;
1420         case ZOOM_TASK_CONNECT:
1421             ret = do_connect(c);
1422             break;
1423         case ZOOM_TASK_SCAN:
1424             if (c->proto == PROTO_HTTP)
1425                 ret = ZOOM_connection_srw_send_scan(c);
1426             else
1427                 ret = ZOOM_connection_Z3950_send_scan(c);
1428             break;
1429         case ZOOM_TASK_PACKAGE:
1430             ret = send_package(c);
1431             break;
1432         case ZOOM_TASK_SORT:
1433             c->tasks->u.sort.resultset->r_sort_spec = 
1434                 ZOOM_query_get_sortspec(c->tasks->u.sort.q);
1435             ret = send_Z3950_sort(c, c->tasks->u.sort.resultset);
1436             break;
1437         }
1438     }
1439     else
1440     {
1441         yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
1442                 "remove tasks because no connection exist", c);
1443         ZOOM_connection_remove_tasks(c);
1444     }
1445     if (ret == zoom_complete)
1446     {
1447         yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
1448                 "task removed (complete)", c);
1449         ZOOM_connection_remove_task(c);
1450         return 0;
1451     }
1452     yaz_log(c->log_details, "%p ZOOM_connection_exec_task "
1453             "task pending", c);
1454     return 1;
1455 }
1456
1457 #if YAZ_HAVE_XML2
1458 static Z_GDU *get_HTTP_Request_url(ODR odr, const char *url)
1459 {
1460     Z_GDU *p = z_get_HTTP_Request(odr);
1461     const char *host = url;
1462     const char *cp0 = strstr(host, "://");
1463     const char *cp1 = 0;
1464     if (cp0)
1465         cp0 = cp0+3;
1466     else
1467         cp0 = host;
1468     
1469     cp1 = strchr(cp0, '/');
1470     if (!cp1)
1471         cp1 = cp0 + strlen(cp0);
1472     
1473     if (cp0 && cp1)
1474     {
1475         char *h = (char*) odr_malloc(odr, cp1 - cp0 + 1);
1476         memcpy (h, cp0, cp1 - cp0);
1477         h[cp1-cp0] = '\0';
1478         z_HTTP_header_add(odr, &p->u.HTTP_Request->headers, "Host", h);
1479     }
1480     p->u.HTTP_Request->path = odr_strdup(odr, *cp1 ? cp1 : "/");
1481     return p;
1482 }
1483
1484 static zoom_ret send_HTTP_redirect(ZOOM_connection c, const char *uri,
1485                                   Z_HTTP_Response *cookie_hres)
1486 {
1487     struct Z_HTTP_Header *h;
1488     Z_GDU *gdu = get_HTTP_Request_url(c->odr_out, uri);
1489     char *combined_cookies;
1490     int combined_cookies_len = 0;
1491
1492     gdu->u.HTTP_Request->method = odr_strdup(c->odr_out, "GET");
1493     z_HTTP_header_add(c->odr_out, &gdu->u.HTTP_Request->headers, "Accept",
1494                       "text/xml");
1495
1496     for (h = cookie_hres->headers; h; h = h->next)
1497     {
1498         if (!strcmp(h->name, "Set-Cookie"))
1499         {
1500             char *cp;
1501
1502             if (!(cp = strchr(h->value, ';')))
1503                 cp = h->value + strlen(h->value);
1504             if (cp - h->value >= 1) {
1505                 combined_cookies = xrealloc(combined_cookies, combined_cookies_len + cp - h->value + 3);
1506                 memcpy(combined_cookies+combined_cookies_len, h->value, cp - h->value);
1507                 combined_cookies[combined_cookies_len + cp - h->value] = '\0';
1508                 strcat(combined_cookies,"; ");
1509                 combined_cookies_len = strlen(combined_cookies);
1510             }
1511         }
1512     }
1513
1514     if (combined_cookies_len)
1515     {
1516         z_HTTP_header_add(c->odr_out, &gdu->u.HTTP_Request->headers,
1517                           "Cookie", combined_cookies);
1518         xfree(combined_cookies);
1519     }
1520
1521     if (c->user && c->password)
1522     {
1523         z_HTTP_header_add_basic_auth(c->odr_out, &gdu->u.HTTP_Request->headers,
1524                                      c->user, c->password);
1525     }
1526     if (!z_GDU(c->odr_out, &gdu, 0, 0))
1527         return zoom_complete;
1528     if (c->odr_print)
1529         z_GDU(c->odr_print, &gdu, 0, 0);
1530     c->buf_out = odr_getbuf(c->odr_out, &c->len_out, 0);
1531
1532     odr_reset(c->odr_out);
1533     return ZOOM_send_buf(c);
1534 }
1535
1536 #if YAZ_HAVE_XML2
1537 void ZOOM_set_HTTP_error(ZOOM_connection c, int error,
1538                          const char *addinfo, const char *addinfo2)
1539 {
1540     ZOOM_set_dset_error(c, error, "HTTP", addinfo, addinfo2);
1541 }
1542 #endif
1543
1544
1545 static void handle_http(ZOOM_connection c, Z_HTTP_Response *hres)
1546 {
1547     zoom_ret cret = zoom_complete;
1548     int ret = -1;
1549     const char *addinfo = 0;
1550     const char *connection_head = z_HTTP_header_lookup(hres->headers,
1551                                                        "Connection");
1552     const char *location;
1553
1554     ZOOM_connection_set_mask(c, 0);
1555     yaz_log(c->log_details, "%p handle_http", c);
1556     
1557     if ((hres->code == 301 || hres->code == 302) && c->sru_mode == zoom_sru_get
1558         && (location = z_HTTP_header_lookup(hres->headers, "Location")))
1559     {
1560         c->no_redirects++;
1561         if (c->no_redirects > 10)
1562         {
1563             ZOOM_set_HTTP_error(c, hres->code, 0, 0);
1564             c->no_redirects = 0;
1565             ZOOM_connection_close(c);
1566         }
1567         else
1568         {
1569             /* since redirect may change host we just reconnect. A smarter
1570                implementation might check whether it's the same server */
1571             do_connect_host(c, location, 0);
1572             send_HTTP_redirect(c, location, hres);
1573             /* we're OK for now. Operation is not really complete */
1574             ret = 0;
1575             cret = zoom_pending;
1576         }
1577     }
1578     else 
1579     {  
1580         ret = ZOOM_handle_sru(c, hres, &cret);
1581         if (ret == 0)
1582         {
1583             if (c->no_redirects) /* end of redirect. change hosts again */
1584                 ZOOM_connection_close(c);
1585         }
1586         c->no_redirects = 0;
1587     }
1588     if (ret)
1589     {
1590         if (hres->code != 200)
1591             ZOOM_set_HTTP_error(c, hres->code, 0, 0);
1592         else
1593             ZOOM_set_error(c, ZOOM_ERROR_DECODE, addinfo);
1594         ZOOM_connection_close(c);
1595     }
1596     if (cret == zoom_complete)
1597     {
1598         yaz_log(YLOG_LOG, "removing tasks in handle_http");
1599         ZOOM_connection_remove_task(c);
1600     }
1601     {
1602         int must_close = 0;
1603         if (!strcmp(hres->version, "1.0"))
1604         {
1605             /* HTTP 1.0: only if Keep-Alive we stay alive.. */
1606             if (!connection_head || strcmp(connection_head, "Keep-Alive"))
1607                 must_close = 1;
1608         }
1609         else
1610         {
1611             /* HTTP 1.1: only if no close we stay alive.. */
1612             if (connection_head && !strcmp(connection_head, "close"))
1613                 must_close = 1;
1614         }
1615         if (must_close)
1616         {
1617             ZOOM_connection_close(c);
1618             if (c->tasks)
1619             {
1620                 c->tasks->running = 0;
1621                 ZOOM_connection_insert_task(c, ZOOM_TASK_CONNECT);
1622                 c->reconnect_ok = 0;
1623             }
1624         }
1625     }
1626 }
1627 #endif
1628
1629 static int do_read(ZOOM_connection c)
1630 {
1631     int r, more;
1632     ZOOM_Event event;
1633     
1634     event = ZOOM_Event_create(ZOOM_EVENT_RECV_DATA);
1635     ZOOM_connection_put_event(c, event);
1636     
1637     r = cs_get(c->cs, &c->buf_in, &c->len_in);
1638     more = cs_more(c->cs);
1639     yaz_log(c->log_details, "%p do_read len=%d more=%d", c, r, more);
1640     if (r == 1)
1641         return 0;
1642     if (r <= 0)
1643     {
1644         if (!ZOOM_test_reconnect(c))
1645         {
1646             ZOOM_set_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
1647             ZOOM_connection_close(c);
1648         }
1649     }
1650     else
1651     {
1652         Z_GDU *gdu;
1653         ZOOM_Event event;
1654
1655         odr_reset(c->odr_in);
1656         odr_setbuf(c->odr_in, c->buf_in, r, 0);
1657         event = ZOOM_Event_create(ZOOM_EVENT_RECV_APDU);
1658         ZOOM_connection_put_event(c, event);
1659
1660         if (!z_GDU(c->odr_in, &gdu, 0, 0))
1661         {
1662             int x;
1663             int err = odr_geterrorx(c->odr_in, &x);
1664             char msg[100];
1665             const char *element = odr_getelement(c->odr_in);
1666             yaz_snprintf(msg, sizeof(msg),
1667                     "ODR code %d:%d element=%s offset=%d",
1668                     err, x, element ? element : "<unknown>",
1669                     odr_offset(c->odr_in));
1670             ZOOM_set_error(c, ZOOM_ERROR_DECODE, msg);
1671             if (c->log_api)
1672             {
1673                 FILE *ber_file = yaz_log_file();
1674                 if (ber_file)
1675                     odr_dumpBER(ber_file, c->buf_in, r);
1676             }
1677             ZOOM_connection_close(c);
1678         }
1679         else
1680         {
1681             if (c->odr_print)
1682                 z_GDU(c->odr_print, &gdu, 0, 0);
1683             if (gdu->which == Z_GDU_Z3950)
1684                 ZOOM_handle_Z3950_apdu(c, gdu->u.z3950);
1685             else if (gdu->which == Z_GDU_HTTP_Response)
1686             {
1687 #if YAZ_HAVE_XML2
1688                 handle_http(c, gdu->u.HTTP_Response);
1689 #else
1690                 ZOOM_set_error(c, ZOOM_ERROR_DECODE, 0);
1691                 ZOOM_connection_close(c);
1692 #endif
1693             }
1694         }
1695         c->reconnect_ok = 0;
1696     }
1697     return 1;
1698 }
1699
1700 static zoom_ret do_write_ex(ZOOM_connection c, char *buf_out, int len_out)
1701 {
1702     int r;
1703     ZOOM_Event event;
1704     
1705     event = ZOOM_Event_create(ZOOM_EVENT_SEND_DATA);
1706     ZOOM_connection_put_event(c, event);
1707
1708     yaz_log(c->log_details, "%p do_write_ex len=%d", c, len_out);
1709     if ((r = cs_put(c->cs, buf_out, len_out)) < 0)
1710     {
1711         yaz_log(c->log_details, "%p do_write_ex write failed", c);
1712         if (ZOOM_test_reconnect(c))
1713         {
1714             return zoom_pending;
1715         }
1716         if (c->state == STATE_CONNECTING)
1717             ZOOM_set_error(c, ZOOM_ERROR_CONNECT, c->host_port);
1718         else
1719             ZOOM_set_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
1720         ZOOM_connection_close(c);
1721         return zoom_complete;
1722     }
1723     else if (r == 1)
1724     {    
1725         int mask = ZOOM_SELECT_EXCEPT;
1726         if (c->cs->io_pending & CS_WANT_WRITE)
1727             mask += ZOOM_SELECT_WRITE;
1728         if (c->cs->io_pending & CS_WANT_READ)
1729             mask += ZOOM_SELECT_READ;
1730         ZOOM_connection_set_mask(c, mask);
1731         yaz_log(c->log_details, "%p do_write_ex write incomplete mask=%d",
1732                 c, c->mask);
1733     }
1734     else
1735     {
1736         ZOOM_connection_set_mask(c, ZOOM_SELECT_READ|ZOOM_SELECT_EXCEPT);
1737         yaz_log(c->log_details, "%p do_write_ex write complete mask=%d",
1738                 c, c->mask);
1739     }
1740     return zoom_pending;
1741 }
1742
1743 zoom_ret ZOOM_send_buf(ZOOM_connection c)
1744 {
1745     return do_write_ex(c, c->buf_out, c->len_out);
1746 }
1747
1748
1749 ZOOM_API(const char *)
1750     ZOOM_connection_option_get(ZOOM_connection c, const char *key)
1751 {
1752     return ZOOM_options_get(c->options, key);
1753 }
1754
1755 ZOOM_API(const char *)
1756     ZOOM_connection_option_getl(ZOOM_connection c, const char *key, int *lenp)
1757 {
1758     return ZOOM_options_getl(c->options, key, lenp);
1759 }
1760
1761 ZOOM_API(void)
1762     ZOOM_connection_option_set(ZOOM_connection c, const char *key,
1763                                const char *val)
1764 {
1765     ZOOM_options_set(c->options, key, val);
1766 }
1767
1768 ZOOM_API(void)
1769     ZOOM_connection_option_setl(ZOOM_connection c, const char *key,
1770                                 const char *val, int len)
1771 {
1772     ZOOM_options_setl(c->options, key, val, len);
1773 }
1774
1775 ZOOM_API(const char *)
1776     ZOOM_resultset_option_get(ZOOM_resultset r, const char *key)
1777 {
1778     return ZOOM_options_get(r->options, key);
1779 }
1780
1781 ZOOM_API(void)
1782     ZOOM_resultset_option_set(ZOOM_resultset r, const char *key,
1783                               const char *val)
1784 {
1785     ZOOM_options_set(r->options, key, val);
1786 }
1787
1788
1789 ZOOM_API(int)
1790     ZOOM_connection_errcode(ZOOM_connection c)
1791 {
1792     return ZOOM_connection_error(c, 0, 0);
1793 }
1794
1795 ZOOM_API(const char *)
1796     ZOOM_connection_errmsg(ZOOM_connection c)
1797 {
1798     const char *msg;
1799     ZOOM_connection_error(c, &msg, 0);
1800     return msg;
1801 }
1802
1803 ZOOM_API(const char *)
1804     ZOOM_connection_addinfo(ZOOM_connection c)
1805 {
1806     const char *addinfo;
1807     ZOOM_connection_error(c, 0, &addinfo);
1808     return addinfo;
1809 }
1810
1811 ZOOM_API(const char *)
1812     ZOOM_connection_diagset(ZOOM_connection c)
1813 {
1814     const char *diagset;
1815     ZOOM_connection_error_x(c, 0, 0, &diagset);
1816     return diagset;
1817 }
1818
1819 ZOOM_API(const char *)
1820     ZOOM_diag_str(int error)
1821 {
1822     switch (error)
1823     {
1824     case ZOOM_ERROR_NONE:
1825         return "No error";
1826     case ZOOM_ERROR_CONNECT:
1827         return "Connect failed";
1828     case ZOOM_ERROR_MEMORY:
1829         return "Out of memory";
1830     case ZOOM_ERROR_ENCODE:
1831         return "Encoding failed";
1832     case ZOOM_ERROR_DECODE:
1833         return "Decoding failed";
1834     case ZOOM_ERROR_CONNECTION_LOST:
1835         return "Connection lost";
1836     case ZOOM_ERROR_INIT:
1837         return "Init rejected";
1838     case ZOOM_ERROR_INTERNAL:
1839         return "Internal failure";
1840     case ZOOM_ERROR_TIMEOUT:
1841         return "Timeout";
1842     case ZOOM_ERROR_UNSUPPORTED_PROTOCOL:
1843         return "Unsupported protocol";
1844     case ZOOM_ERROR_UNSUPPORTED_QUERY:
1845         return "Unsupported query type";
1846     case ZOOM_ERROR_INVALID_QUERY:
1847         return "Invalid query";
1848     case ZOOM_ERROR_CQL_PARSE:
1849         return "CQL parsing error";
1850     case ZOOM_ERROR_CQL_TRANSFORM:
1851         return "CQL transformation error";
1852     case ZOOM_ERROR_CCL_CONFIG:
1853         return "CCL configuration error";
1854     case ZOOM_ERROR_CCL_PARSE:
1855         return "CCL parsing error";
1856     default:
1857         return diagbib1_str(error);
1858     }
1859 }
1860
1861 ZOOM_API(int)
1862     ZOOM_connection_error_x(ZOOM_connection c, const char **cp,
1863                             const char **addinfo, const char **diagset)
1864 {
1865     int error = c->error;
1866     if (cp)
1867     {
1868         if (!c->diagset || !strcmp(c->diagset, "ZOOM"))
1869             *cp = ZOOM_diag_str(error);
1870         else if (!strcmp(c->diagset, "HTTP"))
1871             *cp = z_HTTP_errmsg(c->error);
1872         else if (!strcmp(c->diagset, "Bib-1"))
1873             *cp = ZOOM_diag_str(error);
1874         else if (!strcmp(c->diagset, "info:srw/diagnostic/1"))
1875             *cp = yaz_diag_srw_str(c->error);
1876         else
1877             *cp = "Unknown error and diagnostic set";
1878     }
1879     if (addinfo)
1880         *addinfo = c->addinfo ? c->addinfo : "";
1881     if (diagset)
1882         *diagset = c->diagset ? c->diagset : "";
1883     return c->error;
1884 }
1885
1886 ZOOM_API(int)
1887     ZOOM_connection_error(ZOOM_connection c, const char **cp,
1888                           const char **addinfo)
1889 {
1890     return ZOOM_connection_error_x(c, cp, addinfo, 0);
1891 }
1892
1893 static void ZOOM_connection_do_io(ZOOM_connection c, int mask)
1894 {
1895     ZOOM_Event event = 0;
1896     int r = cs_look(c->cs);
1897     yaz_log(c->log_details, "%p ZOOM_connection_do_io mask=%d cs_look=%d",
1898             c, mask, r);
1899     
1900     if (r == CS_NONE)
1901     {
1902         event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
1903         ZOOM_set_error(c, ZOOM_ERROR_CONNECT, c->host_port);
1904         ZOOM_connection_close(c);
1905         ZOOM_connection_put_event(c, event);
1906     }
1907     else if (r == CS_CONNECT)
1908     {
1909         int ret = ret = cs_rcvconnect(c->cs);
1910         yaz_log(c->log_details, "%p ZOOM_connection_do_io "
1911                 "cs_rcvconnect returned %d", c, ret);
1912         if (ret == 1)
1913         {
1914             int mask = ZOOM_SELECT_EXCEPT;
1915             if (c->cs->io_pending & CS_WANT_WRITE)
1916                 mask += ZOOM_SELECT_WRITE;
1917             if (c->cs->io_pending & CS_WANT_READ)
1918                 mask += ZOOM_SELECT_READ;
1919             ZOOM_connection_set_mask(c, mask);
1920             event = ZOOM_Event_create(ZOOM_EVENT_NONE);
1921             ZOOM_connection_put_event(c, event);
1922         }
1923         else if (ret == 0)
1924         {
1925             event = ZOOM_Event_create(ZOOM_EVENT_CONNECT);
1926             ZOOM_connection_put_event(c, event);
1927             get_cert(c);
1928             if (c->proto == PROTO_Z3950)
1929                 ZOOM_connection_Z3950_send_init(c);
1930             else
1931             {
1932                 /* no init request for SRW .. */
1933                 assert(c->tasks->which == ZOOM_TASK_CONNECT);
1934                 ZOOM_connection_remove_task(c);
1935                 ZOOM_connection_set_mask(c, 0);
1936                 ZOOM_connection_exec_task(c);
1937             }
1938             c->state = STATE_ESTABLISHED;
1939         }
1940         else
1941         {
1942             ZOOM_set_error(c, ZOOM_ERROR_CONNECT, c->host_port);
1943             ZOOM_connection_close(c);
1944         }
1945     }
1946     else
1947     {
1948         if (mask & ZOOM_SELECT_EXCEPT)
1949         {
1950             if (!ZOOM_test_reconnect(c))
1951             {
1952                 ZOOM_set_error(c, ZOOM_ERROR_CONNECTION_LOST, c->host_port);
1953                 ZOOM_connection_close(c);
1954             }
1955             return;
1956         }
1957         if (mask & ZOOM_SELECT_READ)
1958             do_read(c);
1959         if (c->cs && (mask & ZOOM_SELECT_WRITE))
1960             ZOOM_send_buf(c);
1961     }
1962 }
1963
1964 ZOOM_API(int)
1965     ZOOM_connection_last_event(ZOOM_connection cs)
1966 {
1967     if (!cs)
1968         return ZOOM_EVENT_NONE;
1969     return cs->last_event;
1970 }
1971
1972
1973 ZOOM_API(int) ZOOM_connection_fire_event_timeout(ZOOM_connection c)
1974 {
1975     if (c->mask)
1976     {
1977         ZOOM_Event event = ZOOM_Event_create(ZOOM_EVENT_TIMEOUT);
1978         /* timeout and this connection was waiting */
1979         ZOOM_set_error(c, ZOOM_ERROR_TIMEOUT, 0);
1980         ZOOM_connection_close(c);
1981         ZOOM_connection_put_event(c, event);
1982     }
1983     return 0;
1984 }
1985
1986 ZOOM_API(int)
1987     ZOOM_connection_process(ZOOM_connection c)
1988 {
1989     ZOOM_Event event;
1990     if (!c)
1991         return 0;
1992
1993     event = ZOOM_connection_get_event(c);
1994     if (event)
1995     {
1996         ZOOM_Event_destroy(event);
1997         return 1;
1998     }
1999     ZOOM_connection_exec_task(c);
2000     event = ZOOM_connection_get_event(c);
2001     if (event)
2002     {
2003         ZOOM_Event_destroy(event);
2004         return 1;
2005     }
2006     return 0;
2007 }
2008
2009 ZOOM_API(int)
2010     ZOOM_event_nonblock(int no, ZOOM_connection *cs)
2011 {
2012     int i;
2013
2014     yaz_log(log_details0, "ZOOM_process_event(no=%d,cs=%p)", no, cs);
2015     
2016     for (i = 0; i<no; i++)
2017     {
2018         ZOOM_connection c = cs[i];
2019
2020         if (c && ZOOM_connection_process(c))
2021             return i+1;
2022     }
2023     return 0;
2024 }
2025
2026 ZOOM_API(int) ZOOM_connection_fire_event_socket(ZOOM_connection c, int mask)
2027 {
2028     if (c->mask && mask)
2029         ZOOM_connection_do_io(c, mask);
2030     return 0;
2031 }
2032
2033 ZOOM_API(int) ZOOM_connection_get_socket(ZOOM_connection c)
2034 {
2035     if (c->cs)
2036         return cs_fileno(c->cs);
2037     return -1;
2038 }
2039
2040 ZOOM_API(int) ZOOM_connection_set_mask(ZOOM_connection c, int mask)
2041 {
2042     c->mask = mask;
2043     if (!c->cs)
2044         return -1; 
2045     return 0;
2046 }
2047
2048 ZOOM_API(int) ZOOM_connection_get_mask(ZOOM_connection c)
2049 {
2050     if (c->cs)
2051         return c->mask;
2052     return 0;
2053 }
2054
2055 ZOOM_API(int) ZOOM_connection_get_timeout(ZOOM_connection c)
2056 {
2057     return ZOOM_options_get_int(c->options, "timeout", 30);
2058 }
2059
2060 ZOOM_API(void) ZOOM_connection_close(ZOOM_connection c)
2061 {
2062     if (c->cs)
2063         cs_close(c->cs);
2064     c->cs = 0;
2065     ZOOM_connection_set_mask(c, 0);
2066     c->state = STATE_IDLE;
2067 }
2068
2069 /*
2070  * Local variables:
2071  * c-basic-offset: 4
2072  * c-file-style: "Stroustrup"
2073  * indent-tabs-mode: nil
2074  * End:
2075  * vim: shiftwidth=4 tabstop=8 expandtab
2076  */
2077