1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "apu.h"
18#include "apu_config.h"
19#include "apr_lib.h"
20#include "apr_strings.h"
21#include "apr_portable.h"
22#include "apr_xlate.h"
23
24/* If no implementation is available, don't generate code here since
25 * apr_xlate.h emitted macros which return APR_ENOTIMPL.
26 */
27
28#if APR_HAS_XLATE
29
30#ifdef HAVE_STDDEF_H
31#include <stddef.h> /* for NULL */
32#endif
33#if APR_HAVE_STRING_H
34#include <string.h>
35#endif
36#if APR_HAVE_STRINGS_H
37#include <strings.h>
38#endif
39#ifdef HAVE_ICONV_H
40#include <iconv.h>
41#endif
42#if APU_HAVE_APR_ICONV
43#include <apr_iconv.h>
44#endif
45
46#if defined(APU_ICONV_INBUF_CONST) || APU_HAVE_APR_ICONV
47#define ICONV_INBUF_TYPE const char **
48#else
49#define ICONV_INBUF_TYPE char **
50#endif
51
52#ifndef min
53#define min(x,y) ((x) <= (y) ? (x) : (y))
54#endif
55
56struct apr_xlate_t {
57    apr_pool_t *pool;
58    char *frompage;
59    char *topage;
60    char *sbcs_table;
61#if APU_HAVE_ICONV
62    iconv_t ich;
63#elif APU_HAVE_APR_ICONV
64    apr_iconv_t ich;
65#endif
66};
67
68
69static const char *handle_special_names(const char *page, apr_pool_t *pool)
70{
71    if (page == APR_DEFAULT_CHARSET) {
72        return apr_os_default_encoding(pool);
73    }
74    else if (page == APR_LOCALE_CHARSET) {
75        return apr_os_locale_encoding(pool);
76    }
77    else {
78        return page;
79    }
80}
81
82static apr_status_t apr_xlate_cleanup(void *convset)
83{
84    apr_xlate_t *old = convset;
85
86#if APU_HAVE_APR_ICONV
87    if (old->ich != (apr_iconv_t)-1) {
88        return apr_iconv_close(old->ich, old->pool);
89    }
90
91#elif APU_HAVE_ICONV
92    if (old->ich != (iconv_t)-1) {
93        if (iconv_close(old->ich)) {
94            int rv = errno;
95
96            /* Sometimes, iconv is not good about setting errno. */
97            return rv ? rv : APR_EINVAL;
98        }
99    }
100#endif
101
102    return APR_SUCCESS;
103}
104
105#if APU_HAVE_ICONV
106static void check_sbcs(apr_xlate_t *convset)
107{
108    char inbuf[256], outbuf[256];
109    char *inbufptr = inbuf;
110    char *outbufptr = outbuf;
111    apr_size_t inbytes_left, outbytes_left;
112    int i;
113    apr_size_t translated;
114
115    for (i = 0; i < sizeof(inbuf); i++) {
116        inbuf[i] = i;
117    }
118
119    inbytes_left = outbytes_left = sizeof(inbuf);
120    translated = iconv(convset->ich, (ICONV_INBUF_TYPE)&inbufptr,
121                       &inbytes_left, &outbufptr, &outbytes_left);
122
123    if (translated != (apr_size_t)-1
124        && inbytes_left == 0
125        && outbytes_left == 0) {
126        /* hurray... this is simple translation; save the table,
127         * close the iconv descriptor
128         */
129
130        convset->sbcs_table = apr_palloc(convset->pool, sizeof(outbuf));
131        memcpy(convset->sbcs_table, outbuf, sizeof(outbuf));
132        iconv_close(convset->ich);
133        convset->ich = (iconv_t)-1;
134
135        /* TODO: add the table to the cache */
136    }
137    else {
138        /* reset the iconv descriptor, since it's now in an undefined
139         * state. */
140        iconv_close(convset->ich);
141        convset->ich = iconv_open(convset->topage, convset->frompage);
142    }
143}
144#elif APU_HAVE_APR_ICONV
145static void check_sbcs(apr_xlate_t *convset)
146{
147    char inbuf[256], outbuf[256];
148    char *inbufptr = inbuf;
149    char *outbufptr = outbuf;
150    apr_size_t inbytes_left, outbytes_left;
151    int i;
152    apr_size_t translated;
153    apr_status_t rv;
154
155    for (i = 0; i < sizeof(inbuf); i++) {
156        inbuf[i] = i;
157    }
158
159    inbytes_left = outbytes_left = sizeof(inbuf);
160    rv = apr_iconv(convset->ich, (ICONV_INBUF_TYPE)&inbufptr,
161                   &inbytes_left, &outbufptr, &outbytes_left,
162                   &translated);
163
164    if ((rv == APR_SUCCESS)
165        && (translated != (apr_size_t)-1)
166        && inbytes_left == 0
167        && outbytes_left == 0) {
168        /* hurray... this is simple translation; save the table,
169         * close the iconv descriptor
170         */
171
172        convset->sbcs_table = apr_palloc(convset->pool, sizeof(outbuf));
173        memcpy(convset->sbcs_table, outbuf, sizeof(outbuf));
174        apr_iconv_close(convset->ich, convset->pool);
175        convset->ich = (apr_iconv_t)-1;
176
177        /* TODO: add the table to the cache */
178    }
179    else {
180        /* reset the iconv descriptor, since it's now in an undefined
181         * state. */
182        apr_iconv_close(convset->ich, convset->pool);
183        rv = apr_iconv_open(convset->topage, convset->frompage,
184                            convset->pool, &convset->ich);
185    }
186}
187#endif /* APU_HAVE_APR_ICONV */
188
189static void make_identity_table(apr_xlate_t *convset)
190{
191  int i;
192
193  convset->sbcs_table = apr_palloc(convset->pool, 256);
194  for (i = 0; i < 256; i++)
195      convset->sbcs_table[i] = i;
196}
197
198APU_DECLARE(apr_status_t) apr_xlate_open(apr_xlate_t **convset,
199                                         const char *topage,
200                                         const char *frompage,
201                                         apr_pool_t *pool)
202{
203    apr_status_t rv;
204    apr_xlate_t *new;
205    int found = 0;
206
207    *convset = NULL;
208
209    topage = handle_special_names(topage, pool);
210    frompage = handle_special_names(frompage, pool);
211
212    new = (apr_xlate_t *)apr_pcalloc(pool, sizeof(apr_xlate_t));
213    if (!new) {
214        return APR_ENOMEM;
215    }
216
217    new->pool = pool;
218    new->topage = apr_pstrdup(pool, topage);
219    new->frompage = apr_pstrdup(pool, frompage);
220    if (!new->topage || !new->frompage) {
221        return APR_ENOMEM;
222    }
223
224#ifdef TODO
225    /* search cache of codepage pairs; we may be able to avoid the
226     * expensive iconv_open()
227     */
228
229    set found to non-zero if found in the cache
230#endif
231
232    if ((! found) && (strcmp(topage, frompage) == 0)) {
233        /* to and from are the same */
234        found = 1;
235        make_identity_table(new);
236    }
237
238#if APU_HAVE_APR_ICONV
239    if (!found) {
240        rv = apr_iconv_open(topage, frompage, pool, &new->ich);
241        if (rv != APR_SUCCESS) {
242            return rv;
243        }
244        found = 1;
245        check_sbcs(new);
246    } else
247        new->ich = (apr_iconv_t)-1;
248
249#elif APU_HAVE_ICONV
250    if (!found) {
251        new->ich = iconv_open(topage, frompage);
252        if (new->ich == (iconv_t)-1) {
253            int rv = errno;
254            /* Sometimes, iconv is not good about setting errno. */
255            return rv ? rv : APR_EINVAL;
256        }
257        found = 1;
258        check_sbcs(new);
259    } else
260        new->ich = (iconv_t)-1;
261#endif /* APU_HAVE_ICONV */
262
263    if (found) {
264        *convset = new;
265        apr_pool_cleanup_register(pool, (void *)new, apr_xlate_cleanup,
266                            apr_pool_cleanup_null);
267        rv = APR_SUCCESS;
268    }
269    else {
270        rv = APR_EINVAL; /* iconv() would return EINVAL if it
271                                couldn't handle the pair */
272    }
273
274    return rv;
275}
276
277APU_DECLARE(apr_status_t) apr_xlate_sb_get(apr_xlate_t *convset, int *onoff)
278{
279    *onoff = convset->sbcs_table != NULL;
280    return APR_SUCCESS;
281}
282
283APU_DECLARE(apr_status_t) apr_xlate_conv_buffer(apr_xlate_t *convset,
284                                                const char *inbuf,
285                                                apr_size_t *inbytes_left,
286                                                char *outbuf,
287                                                apr_size_t *outbytes_left)
288{
289    apr_status_t status = APR_SUCCESS;
290
291#if APU_HAVE_APR_ICONV
292    if (convset->ich != (apr_iconv_t)-1) {
293        const char *inbufptr = inbuf;
294        apr_size_t translated;
295        char *outbufptr = outbuf;
296        status = apr_iconv(convset->ich, &inbufptr, inbytes_left,
297                           &outbufptr, outbytes_left, &translated);
298
299        /* If everything went fine but we ran out of buffer, don't
300         * report it as an error.  Caller needs to look at the two
301         * bytes-left values anyway.
302         *
303         * There are three expected cases where rc is -1.  In each of
304         * these cases, *inbytes_left != 0.
305         * a) the non-error condition where we ran out of output
306         *    buffer
307         * b) the non-error condition where we ran out of input (i.e.,
308         *    the last input character is incomplete)
309         * c) the error condition where the input is invalid
310         */
311        switch (status) {
312
313            case APR_BADARG:  /* out of space on output */
314                status = 0; /* change table lookup code below if you
315                               make this an error */
316                break;
317
318            case APR_EINVAL: /* input character not complete (yet) */
319                status = APR_INCOMPLETE;
320                break;
321
322            case APR_BADCH: /* bad input byte */
323                status = APR_EINVAL;
324                break;
325
326             /* Sometimes, iconv is not good about setting errno. */
327            case 0:
328                if (inbytes_left && *inbytes_left)
329                    status = APR_INCOMPLETE;
330                break;
331
332            default:
333                break;
334        }
335    }
336    else
337
338#elif APU_HAVE_ICONV
339    if (convset->ich != (iconv_t)-1) {
340        const char *inbufptr = inbuf;
341        char *outbufptr = outbuf;
342        apr_size_t translated;
343        translated = iconv(convset->ich, (ICONV_INBUF_TYPE)&inbufptr,
344                           inbytes_left, &outbufptr, outbytes_left);
345
346        /* If everything went fine but we ran out of buffer, don't
347         * report it as an error.  Caller needs to look at the two
348         * bytes-left values anyway.
349         *
350         * There are three expected cases where rc is -1.  In each of
351         * these cases, *inbytes_left != 0.
352         * a) the non-error condition where we ran out of output
353         *    buffer
354         * b) the non-error condition where we ran out of input (i.e.,
355         *    the last input character is incomplete)
356         * c) the error condition where the input is invalid
357         */
358        if (translated == (apr_size_t)-1) {
359            int rv = errno;
360            switch (rv) {
361
362            case E2BIG:  /* out of space on output */
363                status = 0; /* change table lookup code below if you
364                               make this an error */
365                break;
366
367            case EINVAL: /* input character not complete (yet) */
368                status = APR_INCOMPLETE;
369                break;
370
371            case EILSEQ: /* bad input byte */
372                status = APR_EINVAL;
373                break;
374
375             /* Sometimes, iconv is not good about setting errno. */
376            case 0:
377                status = APR_INCOMPLETE;
378                break;
379
380            default:
381                status = rv;
382                break;
383            }
384        }
385    }
386    else
387#endif
388
389    if (inbuf) {
390        apr_size_t to_convert = min(*inbytes_left, *outbytes_left);
391        apr_size_t converted = to_convert;
392        char *table = convset->sbcs_table;
393
394        while (to_convert) {
395            *outbuf = table[(unsigned char)*inbuf];
396            ++outbuf;
397            ++inbuf;
398            --to_convert;
399        }
400        *inbytes_left -= converted;
401        *outbytes_left -= converted;
402    }
403
404    return status;
405}
406
407APU_DECLARE(apr_int32_t) apr_xlate_conv_byte(apr_xlate_t *convset,
408                                             unsigned char inchar)
409{
410    if (convset->sbcs_table) {
411        return convset->sbcs_table[inchar];
412    }
413    else {
414        return -1;
415    }
416}
417
418APU_DECLARE(apr_status_t) apr_xlate_close(apr_xlate_t *convset)
419{
420    return apr_pool_cleanup_run(convset->pool, convset, apr_xlate_cleanup);
421}
422
423#else /* !APR_HAS_XLATE */
424
425APU_DECLARE(apr_status_t) apr_xlate_open(apr_xlate_t **convset,
426                                         const char *topage,
427                                         const char *frompage,
428                                         apr_pool_t *pool)
429{
430    return APR_ENOTIMPL;
431}
432
433APU_DECLARE(apr_status_t) apr_xlate_sb_get(apr_xlate_t *convset, int *onoff)
434{
435    return APR_ENOTIMPL;
436}
437
438APU_DECLARE(apr_int32_t) apr_xlate_conv_byte(apr_xlate_t *convset,
439                                             unsigned char inchar)
440{
441    return (-1);
442}
443
444APU_DECLARE(apr_status_t) apr_xlate_conv_buffer(apr_xlate_t *convset,
445                                                const char *inbuf,
446                                                apr_size_t *inbytes_left,
447                                                char *outbuf,
448                                                apr_size_t *outbytes_left)
449{
450    return APR_ENOTIMPL;
451}
452
453APU_DECLARE(apr_status_t) apr_xlate_close(apr_xlate_t *convset)
454{
455    return APR_ENOTIMPL;
456}
457
458#endif /* APR_HAS_XLATE */
459