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