1 /* $NetBSD: citrus_iconv_std.c,v 1.18 2026/07/05 20:41:23 riastradh Exp $ */ 2 3 /*- 4 * Copyright (c)2003 Citrus Project, 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 #if defined(LIBC_SCCS) && !defined(lint) 31 __RCSID("$NetBSD: citrus_iconv_std.c,v 1.18 2026/07/05 20:41:23 riastradh Exp $"); 32 #endif /* LIBC_SCCS and not lint */ 33 34 #include <sys/param.h> 35 36 #include <sys/queue.h> 37 38 #include <assert.h> 39 #include <endian.h> 40 #include <errno.h> 41 #include <limits.h> 42 #include <stdalign.h> 43 #include <stdio.h> 44 #include <stdlib.h> 45 #include <string.h> 46 #include <wchar.h> 47 48 #include "citrus_namespace.h" 49 #include "citrus_types.h" 50 #include "citrus_module.h" 51 #include "citrus_region.h" 52 #include "citrus_mmap.h" 53 #include "citrus_hash.h" 54 #include "citrus_iconv.h" 55 #include "citrus_stdenc.h" 56 #include "citrus_mapper.h" 57 #include "citrus_csmapper.h" 58 #include "citrus_memstream.h" 59 #include "citrus_iconv_std.h" 60 #include "citrus_esdb.h" 61 62 /* ---------------------------------------------------------------------- */ 63 64 _CITRUS_ICONV_DECLS(iconv_std); 65 _CITRUS_ICONV_DEF_OPS(iconv_std); 66 67 68 /* ---------------------------------------------------------------------- */ 69 70 int 71 _citrus_iconv_std_iconv_getops(struct _citrus_iconv_ops *ops, size_t lenops, 72 u_int32_t expected_version) 73 { 74 if (expected_version<_CITRUS_ICONV_ABI_VERSION || lenops<sizeof(*ops)) 75 return (EINVAL); 76 77 memcpy(ops, &_citrus_iconv_std_iconv_ops, 78 sizeof(_citrus_iconv_std_iconv_ops)); 79 80 return (0); 81 } 82 83 /* ---------------------------------------------------------------------- */ 84 85 /* 86 * convenience routines for stdenc. 87 */ 88 static __inline void 89 save_encoding_state(struct _citrus_iconv_std_encoding *se) 90 { 91 if (se->se_ps) 92 memcpy(se->se_pssaved, se->se_ps, 93 _stdenc_get_state_size(se->se_handle)); 94 } 95 96 static __inline void 97 restore_encoding_state(struct _citrus_iconv_std_encoding *se) 98 { 99 if (se->se_ps) 100 memcpy(se->se_ps, se->se_pssaved, 101 _stdenc_get_state_size(se->se_handle)); 102 } 103 104 static __inline void 105 init_encoding_state(struct _citrus_iconv_std_encoding *se) 106 { 107 if (se->se_ps) 108 _stdenc_init_state(se->se_handle, se->se_ps); 109 } 110 111 static __inline int 112 mbtocsx(struct _citrus_iconv_std_encoding *se, 113 _csid_t *csid, _index_t *idx, const char **s, size_t n, 114 size_t *nresult) 115 { 116 return _stdenc_mbtocs(se->se_handle, csid, idx, s, n, se->se_ps, 117 nresult); 118 } 119 120 static __inline int 121 cstombx(struct _citrus_iconv_std_encoding *se, 122 char *s, size_t n, _csid_t csid, _index_t idx, size_t *nresult) 123 { 124 return _stdenc_cstomb(se->se_handle, s, n, csid, idx, se->se_ps, 125 nresult); 126 } 127 128 static __inline int 129 wctombx(struct _citrus_iconv_std_encoding *se, 130 char *s, size_t n, _wc_t wc, size_t *nresult) 131 { 132 return _stdenc_wctomb(se->se_handle, s, n, wc, se->se_ps, nresult); 133 } 134 135 static __inline int 136 put_state_resetx(struct _citrus_iconv_std_encoding *se, 137 char *s, size_t n, size_t *nresult) 138 { 139 return _stdenc_put_state_reset(se->se_handle, s, n, se->se_ps, nresult); 140 } 141 142 static __inline int 143 get_state_desc_gen(struct _citrus_iconv_std_encoding *se, int *rstate) 144 { 145 int ret; 146 struct _stdenc_state_desc ssd; 147 148 ret = _stdenc_get_state_desc(se->se_handle, se->se_ps, 149 _STDENC_SDID_GENERIC, &ssd); 150 if (!ret) 151 *rstate = ssd.u.generic.state; 152 153 return ret; 154 } 155 156 /* 157 * init encoding context 158 */ 159 static int 160 init_encoding(struct _citrus_iconv_std_encoding *se, struct _stdenc *cs, 161 void *ps1, void *ps2) 162 { 163 int ret = -1; 164 165 _DIAGASSERT((uintptr_t)ps1 % alignof(mbstate_t) == 0); 166 _DIAGASSERT((uintptr_t)ps2 % alignof(mbstate_t) == 0); 167 168 se->se_handle = cs; 169 se->se_ps = ps1; 170 se->se_pssaved = ps2; 171 172 if (se->se_ps) 173 ret = _stdenc_init_state(cs, se->se_ps); 174 if (!ret && se->se_pssaved) 175 ret = _stdenc_init_state(cs, se->se_pssaved); 176 177 return ret; 178 } 179 180 static int 181 open_csmapper(struct _csmapper **rcm, const char *src, const char *dst, 182 unsigned long *rnorm) 183 { 184 int ret; 185 struct _csmapper *cm; 186 187 ret = _csmapper_open(&cm, src, dst, 0, rnorm); 188 if (ret) 189 return ret; 190 if (_csmapper_get_src_max(cm) != 1 || _csmapper_get_dst_max(cm) != 1 || 191 _csmapper_get_state_size(cm) != 0) { 192 _csmapper_close(cm); 193 return EINVAL; 194 } 195 196 *rcm = cm; 197 198 return 0; 199 } 200 201 static void 202 close_dsts(struct _citrus_iconv_std_dst_list *dl) 203 { 204 struct _citrus_iconv_std_dst *sd; 205 206 while ((sd=TAILQ_FIRST(dl)) != NULL) { 207 TAILQ_REMOVE(dl, sd, sd_entry); 208 _csmapper_close(sd->sd_mapper); 209 free(sd); 210 } 211 } 212 213 static int 214 open_dsts(struct _citrus_iconv_std_dst_list *dl, 215 const struct _esdb_charset *ec, const struct _esdb *dbdst) 216 { 217 int i, ret; 218 struct _citrus_iconv_std_dst *sd, *sdtmp; 219 unsigned long norm; 220 221 sd = malloc(sizeof(*sd)); 222 if (sd == NULL) 223 return errno; 224 225 for (i=0; i<dbdst->db_num_charsets; i++) { 226 ret = open_csmapper(&sd->sd_mapper, ec->ec_csname, 227 dbdst->db_charsets[i].ec_csname, &norm); 228 if (ret == 0) { 229 sd->sd_csid = dbdst->db_charsets[i].ec_csid; 230 sd->sd_norm = norm; 231 /* insert this mapper by sorted order. */ 232 TAILQ_FOREACH(sdtmp, dl, sd_entry) { 233 if (sdtmp->sd_norm > norm) { 234 TAILQ_INSERT_BEFORE(sdtmp, sd, 235 sd_entry); 236 sd = NULL; 237 break; 238 } 239 } 240 if (sd) 241 TAILQ_INSERT_TAIL(dl, sd, sd_entry); 242 sd = malloc(sizeof(*sd)); 243 if (sd == NULL) { 244 ret = errno; 245 close_dsts(dl); 246 return ret; 247 } 248 } else if (ret != ENOENT) { 249 close_dsts(dl); 250 free(sd); 251 return ret; 252 } 253 } 254 free(sd); 255 return 0; 256 } 257 258 static void 259 close_srcs(struct _citrus_iconv_std_src_list *sl) 260 { 261 struct _citrus_iconv_std_src *ss; 262 263 while ((ss=TAILQ_FIRST(sl)) != NULL) { 264 TAILQ_REMOVE(sl, ss, ss_entry); 265 close_dsts(&ss->ss_dsts); 266 free(ss); 267 } 268 } 269 270 static int 271 open_srcs(struct _citrus_iconv_std_src_list *sl, 272 const struct _esdb *dbsrc, const struct _esdb *dbdst) 273 { 274 int i, ret, count = 0; 275 struct _citrus_iconv_std_src *ss; 276 277 ss = malloc(sizeof(*ss)); 278 if (ss == NULL) 279 return errno; 280 281 TAILQ_INIT(&ss->ss_dsts); 282 283 for (i=0; i<dbsrc->db_num_charsets; i++) { 284 ret = open_dsts(&ss->ss_dsts, &dbsrc->db_charsets[i], dbdst); 285 if (ret) 286 goto err; 287 if (!TAILQ_EMPTY(&ss->ss_dsts)) { 288 ss->ss_csid = dbsrc->db_charsets[i].ec_csid; 289 TAILQ_INSERT_TAIL(sl, ss, ss_entry); 290 ss = malloc(sizeof(*ss)); 291 if (ss == NULL) { 292 ret = errno; 293 goto err; 294 } 295 count++; 296 TAILQ_INIT(&ss->ss_dsts); 297 } 298 } 299 free(ss); 300 301 return count ? 0 : ENOENT; 302 303 err: 304 free(ss); 305 close_srcs(sl); 306 return ret; 307 } 308 309 /* do convert a character */ 310 #define E_NO_CORRESPONDING_CHAR ENOENT /* XXX */ 311 static int 312 /*ARGSUSED*/ 313 do_conv(const struct _citrus_iconv_std_shared *is, 314 struct _citrus_iconv_std_context *sc, _csid_t *csid, _index_t *idx) 315 { 316 _index_t tmpidx; 317 int ret; 318 struct _citrus_iconv_std_src *ss; 319 struct _citrus_iconv_std_dst *sd; 320 321 TAILQ_FOREACH(ss, &is->is_srcs, ss_entry) { 322 if (ss->ss_csid == *csid) { 323 TAILQ_FOREACH(sd, &ss->ss_dsts, sd_entry) { 324 ret = _csmapper_convert(sd->sd_mapper, 325 &tmpidx, *idx, NULL); 326 switch (ret) { 327 case _MAPPER_CONVERT_SUCCESS: 328 *csid = sd->sd_csid; 329 *idx = tmpidx; 330 return 0; 331 case _MAPPER_CONVERT_NONIDENTICAL: 332 break; 333 case _MAPPER_CONVERT_SRC_MORE: 334 /*FALLTHROUGH*/ 335 case _MAPPER_CONVERT_DST_MORE: 336 /*FALLTHROUGH*/ 337 case _MAPPER_CONVERT_FATAL: 338 return EINVAL; 339 case _MAPPER_CONVERT_ILSEQ: 340 return EILSEQ; 341 } 342 } 343 break; 344 } 345 } 346 347 return E_NO_CORRESPONDING_CHAR; 348 } 349 /* ---------------------------------------------------------------------- */ 350 351 static int 352 /*ARGSUSED*/ 353 _citrus_iconv_std_iconv_init_shared(struct _citrus_iconv_shared *ci, 354 const char * __restrict curdir, 355 const char * __restrict src, 356 const char * __restrict dst, 357 const void * __restrict var, size_t lenvar) 358 { 359 int ret; 360 struct _citrus_iconv_std_shared *is; 361 struct _citrus_esdb esdbsrc, esdbdst; 362 363 is = malloc(sizeof(*is)); 364 if (is==NULL) { 365 ret = errno; 366 goto err0; 367 } 368 ret = _citrus_esdb_open(&esdbsrc, src); 369 if (ret) 370 goto err1; 371 ret = _citrus_esdb_open(&esdbdst, dst); 372 if (ret) 373 goto err2; 374 ret = _stdenc_open(&is->is_src_encoding, esdbsrc.db_encname, 375 esdbsrc.db_variable, esdbsrc.db_len_variable); 376 if (ret) 377 goto err3; 378 ret = _stdenc_open(&is->is_dst_encoding, esdbdst.db_encname, 379 esdbdst.db_variable, esdbdst.db_len_variable); 380 if (ret) 381 goto err4; 382 is->is_use_invalid = esdbdst.db_use_invalid; 383 is->is_invalid = esdbdst.db_invalid; 384 385 TAILQ_INIT(&is->is_srcs); 386 ret = open_srcs(&is->is_srcs, &esdbsrc, &esdbdst); 387 if (ret) 388 goto err5; 389 390 _esdb_close(&esdbsrc); 391 _esdb_close(&esdbdst); 392 ci->ci_closure = is; 393 394 return 0; 395 396 err5: 397 _stdenc_close(is->is_dst_encoding); 398 err4: 399 _stdenc_close(is->is_src_encoding); 400 err3: 401 _esdb_close(&esdbdst); 402 err2: 403 _esdb_close(&esdbsrc); 404 err1: 405 free(is); 406 err0: 407 return ret; 408 } 409 410 static void 411 _citrus_iconv_std_iconv_uninit_shared(struct _citrus_iconv_shared *ci) 412 { 413 struct _citrus_iconv_std_shared *is = ci->ci_closure; 414 415 if (is == NULL) 416 return; 417 418 _stdenc_close(is->is_src_encoding); 419 _stdenc_close(is->is_dst_encoding); 420 close_srcs(&is->is_srcs); 421 free(is); 422 } 423 424 static int 425 _citrus_iconv_std_iconv_init_context(struct _citrus_iconv *cv) 426 { 427 const struct _citrus_iconv_std_shared *is = cv->cv_shared->ci_closure; 428 struct _citrus_iconv_std_context *sc; 429 size_t szpssrc, szpsdst, sz; 430 char *ptr; 431 432 /* 433 * Allocate space for: 434 * 435 * struct { 436 * struct _citrus_iconv_std_context sc; 437 * struct <src_state> src_ps; 438 * struct <src_state> src_pssaved; 439 * struct <dst_state> dst_ps; 440 * struct <dst_state> dst_pssaved; 441 * }; 442 * 443 * Note that `struct <src_state>' and `struct <dst_state>' are 444 * required to be aligned like mbstate_t. 445 */ 446 szpssrc = roundup(_stdenc_get_state_size(is->is_src_encoding), 447 alignof(mbstate_t)); 448 szpsdst = roundup(_stdenc_get_state_size(is->is_dst_encoding), 449 alignof(mbstate_t)); 450 _DIAGASSERT(szpssrc % alignof(mbstate_t) == 0); 451 _DIAGASSERT(szpsdst % alignof(mbstate_t) == 0); 452 453 sz = roundup(sizeof(*sc), alignof(mbstate_t)) + 454 (szpssrc + szpsdst)*2; 455 sc = malloc(sz); 456 if (sc == NULL) 457 return errno; 458 459 ptr = (char *)sc + roundup(sizeof(*sc), alignof(mbstate_t)); 460 _DIAGASSERT((uintptr_t)ptr % alignof(mbstate_t) == 0); 461 if (szpssrc) 462 init_encoding(&sc->sc_src_encoding, is->is_src_encoding, 463 ptr, ptr+szpssrc); 464 else 465 init_encoding(&sc->sc_src_encoding, is->is_src_encoding, 466 NULL, NULL); 467 ptr += szpssrc*2; 468 _DIAGASSERT((uintptr_t)ptr % alignof(mbstate_t) == 0); 469 if (szpsdst) 470 init_encoding(&sc->sc_dst_encoding, is->is_dst_encoding, 471 ptr, ptr+szpsdst); 472 else 473 init_encoding(&sc->sc_dst_encoding, is->is_dst_encoding, 474 NULL, NULL); 475 476 cv->cv_closure = (void *)sc; 477 478 return 0; 479 } 480 481 static void 482 _citrus_iconv_std_iconv_uninit_context(struct _citrus_iconv *cv) 483 { 484 free(cv->cv_closure); 485 } 486 487 static int 488 _citrus_iconv_std_iconv_convert(struct _citrus_iconv * __restrict cv, 489 const char * __restrict * __restrict in, 490 size_t * __restrict inbytes, 491 char * __restrict * __restrict out, 492 size_t * __restrict outbytes, u_int32_t flags, 493 size_t * __restrict invalids) 494 { 495 const struct _citrus_iconv_std_shared *is = cv->cv_shared->ci_closure; 496 struct _citrus_iconv_std_context *sc = cv->cv_closure; 497 _index_t idx; 498 _csid_t csid; 499 int ret, state; 500 size_t szrin, szrout; 501 size_t inval; 502 const char *tmpin; 503 504 inval = 0; 505 if (in==NULL || *in==NULL) { 506 /* special cases */ 507 if (out!=NULL && *out!=NULL) { 508 /* init output state and store the shift sequence */ 509 save_encoding_state(&sc->sc_src_encoding); 510 save_encoding_state(&sc->sc_dst_encoding); 511 szrout = 0; 512 513 ret = put_state_resetx(&sc->sc_dst_encoding, 514 *out, *outbytes, 515 &szrout); 516 if (ret) 517 goto err; 518 519 if (szrout == (size_t)-2) { 520 /* too small to store the character */ 521 ret = EINVAL; 522 goto err; 523 } 524 *out += szrout; 525 *outbytes -= szrout; 526 } else 527 /* otherwise, discard the shift sequence */ 528 init_encoding_state(&sc->sc_dst_encoding); 529 init_encoding_state(&sc->sc_src_encoding); 530 *invalids = 0; 531 return 0; 532 } 533 534 /* normal case */ 535 for (;;) { 536 if (*inbytes==0) { 537 ret = get_state_desc_gen(&sc->sc_src_encoding, &state); 538 if (state == _STDENC_SDGEN_INITIAL || 539 state == _STDENC_SDGEN_STABLE) 540 break; 541 } 542 543 /* save the encoding states for the error recovery */ 544 save_encoding_state(&sc->sc_src_encoding); 545 save_encoding_state(&sc->sc_dst_encoding); 546 547 /* mb -> csid/index */ 548 tmpin = *in; 549 szrin = szrout = 0; 550 ret = mbtocsx(&sc->sc_src_encoding, &csid, &idx, 551 &tmpin, *inbytes, &szrin); 552 if (ret) 553 goto err; 554 555 if (szrin == (size_t)-2) { 556 /* incompleted character */ 557 ret = get_state_desc_gen(&sc->sc_src_encoding, &state); 558 if (ret) { 559 ret = EINVAL; 560 goto err; 561 } 562 switch (state) { 563 case _STDENC_SDGEN_INITIAL: 564 case _STDENC_SDGEN_STABLE: 565 /* fetch shift sequences only. */ 566 goto next; 567 } 568 ret = EINVAL; 569 goto err; 570 } 571 /* convert the character */ 572 ret = do_conv(is, sc, &csid, &idx); 573 if (ret) { 574 if (ret == E_NO_CORRESPONDING_CHAR) { 575 inval++; 576 szrout = 0; 577 if ((flags&_CITRUS_ICONV_F_HIDE_INVALID)==0 && 578 is->is_use_invalid) { 579 ret = wctombx(&sc->sc_dst_encoding, 580 *out, *outbytes, 581 is->is_invalid, 582 &szrout); 583 if (ret) 584 goto err; 585 } 586 goto next; 587 } else { 588 goto err; 589 } 590 } 591 /* csid/index -> mb */ 592 ret = cstombx(&sc->sc_dst_encoding, 593 *out, *outbytes, csid, idx, &szrout); 594 if (ret) 595 goto err; 596 next: 597 _DIAGASSERT(tmpin >= *in); 598 _DIAGASSERT(*inbytes >= tmpin - *in); 599 _DIAGASSERT(*outbytes >= szrout); 600 *inbytes -= tmpin - *in; /* szrin is insufficient on \0. */ 601 *in = tmpin; 602 *outbytes -= szrout; 603 *out += szrout; 604 } 605 *invalids = inval; 606 607 return 0; 608 609 err: 610 restore_encoding_state(&sc->sc_src_encoding); 611 restore_encoding_state(&sc->sc_dst_encoding); 612 err_norestore: 613 *invalids = inval; 614 615 return ret; 616 } 617