d934e672cc5dc4640d0df24f0affd1fb08de1ecb
[yazpp-moved-to-github.git] / src / yaz-client.cpp
1 /*
2  * Copyright (c) 1998-2000, Index Data.
3  * See the file LICENSE for details.
4  * 
5  * $Log: yaz-client.cpp,v $
6  * Revision 1.14  2000-09-08 10:23:42  adam
7  * Added skeleton of yaz-z-server.
8  *
9  * Revision 1.13  2000/09/06 14:23:45  adam
10  * WIN32 updates.
11  *
12  * Revision 1.12  2000/09/04 08:59:16  adam
13  * Changed call to logging functions (yaz_ added).
14  *
15  * Revision 1.11  2000/07/04 13:48:49  adam
16  * Implemented upper-limit on proxy-to-target sessions.
17  *
18  * Revision 1.10  2000/05/30 03:12:27  ian
19  * minor change to stop g++ 2.95.2 complaining about taking the address
20  * of a member function.
21  *
22  * Revision 1.9  1999/12/06 13:52:45  adam
23  * Modified for new location of YAZ header files. Experimental threaded
24  * operation.
25  *
26  * Revision 1.8  1999/11/10 10:02:34  adam
27  * Work on proxy.
28  *
29  * Revision 1.7  1999/04/21 12:09:01  adam
30  * Many improvements. Modified to proxy server to work with "sessions"
31  * based on cookies.
32  *
33  * Revision 1.6  1999/04/20 10:30:05  adam
34  * Implemented various stuff for client and proxy. Updated calls
35  * to ODR to reflect new name parameter.
36  *
37  * Revision 1.5  1999/04/09 11:46:57  adam
38  * Added object Yaz_Z_Assoc. Much more functional client.
39  *
40  * Revision 1.4  1999/03/23 14:17:57  adam
41  * More work on timeout handling. Work on yaz-client.
42  *
43  * Revision 1.3  1999/02/02 14:01:18  adam
44  * First WIN32 port of YAZ++.
45  *
46  * Revision 1.2  1999/01/28 13:08:42  adam
47  * Yaz_PDU_Assoc better encapsulated. Memory leak fix in
48  * yaz-socket-manager.cc.
49  *
50  * Revision 1.1.1.1  1999/01/28 09:41:07  adam
51  * First implementation of YAZ++.
52  *
53  */
54
55 #include <yaz/log.h>
56 #include <yaz/options.h>
57 #include <yaz/diagbib1.h>
58 #include <yaz-ir-assoc.h>
59 #include <yaz-pdu-assoc.h>
60 #include <yaz-socket-manager.h>
61
62 extern "C" {
63 #if HAVE_READLINE_READLINE_H
64 #include <readline/readline.h>
65 #endif
66 #if HAVE_READLINE_HISTORY_H
67 #include <readline/history.h>
68 #endif
69 }
70
71 class YAZ_EXPORT MyClient : public Yaz_IR_Assoc {
72 private:
73     int m_interactive_flag;
74     char m_thisCommand[1024];
75     char m_lastCommand[1024];
76     int m_setOffset;
77     Yaz_SocketManager *m_socketManager;
78 public:
79     MyClient(IYaz_PDU_Observable *the_PDU_Observable,
80              Yaz_SocketManager *the_SocketManager);
81     IYaz_PDU_Observer *clone(IYaz_PDU_Observable *the_PDU_Observable);
82     int args(Yaz_SocketManager *socketManager, int argc, char **argv);
83     int interactive(Yaz_SocketManager *socketManager);
84     int wait();
85     void recv_initResponse(Z_InitResponse *initResponse);
86     void recv_searchResponse(Z_SearchResponse *searchResponse);
87     void recv_presentResponse(Z_PresentResponse *presentResponse);
88     void recv_records (Z_Records *records);
89     void recv_diagrecs(Z_DiagRec **pp, int num);
90     void recv_namePlusRecord (Z_NamePlusRecord *zpr, int offset);
91     void recv_record(Z_DatabaseRecord *record, int offset,
92                      const char *databaseName);
93     void recv_textRecord(int type, const char *buf, size_t len);
94     void recv_genericRecord(Z_GenericRecord *r);
95     void display_genericRecord(Z_GenericRecord *r, int level);
96     void display_variant(Z_Variant *v, int level);
97     void connectNotify();
98     void failNotify();
99     void timeoutNotify();
100     char *get_cookie (Z_OtherInformation **oi);
101     int processCommand(const char *cmd);
102     const char *MyClient::getCommand();
103     int cmd_open(char *host);
104     int cmd_connect(char *host);
105     int cmd_quit(char *args);
106     int cmd_close(char *args);
107     int cmd_find(char *args);
108     int cmd_show(char *args);
109     int cmd_cookie(char *args);
110     int cmd_init(char *args);
111     int cmd_format(char *args);
112     int cmd_proxy(char *args);
113 };
114
115
116 void MyClient::connectNotify()
117 {
118     printf ("Connection accepted by target\n");
119     set_lastReceived(-1);
120 }
121
122 void MyClient::timeoutNotify()
123 {
124     printf ("Connection timeout\n");
125     close();
126 }
127
128 void MyClient::failNotify()
129 {
130     printf ("Connection closed by target\n");
131     set_lastReceived(-1);
132 }
133
134 IYaz_PDU_Observer *MyClient::clone(IYaz_PDU_Observable *the_PDU_Observable)
135
136     return new MyClient(the_PDU_Observable, m_socketManager);
137 }
138
139 MyClient::MyClient(IYaz_PDU_Observable *the_PDU_Observable,
140                    Yaz_SocketManager *the_socketManager) :
141     Yaz_IR_Assoc (the_PDU_Observable)
142 {
143     m_setOffset = 1;
144     m_interactive_flag = 1;
145     m_thisCommand[0] = '\0';
146     m_lastCommand[0] = '\0';
147     m_socketManager = the_socketManager;
148 }
149
150 void usage(char *prog)
151 {
152     fprintf (stderr, "%s: [-v log] [-c cookie] [-p proxy] [zurl]\n", prog);
153     exit (1);
154 }
155
156 char *MyClient::get_cookie(Z_OtherInformation **otherInfo)
157 {
158     int oid[OID_SIZE];
159     Z_OtherInformationUnit *oi;
160     struct oident ent;
161     ent.proto = PROTO_Z3950;
162     ent.oclass = CLASS_USERINFO;
163     ent.value = (oid_value) VAL_COOKIE;
164
165     if (oid_ent_to_oid (&ent, oid) && 
166         (oi = update_otherInformation(otherInfo, 0, oid, 1, 1)) &&
167         oi->which == Z_OtherInfo_characterInfo)
168         return oi->information.characterInfo;
169     return 0;
170 }
171
172 void MyClient::recv_initResponse(Z_InitResponse *initResponse)
173 {
174     printf ("Got InitResponse. Status ");
175     if (*initResponse->result)
176     {
177         printf ("Ok\n");
178
179         const char *p = get_cookie (&initResponse->otherInfo);
180         if (p)
181         {
182             printf ("cookie = %s\n", p);
183             set_cookie(p);
184         }
185     }
186     else
187         printf ("Fail\n");
188 }
189
190 void MyClient::recv_diagrecs(Z_DiagRec **pp, int num)
191 {
192     int i;
193     oident *ent;
194     Z_DefaultDiagFormat *r;
195
196     printf("Diagnostic message(s) from database:\n");
197     for (i = 0; i<num; i++)
198     {
199         Z_DiagRec *p = pp[i];
200         if (p->which != Z_DiagRec_defaultFormat)
201         {
202             printf("Diagnostic record not in default format.\n");
203             return;
204         }
205         else
206             r = p->u.defaultFormat;
207         if (!(ent = oid_getentbyoid(r->diagnosticSetId)) ||
208             ent->oclass != CLASS_DIAGSET || ent->value != VAL_BIB1)
209             printf("Missing or unknown diagset\n");
210         printf("    [%d] %s", *r->condition, diagbib1_str(*r->condition));
211 #ifdef ASN_COMPILED
212         switch (r->which)
213         {
214         case Z_DefaultDiagFormat_v2Addinfo:
215             printf (" -- v2 addinfo '%s'\n", r->u.v2Addinfo);
216             break;
217         case Z_DefaultDiagFormat_v3Addinfo:
218             printf (" -- v3 addinfo '%s'\n", r->u.v3Addinfo);
219             break;
220         }
221 #else
222         if (r->addinfo && *r->addinfo)
223             printf(" -- '%s'\n", r->addinfo);
224         else
225             printf("\n");
226 #endif
227     }
228 }
229
230 void MyClient::recv_textRecord(int type, const char *buf, size_t len)
231 {
232     fwrite (buf, 1, len, stdout);
233     fputc ('\n', stdout);
234 }
235
236 void MyClient::display_variant(Z_Variant *v, int level)
237 {
238     int i;
239
240     for (i = 0; i < v->num_triples; i++)
241     {
242         printf("%*sclass=%d,type=%d", level * 4, "", *v->triples[i]->zclass,
243             *v->triples[i]->type);
244         if (v->triples[i]->which == Z_Triple_internationalString)
245             printf(",value=%s\n", v->triples[i]->value.internationalString);
246         else
247             printf("\n");
248     }
249 }
250
251 void MyClient::display_genericRecord(Z_GenericRecord *r, int level)
252 {
253     int i;
254
255     if (!r)
256         return;
257     for (i = 0; i < r->num_elements; i++)
258     {
259         Z_TaggedElement *t;
260
261         printf("%*s", level * 4, "");
262         t = r->elements[i];
263         printf("(");
264         if (t->tagType)
265             printf("%d,", *t->tagType);
266         else
267             printf("?,");
268         if (t->tagValue->which == Z_StringOrNumeric_numeric)
269             printf("%d) ", *t->tagValue->u.numeric);
270         else
271             printf("%s) ", t->tagValue->u.string);
272         if (t->content->which == Z_ElementData_subtree)
273         {
274             printf("\n");
275             display_genericRecord(t->content->u.subtree, level+1);
276         }
277         else if (t->content->which == Z_ElementData_string)
278             printf("%s\n", t->content->u.string);
279         else if (t->content->which == Z_ElementData_numeric)
280             printf("%d\n", *t->content->u.numeric);
281         else if (t->content->which == Z_ElementData_oid)
282         {
283             int *ip = t->content->u.oid;
284             oident *oent;
285
286             if ((oent = oid_getentbyoid(t->content->u.oid)))
287                 printf("OID: %s\n", oent->desc);
288             else
289             {
290                 printf("{");
291                 while (ip && *ip >= 0)
292                     printf(" %d", *(ip++));
293                 printf(" }\n");
294             }
295         }
296         else if (t->content->which == Z_ElementData_noDataRequested)
297             printf("[No data requested]\n");
298         else if (t->content->which == Z_ElementData_elementEmpty)
299             printf("[Element empty]\n");
300         else if (t->content->which == Z_ElementData_elementNotThere)
301             printf("[Element not there]\n");
302         else
303             printf("??????\n");
304         if (t->appliedVariant)
305             display_variant(t->appliedVariant, level+1);
306         if (t->metaData && t->metaData->supportedVariants)
307         {
308             int c;
309
310             printf("%*s---- variant list\n", (level+1)*4, "");
311             for (c = 0; c < t->metaData->num_supportedVariants; c++)
312             {
313                 printf("%*svariant #%d\n", (level+1)*4, "", c);
314                 display_variant(t->metaData->supportedVariants[c], level + 2);
315             }
316         }
317     }
318 }
319
320 void MyClient::recv_genericRecord(Z_GenericRecord *r)
321 {
322     display_genericRecord(r, 0);
323 }
324
325 void MyClient::recv_record(Z_DatabaseRecord *record, int offset,
326                            const char *databaseName)
327 {
328     Z_External *r = (Z_External*) record;
329     oident *ent = oid_getentbyoid(r->direct_reference);
330
331     /*
332      * Tell the user what we got.
333      */
334     if (r->direct_reference)
335     {
336         printf("Record type: ");
337         if (ent)
338             printf("%s\n", ent->desc);
339     }
340     /* Check if this is a known, ASN.1 type tucked away in an octet string */
341     Z_ext_typeent *etype = z_ext_getentbyref(ent->value);
342     if (ent && (r->which == Z_External_octet || r->which == Z_External_single)
343         && (etype = z_ext_getentbyref(ent->value)))
344
345     {
346         void *rr;
347         /*
348          * Call the given decoder to process the record.
349          */
350         odr_setbuf(odr_decode(), (char*)record->u.octet_aligned->buf,
351                    record->u.octet_aligned->len, 0);
352         if (!(*etype->fun)(odr_decode(), (char **)&rr, 0, 0))
353         {
354             odr_perror(odr_decode(), "Decoding constructed record.");
355             fprintf(stderr, "[Near %d]\n", odr_offset(odr_decode()));
356             fprintf(stderr, "Packet dump:\n---------\n");
357             odr_dumpBER(stderr, (char*)record->u.octet_aligned->buf,
358                         record->u.octet_aligned->len);
359             fprintf(stderr, "---------\n");
360         }
361         if (etype->what == Z_External_sutrs)
362         {
363             Z_SUTRS *sutrs = (Z_SUTRS *) rr;
364             recv_textRecord ((int) VAL_SUTRS, (const char *) sutrs->buf,
365                              (size_t) sutrs->len);
366         }
367         return;
368     }
369     if (r->which == Z_External_octet && record->u.octet_aligned->len)
370     {
371         recv_textRecord((int) ent->value,
372                         (const char *) record->u.octet_aligned->buf,
373                         (size_t) record->u.octet_aligned->len);
374     }
375     else if (ent && ent->value == VAL_SUTRS && r->which == Z_External_sutrs)
376         recv_textRecord((int) VAL_SUTRS, (const char *) r->u.sutrs->buf,
377                         (size_t) r->u.sutrs->len);
378     else if (ent && ent->value == VAL_GRS1 && r->which == Z_External_grs1)
379         recv_genericRecord(r->u.grs1);
380     else 
381     {
382         printf("Unknown record representation.\n");
383         if (!z_External(odr_print(), &r, 0, 0))
384         {
385             odr_perror(odr_print(), "Printing external");
386             odr_reset(odr_print());
387         }
388     }    
389 }
390
391 void MyClient::recv_namePlusRecord (Z_NamePlusRecord *zpr, int offset)
392 {
393     if (zpr->databaseName)
394         printf("[%s]", zpr->databaseName);
395     if (zpr->which == Z_NamePlusRecord_surrogateDiagnostic)
396         recv_diagrecs(&zpr->u.surrogateDiagnostic, 1);
397     else
398         recv_record(zpr->u.databaseRecord, offset, zpr->databaseName);
399 }
400
401 void MyClient::recv_records (Z_Records *records)
402 {
403 #ifdef ASN_COMPILED
404     Z_DiagRec dr, *dr_p = &dr;
405 #endif
406     if (!records)
407         return;
408     int i;
409     switch (records->which)
410     {
411     case Z_Records_DBOSD:
412         for (i = 0; i < records->u.databaseOrSurDiagnostics->num_records; i++)
413             recv_namePlusRecord(records->u.databaseOrSurDiagnostics->
414                                 records[i], i + m_setOffset);
415         m_setOffset += records->u.databaseOrSurDiagnostics->num_records;
416         break;
417     case Z_Records_NSD:
418 #ifdef ASN_COMPILED
419         dr.which = Z_DiagRec_defaultFormat;
420         dr.u.defaultFormat = records->u.nonSurrogateDiagnostic;
421         recv_diagrecs (&dr_p, 1);
422 #else
423         recv_diagrecs (&records->u.nonSurrogateDiagnostic, 1);
424 #endif
425         break;
426     case Z_Records_multipleNSD:
427         recv_diagrecs (records->u.multipleNonSurDiagnostics->diagRecs,
428                        records->u.multipleNonSurDiagnostics->num_diagRecs);
429         break;
430     }
431 }
432
433 void MyClient::recv_searchResponse(Z_SearchResponse *searchResponse)
434 {
435     printf ("Got SearchResponse. Status ");
436     if (!*searchResponse->searchStatus)
437     {
438         printf ("Fail\n");
439         return;
440     }
441     printf ("Ok\n");
442     printf ("Hits: %d\n", *searchResponse->resultCount);
443     recv_records (searchResponse->records);
444 }
445
446 void MyClient::recv_presentResponse(Z_PresentResponse *presentResponse)
447 {
448     printf ("Got PresentResponse\n");
449     recv_records (presentResponse->records);
450 }
451
452 int MyClient::wait()
453 {
454     set_lastReceived(0);
455     while (m_socketManager->processEvent() > 0)
456     {
457         if (get_lastReceived())
458             return 1;
459     }
460     return 0;
461 }
462
463 #define C_PROMPT "Z>"
464
465 int MyClient::cmd_connect(char *host)
466 {
467     client (host);
468     timeout (10);
469     wait ();
470     timeout (0);
471     return 1;
472 }
473
474 int MyClient::cmd_open(char *host)
475 {
476     client (host);
477     timeout (10);
478     wait ();
479     timeout (0);
480     send_initRequest();
481     wait ();
482     return 1;
483 }
484
485 int MyClient::cmd_init(char *args)
486 {
487     if (send_initRequest() >= 0)
488         wait();
489     else
490         close();
491     return 1;
492 }
493
494 int MyClient::cmd_quit(char *args)
495 {
496     return 0;
497 }
498
499 int MyClient::cmd_close(char *args)
500 {
501     close();
502     return 1;
503 }
504
505 int MyClient::cmd_find(char *args)
506 {
507     Yaz_Z_Query query;
508
509     if (query.set_rpn(args) <= 0)
510     {
511         printf ("Bad RPN query\n");
512         return 1;
513     }
514     if (send_searchRequest(&query) >= 0)
515         wait();
516     else
517         printf ("Not connected\n");
518     return 1;
519 }
520
521 int MyClient::cmd_show(char *args)
522 {
523     int start = m_setOffset, number = 1;
524
525     sscanf (args, "%d %d", &start, &number);
526     m_setOffset = start;
527     if (send_presentRequest(start, number) >= 0)
528         wait();
529     else
530         printf ("Not connected\n");
531     return 1;
532 }
533
534 int MyClient::cmd_cookie(char *args)
535 {
536     set_cookie(*args ? args : 0);
537     return 1;
538 }
539
540 int MyClient::cmd_format(char *args)
541 {
542     set_preferredRecordSyntax(args);
543     return 1;
544 }
545
546 int MyClient::cmd_proxy(char *args)
547 {
548     set_proxy(args);
549     return 1;
550 }
551
552 int MyClient::processCommand(const char *commandLine)
553 {
554     char cmdStr[1024], cmdArgs[1024];
555     cmdArgs[0] = '\0';
556     cmdStr[0] = '\0';
557     static struct {
558         char *cmd;
559         int (MyClient::*fun)(char *arg);
560         char *ad;
561     } cmd[] = {
562         {"open", &MyClient::cmd_open, "<host>[':'<port>][/<database>]"},
563         {"connect", &MyClient::cmd_connect, "<host>[':'<port>][/<database>]"},
564         {"quit", &MyClient::cmd_quit, ""},
565         {"close", &MyClient::cmd_close, ""},
566         {"find", &MyClient::cmd_find, "<query>"},
567         {"show", &MyClient::cmd_show, "[<start> [<number>]]"},
568         {"cookie", &MyClient::cmd_cookie, "<cookie>"},
569         {"init", &MyClient::cmd_init, ""},
570         {"format", &MyClient::cmd_format, "<record-syntax>"},
571         {"proxy", &MyClient::cmd_proxy, "<host>:[':'<port>]"},
572         {0,0,0}
573     };
574     
575     if (sscanf(commandLine, "%s %[^;]", cmdStr, cmdArgs) < 1)
576         return 1;
577     int i;
578     for (i = 0; cmd[i].cmd; i++)
579         if (!strncmp(cmd[i].cmd, cmdStr, strlen(cmdStr)))
580             break;
581     
582     int res = 1;
583     if (cmd[i].cmd) // Invoke command handler
584         res = (this->*cmd[i].fun)(cmdArgs);
585     else            // Dump help screen
586     {
587         printf("Unknown command: %s.\n", cmdStr);
588         printf("Currently recognized commands:\n");
589         for (i = 0; cmd[i].cmd; i++)
590             printf("   %s %s\n", cmd[i].cmd, cmd[i].ad);
591     }
592     return res;
593 }
594
595 const char *MyClient::getCommand()
596 {
597 #if HAVE_READLINE_READLINE_H
598     // Read using GNU readline
599     char *line_in;
600     line_in=readline(C_PROMPT);
601     if (!line_in)
602         return 0;
603 #if HAVE_READLINE_HISTORY_H
604     if (*line_in)
605         add_history(line_in);
606 #endif
607     strncpy(m_thisCommand,line_in, 1023);
608     m_thisCommand[1023] = '\0';
609     free (line_in);
610 #else    
611     // Read using fgets(3)
612     printf (C_PROMPT);
613     fflush(stdout);
614     if (!fgets(m_thisCommand, 1023, stdin))
615         return 0;
616 #endif
617     // Remove trailing whitespace
618     char *cp = m_thisCommand + strlen(m_thisCommand);
619     while (cp != m_thisCommand && strchr("\t \n", cp[-1]))
620         cp--;
621     *cp = '\0';
622     cp = m_thisCommand;
623     // Remove leading spaces...
624     while (*cp && strchr ("\t \n", *cp))
625         cp++;
626     // Save command if non-empty
627     if (*cp != '\0')
628         strcpy (m_lastCommand, cp);
629     return m_lastCommand;
630 }
631
632 int MyClient::interactive(Yaz_SocketManager *socketManager)
633 {
634     const char *cmd;
635     if (!m_interactive_flag)
636         return 0;
637     while ((cmd = getCommand()))
638     {
639         if (!processCommand(cmd))
640             break;
641     }
642     return 0;
643 }
644
645 int MyClient::args(Yaz_SocketManager *socketManager, int argc, char **argv)
646 {
647     char *host = 0;
648     char *proxy = 0;
649     char *arg;
650     char *prog = argv[0];
651     int ret;
652
653     while ((ret = options("c:p:v:q", argv, argc, &arg)) != -2)
654     {
655         switch (ret)
656         {
657         case 0:
658             if (host)
659             {
660                 usage(prog);
661                 return 1;
662             }
663             host = arg;
664             break;
665         case 'p':
666             if (proxy)
667             {
668                 usage(prog);
669                 return 1;
670             }
671             set_proxy(arg);
672             break;
673         case 'c':
674             set_cookie(arg);
675             break;
676         case 'v':
677             yaz_log_init_level (yaz_log_mask_str(arg));
678             break;
679         case 'q':
680             m_interactive_flag = 0;
681             break;
682         default:
683             usage(prog);
684             return 1;
685         }
686     }
687     if (host)
688     {
689         client (host);
690         timeout (10);
691         wait ();
692         timeout (0);
693         send_initRequest();
694         wait ();
695     }
696     return 0;
697 }
698
699 int main(int argc, char **argv)
700 {
701     Yaz_SocketManager mySocketManager;
702     Yaz_PDU_Assoc *some = new Yaz_PDU_Assoc(&mySocketManager);
703
704     MyClient z(some, &mySocketManager);
705
706     if (z.args(&mySocketManager, argc, argv))
707         exit (1);
708     if (z.interactive(&mySocketManager))
709         exit (1);
710     return 0;
711 }