1 /*
   2  * This file and its contents are supplied under the terms of the
   3  * Common Development and Distribution License ("CDDL"), version 1.0.
   4  * You may only use this file in accordance with the terms of version
   5  * 1.0 of the CDDL.
   6  *
   7  * A full copy of the text of the CDDL should have accompanied this
   8  * source.  A copy of the CDDL is also available via the Internet at
   9  * http://www.illumos.org/license/CDDL.
  10  */
  11 
  12 /*
  13  * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
  14  */
  15 
  16 /*
  17  * CHARMAP file handling for iconv.
  18  */
  19 
  20 #include <stdio.h>
  21 #include <stdlib.h>
  22 #include <string.h>
  23 #include <errno.h>
  24 #include <limits.h>
  25 #include <unistd.h>
  26 #include <alloca.h>
  27 #include <sys/avl.h>
  28 #include <stddef.h>
  29 #include <unistd.h>
  30 #include "charmap.h"
  31 #include "parser.tab.h"
  32 #include <assert.h>
  33 
  34 enum cmap_pass cmap_pass;
  35 static avl_tree_t       cmap_sym;
  36 static avl_tree_t       cmap_mbs;
  37 
  38 typedef struct charmap {
  39         const char *name;
  40         struct charmap *alias_of;
  41         avl_node_t avl_sym;
  42         avl_node_t avl_mbs;
  43         int warned;
  44         int mbs_len;
  45         int tombs_len;
  46         char mbs[MB_LEN_MAX + 1]; /* input */
  47         char tombs[MB_LEN_MAX + 1]; /* output */
  48 } charmap_t;
  49 
  50 static void add_charmap_impl_fr(char *sym, char *mbs, int mbs_len, int nodups);
  51 static void add_charmap_impl_to(char *sym, char *mbs, int mbs_len, int nodups);
  52 
  53 /*
  54  * Array of POSIX specific portable characters.
  55  */
  56 static const struct {
  57         char    *name;
  58         int     ch;
  59 } portable_chars[] = {
  60         { "NUL",                 '\0' },
  61         { "alert",              '\a' },
  62         { "backspace",          '\b' },
  63         { "tab",                '\t' },
  64         { "carriage-return",    '\r' },
  65         { "newline",            '\n' },
  66         { "vertical-tab",       '\v' },
  67         { "form-feed",          '\f' },
  68         { "space",              ' ' },
  69         { "exclamation-mark",   '!' },
  70         { "quotation-mark",     '"' },
  71         { "number-sign",        '#' },
  72         { "dollar-sign",        '$' },
  73         { "percent-sign",       '%' },
  74         { "ampersand",          '&' },
  75         { "apostrophe",         '\'' },
  76         { "left-parenthesis",   '(' },
  77         { "right-parenthesis",  '(' },
  78         { "asterisk",           '*' },
  79         { "plus-sign",          '+' },
  80         { "comma",               ','},
  81         { "hyphen-minus",       '-' },
  82         { "hyphen",             '-' },
  83         { "full-stop",          '.' },
  84         { "period",             '.' },
  85         { "slash",              '/' },
  86         { "solidus",            '/' },
  87         { "zero",               '0' },
  88         { "one",                '1' },
  89         { "two",                '2' },
  90         { "three",              '3' },
  91         { "four",               '4' },
  92         { "five",               '5' },
  93         { "six",                '6' },
  94         { "seven",              '7' },
  95         { "eight",              '8' },
  96         { "nine",               '9' },
  97         { "colon",              ':' },
  98         { "semicolon",          ';' },
  99         { "less-than-sign",     '<' },
 100         { "equals-sign",        '=' },
 101         { "greater-than-sign",  '>' },
 102         { "question-mark",      '?' },
 103         { "commercial-at",      '@' },
 104         { "left-square-bracket", '[' },
 105         { "backslash",          '\\' },
 106         { "reverse-solidus",    '\\' },
 107         { "right-square-bracket", ']' },
 108         { "circumflex",         '^' },
 109         { "circumflex-accent",  '^' },
 110         { "low-line",           '_' },
 111         { "underscore",         '_' },
 112         { "grave-accent",       '`' },
 113         { "left-brace",         '{' },
 114         { "left-curly-bracket", '{' },
 115         { "vertical-line",      '|' },
 116         { "right-brace",        '}' },
 117         { "right-curly-bracket", '}' },
 118         { "tilde",              '~' },
 119         { "A", 'A' },
 120         { "B", 'B' },
 121         { "C", 'C' },
 122         { "D", 'D' },
 123         { "E", 'E' },
 124         { "F", 'F' },
 125         { "G", 'G' },
 126         { "H", 'H' },
 127         { "I", 'I' },
 128         { "J", 'J' },
 129         { "K", 'K' },
 130         { "L", 'L' },
 131         { "M", 'M' },
 132         { "N", 'N' },
 133         { "O", 'O' },
 134         { "P", 'P' },
 135         { "Q", 'Q' },
 136         { "R", 'R' },
 137         { "S", 'S' },
 138         { "T", 'T' },
 139         { "U", 'U' },
 140         { "V", 'V' },
 141         { "W", 'W' },
 142         { "X", 'X' },
 143         { "Y", 'Y' },
 144         { "Z", 'Z' },
 145         { "a", 'a' },
 146         { "b", 'b' },
 147         { "c", 'c' },
 148         { "d", 'd' },
 149         { "e", 'e' },
 150         { "f", 'f' },
 151         { "g", 'g' },
 152         { "h", 'h' },
 153         { "i", 'i' },
 154         { "j", 'j' },
 155         { "k", 'k' },
 156         { "l", 'l' },
 157         { "m", 'm' },
 158         { "n", 'n' },
 159         { "o", 'o' },
 160         { "p", 'p' },
 161         { "q", 'q' },
 162         { "r", 'r' },
 163         { "s", 's' },
 164         { "t", 't' },
 165         { "u", 'u' },
 166         { "v", 'v' },
 167         { "w", 'w' },
 168         { "x", 'x' },
 169         { "y", 'y' },
 170         { "z", 'z' },
 171         { NULL, 0 }
 172 };
 173 
 174 static int
 175 cmap_compare_sym(const void *n1, const void *n2)
 176 {
 177         const charmap_t *c1 = n1;
 178         const charmap_t *c2 = n2;
 179         int rv;
 180 
 181         rv = strcmp(c1->name, c2->name);
 182         return ((rv < 0) ? -1 : (rv > 0) ? 1 : 0);
 183 }
 184 
 185 /*
 186  * In order for partial match searches to work,
 187  * we need these sorted by mbs contents.
 188  */
 189 static int
 190 cmap_compare_mbs(const void *n1, const void *n2)
 191 {
 192         const charmap_t *c1 = n1;
 193         const charmap_t *c2 = n2;
 194         int len, rv;
 195 
 196         len = c1->mbs_len;
 197         if (len < c2->mbs_len)
 198                 len = c2->mbs_len;
 199         rv = memcmp(c1->mbs, c2->mbs, len);
 200         if (rv < 0)
 201                 return (-1);
 202         if (rv > 0)
 203                 return (1);
 204         /* they match through length */
 205         if (c1->mbs_len < c2->mbs_len)
 206                 return (-1);
 207         if (c2->mbs_len < c1->mbs_len)
 208                 return (1);
 209         return (0);
 210 }
 211 
 212 void
 213 charmap_init(char *to_map, char *from_map)
 214 {
 215         avl_create(&cmap_sym, cmap_compare_sym, sizeof (charmap_t),
 216             offsetof(charmap_t, avl_sym));
 217 
 218         avl_create(&cmap_mbs, cmap_compare_mbs, sizeof (charmap_t),
 219             offsetof(charmap_t, avl_mbs));
 220 
 221         cmap_pass = CMAP_PASS_FROM;
 222         reset_scanner(from_map);
 223         (void) yyparse();
 224         add_charmap_posix();
 225 
 226         cmap_pass = CMAP_PASS_TO;
 227         reset_scanner(to_map);
 228         (void) yyparse();
 229 }
 230 
 231 void
 232 charmap_dump()
 233 {
 234         charmap_t *cm;
 235         int i;
 236 
 237         cm = avl_first(&cmap_mbs);
 238         while (cm != NULL) {
 239                 (void) printf("name=\"%s\"\n", cm->name);
 240 
 241                 (void) printf("\timbs=\"");
 242                 for (i = 0; i < cm->mbs_len; i++)
 243                         (void) printf("\\x%02x", cm->mbs[i] & 0xFF);
 244                 (void) printf("\"\n");
 245 
 246                 (void) printf("\tombs=\"");
 247                 for (i = 0; i < cm->tombs_len; i++)
 248                         (void) printf("\\x%02x", cm->tombs[i] & 0xFF);
 249                 (void) printf("\"\n");
 250 
 251                 cm = AVL_NEXT(&cmap_mbs, cm);
 252         }
 253 }
 254 
 255 /*
 256  * We parse two charmap files:  First the "from" map, where we build
 257  * cmap_mbs and cmap_sym which we'll later use to translate the input
 258  * stream (mbs encodings) to symbols.  Second, we parse the "to" map,
 259  * where we fill in the tombs members of entries in cmap_sym, (which
 260  * must alread exist) used later to write the output encoding.
 261  */
 262 static void
 263 add_charmap_impl(char *sym, char *mbs, int mbs_len, int nodups)
 264 {
 265 
 266         /*
 267          * While parsing both the "from" and "to" cmaps,
 268          * require both the symbol and encoding.
 269          */
 270         if (sym == NULL || mbs == NULL) {
 271                 errf(_("invalid charmap entry"));
 272                 return;
 273         }
 274 
 275         switch (cmap_pass) {
 276         case CMAP_PASS_FROM:
 277                 add_charmap_impl_fr(sym, mbs, mbs_len, nodups);
 278                 break;
 279         case CMAP_PASS_TO:
 280                 add_charmap_impl_to(sym, mbs, mbs_len, nodups);
 281                 break;
 282         default:
 283                 assert(0);
 284                 break;
 285         }
 286 }
 287 
 288 static void
 289 add_charmap_impl_fr(char *sym, char *mbs, int mbs_len, int nodups)
 290 {
 291         charmap_t       *m, *n, *s;
 292         avl_index_t     where_sym, where_mbs;
 293 
 294         if ((n = calloc(1, sizeof (*n))) == NULL) {
 295                 errf(_("out of memory"));
 296                 return;
 297         }
 298         n->name = sym;
 299 
 300         assert(0 < mbs_len && mbs_len <= MB_LEN_MAX);
 301         (void) memcpy(n->mbs, mbs, mbs_len);
 302         n->mbs_len = mbs_len;
 303 
 304         m = avl_find(&cmap_mbs, n, &where_mbs);
 305         s = avl_find(&cmap_sym, n, &where_sym);
 306 
 307         /*
 308          * If we found the symbol, this is a dup.
 309          */
 310         if (s != NULL) {
 311                 if (nodups) {
 312                         warn(_("%s: duplicate character symbol"), sym);
 313                 }
 314                 free(n);
 315                 return;
 316         }
 317 
 318         /*
 319          * If we found the mbs, the new one is an alias,
 320          * which we'll add _only_ to the symbol AVL.
 321          */
 322         if (m != NULL) {
 323                 /* The new one is an alias of the original. */
 324                 n->alias_of = m;
 325                 avl_insert(&cmap_sym, n, where_sym);
 326                 return;
 327         }
 328 
 329         avl_insert(&cmap_sym, n, where_sym);
 330         avl_insert(&cmap_mbs, n, where_mbs);
 331 }
 332 
 333 static void
 334 add_charmap_impl_to(char *sym, char *mbs, int mbs_len, int nodups)
 335 {
 336         charmap_t       srch = {0};
 337         charmap_t       *m;
 338         avl_index_t     where;
 339 
 340         assert(0 < mbs_len && mbs_len <= MB_LEN_MAX);
 341 
 342         srch.name = sym;
 343 
 344         m = avl_find(&cmap_sym, &srch, &where);
 345         if (m == NULL) {
 346                 if (sflag == 0)
 347                         warn(_("%s: symbol not found"), sym);
 348                 return;
 349         }
 350         if (m->alias_of != NULL) {
 351                 m = m->alias_of;
 352 
 353                 /* don't warn for dups with aliases */
 354                 if (m->tombs_len != 0)
 355                         return;
 356         }
 357 
 358         if (m->tombs_len != 0) {
 359                 if (nodups) {
 360                         warn(_("%s: duplicate encoding for"), sym);
 361                 }
 362                 return;
 363         }
 364 
 365         (void) memcpy(m->tombs, mbs, mbs_len);
 366         m->tombs_len = mbs_len;
 367 }
 368 
 369 void
 370 add_charmap(char *sym, char *mbs)
 371 {
 372         /* mbs[0] is the length */
 373         int mbs_len = *mbs++;
 374         assert(0 < mbs_len && mbs_len <= MB_LEN_MAX);
 375         add_charmap_impl(sym, mbs, mbs_len, 1);
 376 }
 377 
 378 
 379 void
 380 add_charmap_range(char *ssym, char *esym, char *mbs)
 381 {
 382         int     ls, le;
 383         int     si;
 384         int     sn, en;
 385         int     i;
 386         int     mbs_len;
 387         char    tmbs[MB_LEN_MAX+1];
 388         char    *mb_last;
 389 
 390         static const char *digits = "0123456789";
 391 
 392         /* mbs[0] is the length */
 393         mbs_len = *mbs++;
 394         assert(0 < mbs_len && mbs_len <= MB_LEN_MAX);
 395         (void) memcpy(tmbs, mbs, mbs_len);
 396         mb_last = tmbs + mbs_len - 1;
 397 
 398         ls = strlen(ssym);
 399         le = strlen(esym);
 400 
 401         if (((si = strcspn(ssym, digits)) == 0) || (si == ls) ||
 402             (strncmp(ssym, esym, si) != 0) ||
 403             (strspn(ssym + si, digits) != (ls - si)) ||
 404             (strspn(esym + si, digits) != (le - si)) ||
 405             ((sn = atoi(ssym + si)) > ((en = atoi(esym + si))))) {
 406                 errf(_("malformed charmap range"));
 407                 return;
 408         }
 409 
 410         ssym[si] = 0;
 411         for (i = sn; i <= en; i++) {
 412                 char *nn;
 413                 (void) asprintf(&nn, "%s%0*u", ssym, ls - si, i);
 414                 if (nn == NULL) {
 415                         errf(_("out of memory"));
 416                         return;
 417                 }
 418 
 419                 add_charmap_impl(nn, tmbs, mbs_len, 1);
 420                 (*mb_last)++;
 421         }
 422         free(ssym);
 423         free(esym);
 424 }
 425 
 426 void
 427 add_charmap_char(char *name, int c)
 428 {
 429         char mbs[MB_LEN_MAX+1];
 430 
 431         mbs[0] = c;
 432         mbs[1] = '\0';
 433         add_charmap_impl(name, mbs, 1, 0);
 434 }
 435 
 436 /*
 437  * POSIX insists that certain entries be present, even when not in the
 438  * orginal charmap file.
 439  */
 440 void
 441 add_charmap_posix(void)
 442 {
 443         char    i;
 444 
 445         for (i = 0; portable_chars[i].name; i++) {
 446                 add_charmap_char(portable_chars[i].name, portable_chars[i].ch);
 447         }
 448 }
 449 
 450 static charmap_t *
 451 find_mbs(const char *mbs, int len)
 452 {
 453         charmap_t srch = {0};
 454         charmap_t *cm;
 455 
 456         while (len > 0) {
 457                 (void) memcpy(srch.mbs, mbs, len);
 458                 srch.mbs_len = len;
 459                 cm = avl_find(&cmap_mbs, &srch, NULL);
 460                 if (cm != NULL)
 461                         break;
 462                 len--;
 463         }
 464 
 465         return (cm);
 466 }
 467 
 468 /*
 469  * Return true if this sequence matches the initial part
 470  * of any sequence known in this charmap.
 471  */
 472 static boolean_t
 473 find_mbs_partial(const char *mbs, int len)
 474 {
 475         charmap_t srch = {0};
 476         charmap_t *cm;
 477         avl_index_t where;
 478 
 479         (void) memcpy(srch.mbs, mbs, len);
 480         srch.mbs_len = len;
 481         cm = avl_find(&cmap_mbs, &srch, &where);
 482         if (cm != NULL) {
 483                 /* full match - not expected, but OK */
 484                 return (B_TRUE);
 485         }
 486         cm = avl_nearest(&cmap_mbs, where, AVL_AFTER);
 487         if (cm != NULL && 0 == memcmp(cm->mbs, mbs, len))
 488                 return (B_TRUE);
 489 
 490         return (B_FALSE);
 491 }
 492 
 493 /*
 494  * Do like iconv(3), but with charmaps.
 495  */
 496 size_t
 497 cm_iconv(const char **iptr, size_t *ileft, char **optr, size_t *oleft)
 498 {
 499         charmap_t *cm;
 500         int mbs_len;
 501 
 502         /* Ignore state reset requests. */
 503         if (iptr == NULL || *iptr == NULL)
 504                 return (0);
 505 
 506         if (*oleft < MB_LEN_MAX) {
 507                 errno = E2BIG;
 508                 return ((size_t)-1);
 509         }
 510 
 511         while (*ileft > 0 && *oleft >= MB_LEN_MAX) {
 512                 mbs_len = MB_LEN_MAX;
 513                 if (mbs_len > *ileft)
 514                         mbs_len = *ileft;
 515                 cm = find_mbs(*iptr, mbs_len);
 516                 if (cm == NULL) {
 517                         if (mbs_len < MB_LEN_MAX &&
 518                             find_mbs_partial(*iptr, mbs_len)) {
 519                                 /* incomplete sequence */
 520                                 errno = EINVAL;
 521                         } else {
 522                                 errno = EILSEQ;
 523                         }
 524                         return ((size_t)-1);
 525                 }
 526                 assert(cm->mbs_len > 0);
 527                 if (cm->tombs_len == 0) {
 528                         if (sflag == 0 && cm->warned == 0) {
 529                                 cm->warned = 1;
 530                                 fprintf(stderr, gettext(
 531                                     "To-map does not encode <%s>\n"),
 532                                     cm->name);
 533                         }
 534                         if (cflag == 0) {
 535                                 errno = EILSEQ;
 536                                 return ((size_t)-1);
 537                         }
 538                         /* just skip this input seq. */
 539                         *iptr  += cm->mbs_len;
 540                         *ileft -= cm->mbs_len;
 541                         continue;
 542                 }
 543 
 544                 *iptr  += cm->mbs_len;
 545                 *ileft -= cm->mbs_len;
 546                 (void) memcpy(*optr, cm->tombs, cm->tombs_len);
 547                 *optr  += cm->tombs_len;
 548                 *oleft -= cm->tombs_len;
 549         }
 550 
 551         return (0);
 552 }