1 /* This code was written by Dino Leonardo Sangoi <SANGOID@lloydadriatico.it> */
2 
3 #include "config.h"
4 
5 #include <stdio.h>
6 #include <slang.h>
7 #include <string.h>
8 #include <iconv.h>
9 #include <errno.h>
10 
11 SLANG_MODULE(iconv);
12 
13 #ifndef ICONV_CONST
14 # define ICONV_CONST
15 #endif
16 
17 static int ICONV_Type_Id = 0;
18 
19 typedef struct
20 {
21    iconv_t cd;
22 }
23 ICONV_Type;
24 
free_iconv_type(ICONV_Type * it)25 static void free_iconv_type (ICONV_Type *it)
26 {
27    SLfree ((char *) it);
28 }
29 
allocate_iconv_type(iconv_t cd)30 static SLang_MMT_Type *allocate_iconv_type (iconv_t cd)
31 {
32    ICONV_Type *it;
33    SLang_MMT_Type *mmt;
34 
35    it = (ICONV_Type *) SLmalloc (sizeof (ICONV_Type));
36    if (it == NULL)
37      return NULL;
38 
39    it->cd = cd;
40 
41    if (NULL == (mmt = SLang_create_mmt (ICONV_Type_Id, (VOID_STAR) it)))
42      {
43 	free_iconv_type (it);
44 	return NULL;
45      }
46    return mmt;
47 }
48 
_iconv_open(char * tocode,char * fromcode)49 static void _iconv_open(char *tocode, char *fromcode)
50 {
51    iconv_t cd;
52    SLang_MMT_Type *mmt;
53 
54    cd = iconv_open(tocode, fromcode);
55    if (cd == (iconv_t)(-1))
56      {
57 	SLang_verror (SL_INTRINSIC_ERROR, "Error preparing iconv to convert from '%s' to '%s'.", fromcode, tocode);
58 	return;
59      }
60 
61    if (NULL == (mmt = allocate_iconv_type (cd)))
62      {
63 	iconv_close(cd);
64 	return;
65      }
66 
67    if (-1 == SLang_push_mmt (mmt))
68      {
69 	SLang_free_mmt (mmt);
70 	return;
71      }
72    return;
73 }
74 
_iconv_close(ICONV_Type * it)75 static void _iconv_close(ICONV_Type *it)
76 {
77    if (it->cd != (iconv_t)-1)
78      {
79 	iconv_close(it->cd);
80 	it->cd = (iconv_t)-1;
81      }
82 }
83 
destroy_iconv(SLtype type,VOID_STAR f)84 static void destroy_iconv (SLtype type, VOID_STAR f)
85 {
86    ICONV_Type *it;
87    (void) type;
88 
89    it = (ICONV_Type *) f;
90    _iconv_close(it);
91    free_iconv_type (it);
92 }
93 
_iconv_reset(ICONV_Type * it)94 static void _iconv_reset(ICONV_Type *it)
95 {
96    iconv(it->cd, NULL, NULL, NULL, NULL);
97 }
98 
99 #define SHIFT_BUF_LEN 64U
_iconv_reset_shift(ICONV_Type * it)100 static void _iconv_reset_shift(ICONV_Type *it)
101 {
102    size_t n = SHIFT_BUF_LEN;
103    char buf[SHIFT_BUF_LEN], *p = buf;
104    SLang_BString_Type *bstr;
105    size_t rc;
106 
107    rc = iconv(it->cd, NULL, NULL, &p, &n);
108    if (rc == (size_t)(-1))
109      {
110 	/* Since no input was provided, errno must be E2BIG */
111 	SLang_verror (SL_Internal_Error, "Internal error: shift buffer too small in iconv_reset_shift!");
112 	return;
113      }
114    buf[SHIFT_BUF_LEN-n] = '\0';
115    bstr = SLbstring_create((unsigned char *)buf, SHIFT_BUF_LEN-n);
116    if (bstr == NULL)
117      return;
118 
119    (void) SLang_push_bstring(bstr);
120    SLbstring_free (bstr);
121 }
122 
_iconv(ICONV_Type * it,SLang_BString_Type * bstr)123 static void _iconv(ICONV_Type *it, SLang_BString_Type *bstr)
124 {
125    char *buf = NULL;
126    size_t rc;
127    char ICONV_CONST *instr;
128    char *outstr;
129    size_t inn, outn, bufn;
130    size_t fail = (size_t)-1;
131    SLstrlen_Type bstrlen;
132 
133    if (NULL == (instr = (char ICONV_CONST *)SLbstring_get_pointer(bstr, &bstrlen)))
134      return;
135    inn = (size_t) bstrlen;
136 
137    bufn = outn = 2*inn + 2;
138    if (NULL == (buf = (char *)SLmalloc(bufn)))
139      return;
140 
141    outstr = buf;
142 
143    while (1)
144      {
145 	errno = 0;
146 	rc = iconv(it->cd, &instr, &inn, &outstr, &outn);
147 	if (rc != (size_t)-1)
148 	  break; /* ok! */
149 
150 	if (fail == inn)
151 	  {
152 	     SLang_verror (SL_Unknown_Error, "Unknown error in iconv");
153 	     goto error;
154 	  }
155 
156 	fail = inn;
157 	switch (errno)
158 	  {
159 	   case EILSEQ:
160 	     SLang_verror (SL_InvalidUTF8_Error, "Invalid multibyte sequence or unable to convert to the target encoding");
161 	     goto error;
162 	   case EINVAL:
163 	     SLang_verror (SL_InvalidUTF8_Error, "Incomplete multibyte sequence");
164 	     goto error;
165 	   case 0:
166 	     /* drop */
167 	     /* grrrr
168 	      * At least on windows, libiconv returns with errno = 0
169 	      * (or unmodified?) when there's no more room on outstr
170 	      * if so, fallback
171 	      */
172 	   case E2BIG:
173 	       {
174 		  char *p;
175 		  long outdelta;
176 
177 		  outdelta = outstr - buf;
178 		  outn += bufn;
179 		  bufn += bufn;
180 		  p = (char *)SLrealloc(buf, bufn);
181 		  if (p == NULL)
182 		    goto error;
183 		  buf = p;
184 		  outstr = buf + outdelta;
185 	       }
186 	     break;
187 	   default:
188 	     SLang_verror (SL_Unknown_Error, "Unknown iconv error");
189 	     goto error;
190 	  }
191      }
192 
193    bstr = SLbstring_create((unsigned char *) buf, outstr - buf);
194    if (bstr != NULL)
195      (void) SLang_push_bstring(bstr);
196    SLbstring_free (bstr);
197    /* drop */
198 error:
199    SLfree(buf);
200 }
201 
202 #define DUMMY_ICONV_TYPE 255
203 #define P DUMMY_ICONV_TYPE
204 #define V SLANG_VOID_TYPE
205 #define S SLANG_STRING_TYPE
206 #define B SLANG_BSTRING_TYPE
207 static SLang_Intrin_Fun_Type ICONV_Intrinsics [] =
208 {
209    MAKE_INTRINSIC_2("iconv_open", _iconv_open, V, S, S),
210    MAKE_INTRINSIC_1("iconv_close", _iconv_close, V, P),
211    MAKE_INTRINSIC_1("iconv_reset", _iconv_reset, V, P),
212    MAKE_INTRINSIC_1("iconv_reset_shift", _iconv_reset_shift, V, P),
213    MAKE_INTRINSIC_2("iconv", _iconv, V, P, B),
214    SLANG_END_INTRIN_FUN_TABLE
215 };
216 
217 #undef B
218 #undef S
219 #undef V
220 #undef P
221 
register_iconv_type(void)222 static int register_iconv_type (void)
223 {
224    SLang_Class_Type *cl;
225 
226    if (ICONV_Type_Id != 0)
227      return 0;
228 
229    if (NULL == (cl = SLclass_allocate_class ("ICONV_Type")))
230      return -1;
231 
232    if (-1 == SLclass_set_destroy_function (cl, destroy_iconv))
233      return -1;
234 
235    /* By registering as SLANG_VOID_TYPE, slang will dynamically allocate a
236     * type.
237     */
238    if (-1 == SLclass_register_class (cl, SLANG_VOID_TYPE, sizeof (ICONV_Type), SLANG_CLASS_TYPE_MMT))
239      return -1;
240 
241    ICONV_Type_Id = SLclass_get_class_id (cl);
242    if (-1 == SLclass_patch_intrin_fun_table1 (ICONV_Intrinsics, DUMMY_ICONV_TYPE, ICONV_Type_Id))
243      return -1;
244 
245    return 0;
246 }
247 
init_iconv_module_ns(char * ns_name)248 int init_iconv_module_ns (char *ns_name)
249 {
250    SLang_NameSpace_Type *ns = SLns_create_namespace (ns_name);
251    if (ns == NULL)
252      return -1;
253 
254    if (-1 == register_iconv_type ())
255      return -1;
256 
257    if (-1 == SLns_add_intrin_fun_table (ns, ICONV_Intrinsics, "__ICONV__"))
258      return -1;
259 
260    return 0;
261 }
262 
263 /* This function is optional */
deinit_iconv_module(void)264 void deinit_iconv_module (void)
265 {
266 }
267