Fixed bug that caused MARC decode to SEGV for some invalid MARC records.
[yaz-moved-to-github.git] / src / marcdisp.c
1 /*
2  * Copyright (C) 1995-2005, Index Data ApS
3  * See the file LICENSE for details.
4  *
5  * $Id: marcdisp.c,v 1.17 2005-02-25 09:37:53 adam Exp $
6  */
7
8 /**
9  * \file marcdisp.c
10  * \brief Implements MARC display - and conversion utilities
11  */
12
13 #if HAVE_CONFIG_H
14 #include <config.h>
15 #endif
16
17 #include <stdio.h>
18 #include <string.h>
19 #include <ctype.h>
20 #include <yaz/marcdisp.h>
21 #include <yaz/wrbuf.h>
22 #include <yaz/yaz-util.h>
23
24 struct yaz_marc_t_ {
25     WRBUF m_wr;
26     int xml;
27     int debug;
28     yaz_iconv_t iconv_cd;
29     char subfield_str[8];
30     char endline_str[8];
31 };
32
33 yaz_marc_t yaz_marc_create(void)
34 {
35     yaz_marc_t mt = (yaz_marc_t) xmalloc(sizeof(*mt));
36     mt->xml = YAZ_MARC_LINE;
37     mt->debug = 0;
38     mt->m_wr = wrbuf_alloc();
39     mt->iconv_cd = 0;
40     strcpy(mt->subfield_str, " $");
41     strcpy(mt->endline_str, "\n");
42     return mt;
43 }
44
45 void yaz_marc_subfield_str(yaz_marc_t mt, const char *s)
46 {
47     strncpy(mt->subfield_str, s, sizeof(mt->subfield_str)-1);
48     mt->subfield_str[sizeof(mt->subfield_str)-1] = '\0';
49 }
50
51 void yaz_marc_endline_str(yaz_marc_t mt, const char *s)
52 {
53     strncpy(mt->endline_str, s, sizeof(mt->endline_str)-1);
54     mt->endline_str[sizeof(mt->endline_str)-1] = '\0';
55 }
56
57 void yaz_marc_destroy(yaz_marc_t mt)
58 {
59     if (!mt)
60         return ;
61     wrbuf_free (mt->m_wr, 1);
62     xfree (mt);
63 }
64
65 static void marc_cdata (yaz_marc_t mt, const char *buf, size_t len, WRBUF wr)
66 {
67     if (mt->xml == YAZ_MARC_ISO2709)
68         wrbuf_iconv_write(wr, mt->iconv_cd, buf, len);
69     else if (mt->xml == YAZ_MARC_LINE)
70         wrbuf_iconv_write(wr, mt->iconv_cd, buf, len);
71     else
72         wrbuf_iconv_write_cdata(wr, mt->iconv_cd, buf, len);
73 }
74
75 static int atoi_n_check(const char *buf, int size, int *val)
76 {
77     if (!isdigit(*(const unsigned char *) buf))
78         return 0;
79     *val = atoi_n(buf, size);
80     return 1;
81 }
82
83 int yaz_marc_decode_wrbuf (yaz_marc_t mt, const char *buf, int bsize, WRBUF wr)
84 {
85     int entry_p;
86     int record_length;
87     int indicator_length;
88     int identifier_length;
89     int base_address;
90     int length_data_entry;
91     int length_starting;
92     int length_implementation;
93     char lead[24];
94     int produce_warnings = 0;
95
96     if (mt->debug)
97         produce_warnings = 1;
98     if (mt->xml == YAZ_MARC_SIMPLEXML || mt->xml == YAZ_MARC_OAIMARC
99         || mt->xml == YAZ_MARC_MARCXML || mt->xml == YAZ_MARC_XCHANGE)
100         produce_warnings = 1;
101
102     wrbuf_rewind(wr);
103
104     record_length = atoi_n (buf, 5);
105     if (record_length < 25)
106     {
107         if (mt->debug)
108         {
109             char str[40];
110             
111             wrbuf_printf(wr, "<!-- Record length %d - aborting -->\n",
112                             record_length);
113         }
114         return -1;
115     }
116     memcpy(lead, buf, 24);  /* se can modify the header for output */
117
118     /* ballout if bsize is known and record_length is less than that */
119     if (bsize != -1 && record_length > bsize)
120         return -1;
121     if (!atoi_n_check(buf+10, 1, &indicator_length))
122     {
123         if (produce_warnings)
124             wrbuf_printf(wr, "<!-- Indicator length at offset 10 should hold a digit. Assuming 2 -->\n");
125         lead[10] = '2';
126         indicator_length = 2;
127     }
128     if (!atoi_n_check(buf+11, 1, &identifier_length))
129     {
130         if (produce_warnings)
131             wrbuf_printf(wr, "<!-- Identifier length at offset 11 should hold a digit. Assuming 2 -->\n");
132         lead[11] = '2';
133         identifier_length = 2;
134     }
135     if (!atoi_n_check(buf+12, 5, &base_address))
136     {
137         if (produce_warnings)
138             wrbuf_printf(wr, "<!-- Base address at offsets 12..16 should hold a number. Assuming 0 -->\n");
139         base_address = 0;
140     }
141     if (!atoi_n_check(buf+20, 1, &length_data_entry))
142     {
143         if (produce_warnings)
144             wrbuf_printf(wr, "<!-- Length data entry at offset 20 should hold a digit. Assuming 4 -->\n");
145         length_data_entry = 4;
146         lead[20] = '4';
147     }
148     if (!atoi_n_check(buf+21, 1, &length_starting))
149     {
150         if (produce_warnings)
151             wrbuf_printf(wr, "<!-- Length starting at offset 21 should hold a digit. Assuming 5 -->\n");
152         length_starting = 5;
153         lead[21] = '5';
154     }
155     if (!atoi_n_check(buf+22, 1, &length_implementation))
156     {
157         if (produce_warnings)
158             wrbuf_printf(wr, "<!-- Length implementation at offset 22 should hold a digit. Assuming 0 -->\n");
159         length_implementation = 0;
160         lead[22] = '0';
161     }
162
163     if (mt->xml != YAZ_MARC_LINE)
164     {
165         char str[80];
166         int i;
167         switch(mt->xml)
168         {
169         case YAZ_MARC_ISO2709:
170             break;
171         case YAZ_MARC_SIMPLEXML:
172             wrbuf_puts (wr, "<iso2709\n");
173             sprintf (str, " RecordStatus=\"%c\"\n", buf[5]);
174             wrbuf_puts (wr, str);
175             sprintf (str, " TypeOfRecord=\"%c\"\n", buf[6]);
176             wrbuf_puts (wr, str);
177             for (i = 1; i<=19; i++)
178             {
179                 sprintf (str, " ImplDefined%d=\"%c\"\n", i, buf[6+i]);
180                 wrbuf_puts (wr, str);
181             }
182             wrbuf_puts (wr, ">\n");
183             break;
184         case YAZ_MARC_OAIMARC:
185             wrbuf_puts(
186                 wr,
187                 "<oai_marc xmlns=\"http://www.openarchives.org/OIA/oai_marc\""
188                 "\n"
189                 " xmlns:xsi=\"http://www.w3.org/2000/10/XMLSchema-instance\""
190                 "\n"
191                 " xsi:schemaLocation=\"http://www.openarchives.org/OAI/oai_marc.xsd\""
192                 "\n"
193                 );
194             
195             sprintf (str, " status=\"%c\" type=\"%c\" catForm=\"%c\">\n",
196                      buf[5], buf[6], buf[7]);
197             wrbuf_puts (wr, str);
198             break;
199         case YAZ_MARC_MARCXML:
200             wrbuf_printf(
201                 wr,
202                 "<record xmlns=\"http://www.loc.gov/MARC21/slim\">\n"
203                 "  <leader>");
204             lead[9] = 'a';                 /* set leader to signal unicode */
205             marc_cdata(mt, lead, 24, wr); 
206             wrbuf_printf(wr, "</leader>\n");
207             break;
208         case YAZ_MARC_XCHANGE:
209             wrbuf_printf(
210                 wr,
211                 "<record xmlns=\"http://www.bs.dk/standards/MarcXchange\">\n"
212                 "  <leader>");
213             marc_cdata(mt, lead, 24, wr);
214             wrbuf_printf(wr, "</leader>\n");
215             break;
216         }
217     }
218     if (mt->debug)
219     {
220         char str[40];
221
222         if (mt->xml)
223             wrbuf_puts (wr, "<!--\n");
224         sprintf (str, "Record length         %5d\n", record_length);
225         wrbuf_puts (wr, str);
226         sprintf (str, "Indicator length      %5d\n", indicator_length);
227         wrbuf_puts (wr, str);
228         sprintf (str, "Identifier length     %5d\n", identifier_length);
229         wrbuf_puts (wr, str);
230         sprintf (str, "Base address          %5d\n", base_address);
231         wrbuf_puts (wr, str);
232         sprintf (str, "Length data entry     %5d\n", length_data_entry);
233         wrbuf_puts (wr, str);
234         sprintf (str, "Length starting       %5d\n", length_starting);
235         wrbuf_puts (wr, str);
236         sprintf (str, "Length implementation %5d\n", length_implementation);
237         wrbuf_puts (wr, str);
238         if (mt->xml)
239             wrbuf_puts (wr, "-->\n");
240     }
241
242     /* first pass. determine length of directory & base of data */
243     for (entry_p = 24; buf[entry_p] != ISO2709_FS; )
244     {
245         entry_p += 3+length_data_entry+length_starting;
246         if (entry_p >= record_length)
247             return -1;
248     }
249     if (base_address != entry_p+1)
250     {
251         if (produce_warnings)
252             wrbuf_printf (wr,"  <!-- Base address not at end of directory "
253                           "base=%d end=%d -->\n", base_address, entry_p+1);
254     }
255     base_address = entry_p+1;
256
257     if (mt->xml == YAZ_MARC_ISO2709)
258     {
259         WRBUF wr_head = wrbuf_alloc();
260         WRBUF wr_dir = wrbuf_alloc();
261         WRBUF wr_tmp = wrbuf_alloc();
262
263         int data_p = 0;
264         /* second pass. create directory for ISO2709 output */
265         for (entry_p = 24; buf[entry_p] != ISO2709_FS; )
266         {
267             int data_length, data_offset, end_offset;
268             int i, sz1, sz2;
269             
270             wrbuf_write(wr_dir, buf+entry_p, 3);
271             entry_p += 3;
272             
273             data_length = atoi_n (buf+entry_p, length_data_entry);
274             entry_p += length_data_entry;
275             data_offset = atoi_n (buf+entry_p, length_starting);
276             entry_p += length_starting;
277             i = data_offset + base_address;
278             end_offset = i+data_length-1;
279             
280             if (data_length <= 0 || data_offset < 0 || end_offset >= record_length)
281                 return -1;
282         
283             while (i < end_offset &&
284                     buf[i] != ISO2709_RS && buf[i] != ISO2709_FS)
285                 i++;
286             sz1 = 1+i - (data_offset + base_address);
287             if (mt->iconv_cd)
288             {
289                 sz2 = wrbuf_iconv_write(wr_tmp, mt->iconv_cd,
290                                         buf + data_offset+base_address, sz1);
291                 wrbuf_rewind(wr_tmp);
292             }
293             else
294                 sz2 = sz1;
295             wrbuf_printf(wr_dir, "%0*d", length_data_entry, sz2);
296             wrbuf_printf(wr_dir, "%0*d", length_starting, data_p);
297             data_p += sz2;
298         }
299         wrbuf_putc(wr_dir, ISO2709_FS);
300         wrbuf_printf(wr_head, "%05d", data_p+1 + base_address);
301         wrbuf_write(wr_head, lead+5, 7);
302         wrbuf_printf(wr_head, "%05d", base_address);
303         wrbuf_write(wr_head, lead+17, 7);
304
305         wrbuf_write(wr, wrbuf_buf(wr_head), 24);
306         wrbuf_write(wr, wrbuf_buf(wr_dir), wrbuf_len(wr_dir));
307         wrbuf_free(wr_head, 1);
308         wrbuf_free(wr_dir, 1);
309         wrbuf_free(wr_tmp, 1);
310     }
311     /* third pass. create data output */
312     for (entry_p = 24; buf[entry_p] != ISO2709_FS; )
313     {
314         int data_length;
315         int data_offset;
316         int end_offset;
317         int i, j;
318         char tag[4];
319         int identifier_flag = 0;
320         int entry_p0;
321
322         memcpy (tag, buf+entry_p, 3);
323         entry_p += 3;
324         entry_p0 = entry_p;
325         tag[3] = '\0';
326         data_length = atoi_n (buf+entry_p, length_data_entry);
327         entry_p += length_data_entry;
328         data_offset = atoi_n (buf+entry_p, length_starting);
329         entry_p += length_starting;
330         i = data_offset + base_address;
331         end_offset = i+data_length-1;
332
333         if (data_length <= 0 || data_offset < 0 || end_offset >= record_length)
334         {
335             if (produce_warnings)
336                 wrbuf_printf (wr,"  <!-- Bad data-offset=%d or "
337                                     "data-length=%d -->\n",
338                                           data_length, data_offset);
339             break;
340         }
341         
342         if (mt->debug)
343         {
344             wrbuf_printf(wr, "<!-- offset=%d data dlength=%d doffset=%d -->\n",
345                     entry_p0, data_length, data_offset);
346         }
347         
348         if (memcmp (tag, "00", 2))
349             identifier_flag = 1;  /* if not 00X assume subfields */
350         else if (indicator_length < 4 && indicator_length > 0)
351         {
352             /* Danmarc 00X have subfields */
353             if (buf[i + indicator_length] == ISO2709_IDFS)
354                 identifier_flag = 1;
355             else if (buf[i + indicator_length + 1] == ISO2709_IDFS)
356                 identifier_flag = 2;
357         }
358
359         if (mt->debug)
360         {
361             wrbuf_printf(wr, "<!-- identifier_flag = %d -->\n",
362                          identifier_flag);
363         } 
364        
365         switch(mt->xml)
366         {
367         case YAZ_MARC_LINE:
368             if (mt->debug)
369                 wrbuf_puts (wr, "Tag: ");
370             wrbuf_puts (wr, tag);
371             wrbuf_puts (wr, " ");
372             break;
373         case YAZ_MARC_SIMPLEXML:
374             wrbuf_printf (wr, "<field tag=\"");
375             marc_cdata(mt, tag, strlen(tag), wr);
376             wrbuf_printf(wr, "\"");
377             break;
378         case YAZ_MARC_OAIMARC:
379             if (identifier_flag)
380                 wrbuf_printf (wr, "  <varfield id=\"");
381             else
382                 wrbuf_printf (wr, "  <fixfield id=\"");
383             marc_cdata(mt, tag, strlen(tag), wr);
384             wrbuf_printf(wr, "\"");
385             break;
386         case YAZ_MARC_MARCXML:
387         case YAZ_MARC_XCHANGE:
388             if (identifier_flag)
389                 wrbuf_printf (wr, "  <datafield tag=\"");
390             else
391                 wrbuf_printf (wr, "  <controlfield tag=\"");
392             marc_cdata(mt, tag, strlen(tag), wr);
393             wrbuf_printf(wr, "\"");
394         }
395         
396         if (identifier_flag)
397         {
398             i += identifier_flag-1;
399             for (j = 0; j<indicator_length; j++, i++)
400             {
401                 switch(mt->xml)
402                 {
403                 case YAZ_MARC_ISO2709:
404                     wrbuf_putc(wr, buf[i]);
405                     break;
406                 case YAZ_MARC_LINE:
407                     if (mt->debug)
408                         wrbuf_puts (wr, " Ind: ");
409                     wrbuf_putc(wr, buf[i]);
410                     break;
411                 case YAZ_MARC_SIMPLEXML:
412                     wrbuf_printf(wr, " Indicator%d=\"", j+1);
413                     marc_cdata(mt, buf+i, 1, wr);
414                     wrbuf_printf(wr, "\"");
415                     break;
416                 case YAZ_MARC_OAIMARC:
417                     wrbuf_printf(wr, " i%d=\"", j+1);
418                     marc_cdata(mt, buf+i, 1, wr);
419                     wrbuf_printf(wr, "\"");
420                     break;
421                 case YAZ_MARC_MARCXML:
422                 case YAZ_MARC_XCHANGE:
423                     wrbuf_printf(wr, " ind%d=\"", j+1);
424                     marc_cdata(mt, buf+i, 1, wr);
425                     wrbuf_printf(wr, "\"");
426                 }
427             }
428         }
429         if (mt->xml == YAZ_MARC_SIMPLEXML || mt->xml == YAZ_MARC_MARCXML
430             || mt->xml == YAZ_MARC_OAIMARC || mt->xml == YAZ_MARC_XCHANGE)
431         {
432             wrbuf_puts (wr, ">");
433             if (identifier_flag)
434                 wrbuf_puts (wr, "\n");
435         }
436         if (mt->xml == YAZ_MARC_LINE)
437         {
438             if (mt->debug)
439                 wrbuf_puts (wr, " Fields: ");
440         }
441         if (identifier_flag)
442         {
443             while (i < end_offset &&
444                     buf[i] != ISO2709_RS && buf[i] != ISO2709_FS)
445             {
446                 int i0;
447                 i++;
448                 switch(mt->xml)
449                 {
450                 case YAZ_MARC_ISO2709:
451                     --i;
452                     wrbuf_iconv_write(wr, mt->iconv_cd, 
453                                       buf+i, identifier_length);
454                     i += identifier_length;
455                     break;
456                 case YAZ_MARC_LINE: 
457                     wrbuf_puts (wr, mt->subfield_str); 
458                     marc_cdata(mt, buf+i, identifier_length-1, wr);
459                     i = i+identifier_length-1;
460                     wrbuf_putc (wr, ' ');
461                     break;
462                 case YAZ_MARC_SIMPLEXML:
463                     wrbuf_puts (wr, "  <subfield code=\"");
464                     marc_cdata(mt, buf+i, identifier_length-1, wr);
465                     i = i+identifier_length-1;
466                     wrbuf_puts (wr, "\">");
467                     break;
468                 case YAZ_MARC_OAIMARC:
469                     wrbuf_puts (wr, "    <subfield label=\"");
470                     marc_cdata(mt, buf+i, identifier_length-1, wr);
471                     i = i+identifier_length-1;
472                     wrbuf_puts (wr, "\">");
473                     break;
474                 case YAZ_MARC_MARCXML:
475                 case YAZ_MARC_XCHANGE:
476                     wrbuf_puts (wr, "    <subfield code=\"");
477                     marc_cdata(mt, buf+i, identifier_length-1, wr);
478                     i = i+identifier_length-1;
479                     wrbuf_puts (wr, "\">");
480                     break;
481                 }
482                 i0 = i;
483                 while (i < end_offset &&
484                         buf[i] != ISO2709_RS && buf[i] != ISO2709_IDFS &&
485                         buf[i] != ISO2709_FS)
486                     i++;
487                 marc_cdata(mt, buf + i0, i - i0, wr);
488
489                 if (mt->xml == YAZ_MARC_ISO2709 && buf[i] != ISO2709_IDFS)
490                     marc_cdata(mt, buf + i, 1, wr);
491
492                 if (mt->xml == YAZ_MARC_SIMPLEXML || 
493                     mt->xml == YAZ_MARC_MARCXML ||
494                     mt->xml == YAZ_MARC_XCHANGE ||
495                     mt->xml == YAZ_MARC_OAIMARC)
496                     wrbuf_puts (wr, "</subfield>\n");
497             }
498         }
499         else
500         {
501             int i0 = i;
502             while (i < end_offset && 
503                 buf[i] != ISO2709_RS && buf[i] != ISO2709_FS)
504                 i++;
505             marc_cdata(mt, buf + i0, i - i0, wr);
506             if (mt->xml == YAZ_MARC_ISO2709)
507                 marc_cdata(mt, buf + i, 1, wr);
508         }
509         if (mt->xml == YAZ_MARC_LINE)
510             wrbuf_puts (wr, mt->endline_str);
511         if (i < end_offset)
512             wrbuf_printf(wr, "  <!-- separator but not at end of field length=%d-->\n", data_length);
513         if (buf[i] != ISO2709_RS && buf[i] != ISO2709_FS)
514             wrbuf_printf(wr, "  <!-- no separator at end of field length=%d-->\n", data_length);
515         switch(mt->xml)
516         {
517         case YAZ_MARC_SIMPLEXML:
518             wrbuf_puts (wr, "</field>\n");
519             break;
520         case YAZ_MARC_OAIMARC:
521             if (identifier_flag)
522                 wrbuf_puts (wr, "</varfield>\n");
523             else
524                 wrbuf_puts (wr, "</fixfield>\n");
525             break;
526         case YAZ_MARC_MARCXML:
527         case YAZ_MARC_XCHANGE:
528             if (identifier_flag)
529                 wrbuf_puts (wr, "  </datafield>\n");
530             else
531                 wrbuf_puts (wr, "</controlfield>\n");
532             break;
533         }
534     }
535     switch (mt->xml)
536     {
537     case YAZ_MARC_LINE:
538         wrbuf_puts (wr, "");
539         break;
540     case YAZ_MARC_SIMPLEXML:
541         wrbuf_puts (wr, "</iso2709>\n");
542         break;
543     case YAZ_MARC_OAIMARC:
544         wrbuf_puts (wr, "</oai_marc>\n");
545         break;
546     case YAZ_MARC_MARCXML:
547     case YAZ_MARC_XCHANGE:
548         wrbuf_puts (wr, "</record>\n");
549         break;
550     case YAZ_MARC_ISO2709:
551         wrbuf_putc (wr, ISO2709_RS);
552         break;
553     }
554     return record_length;
555 }
556
557 int yaz_marc_decode_buf (yaz_marc_t mt, const char *buf, int bsize,
558                          char **result, int *rsize)
559 {
560     int r = yaz_marc_decode_wrbuf(mt, buf, bsize, mt->m_wr);
561     if (r > 0)
562     {
563         if (result)
564             *result = wrbuf_buf(mt->m_wr);
565         if (rsize)
566             *rsize = wrbuf_len(mt->m_wr);
567     }
568     return r;
569 }
570
571 void yaz_marc_xml(yaz_marc_t mt, int xmlmode)
572 {
573     if (mt)
574         mt->xml = xmlmode;
575 }
576
577 void yaz_marc_debug(yaz_marc_t mt, int level)
578 {
579     if (mt)
580         mt->debug = level;
581 }
582
583 void yaz_marc_iconv(yaz_marc_t mt, yaz_iconv_t cd)
584 {
585     mt->iconv_cd = cd;
586 }
587
588 /* depricated */
589 int yaz_marc_decode(const char *buf, WRBUF wr, int debug, int bsize, int xml)
590 {
591     yaz_marc_t mt = yaz_marc_create();
592     int r;
593
594     mt->debug = debug;
595     mt->xml = xml;
596     r = yaz_marc_decode_wrbuf(mt, buf, bsize, wr);
597     yaz_marc_destroy(mt);
598     return r;
599 }
600
601 /* depricated */
602 int marc_display_wrbuf (const char *buf, WRBUF wr, int debug, int bsize)
603 {
604     return yaz_marc_decode(buf, wr, debug, bsize, 0);
605 }
606
607 /* depricated */
608 int marc_display_exl (const char *buf, FILE *outf, int debug, int bsize)
609 {
610     yaz_marc_t mt = yaz_marc_create();
611     int r;
612
613     mt->debug = debug;
614     r = yaz_marc_decode_wrbuf (mt, buf, bsize, mt->m_wr);
615     if (!outf)
616         outf = stdout;
617     if (r > 0)
618         fwrite (wrbuf_buf(mt->m_wr), 1, wrbuf_len(mt->m_wr), outf);
619     yaz_marc_destroy(mt);
620     return r;
621 }
622
623 /* depricated */
624 int marc_display_ex (const char *buf, FILE *outf, int debug)
625 {
626     return marc_display_exl (buf, outf, debug, -1);
627 }
628
629 /* depricated */
630 int marc_display (const char *buf, FILE *outf)
631 {
632     return marc_display_ex (buf, outf, 0);
633 }
634