1/*
2 *******************************************************************************
3 *
4 *   Copyright (C) 2005-2014, International Business Machines
5 *   Corporation and others.  All Rights Reserved.
6 *
7 *******************************************************************************
8 *
9 *   created on: 2005jun15
10 *   created by: Raymond Yang
11 */
12
13#if !UCONFIG_NO_IDNA
14
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
18#include "unicode/utypes.h"
19#include "unicode/ucnv.h"
20#include "unicode/ustring.h"
21#include "unicode/uidna.h"
22
23#include "idnaconf.h"
24
25static const UChar C_TAG[] = {0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0}; // =====
26static const UChar C_NAMEZONE[] = {0x6E, 0x61, 0x6D, 0x65, 0x7A, 0x6F, 0x6E, 0x65, 0}; // namezone
27static const UChar C_NAMEBASE[] = {0x6E, 0x61, 0x6D, 0x65, 0x62, 0x61, 0x73, 0x65, 0}; // namebase
28
29static const UChar C_TYPE[] = {0x74, 0x79, 0x70, 0x65, 0}; // type
30static const UChar C_TOASCII[]  =  {0x74, 0x6F, 0x61, 0x73, 0x63, 0x69, 0x69, 0};       // toascii
31static const UChar C_TOUNICODE[] = {0x74, 0x6F, 0x75, 0x6E, 0x69, 0x63, 0x6F, 0x64, 0x65, 0}; // tounicode
32
33static const UChar C_PASSFAIL[] = {0x70, 0x61, 0x73, 0x73, 0x66, 0x61, 0x69, 0x6C, 0}; // passfail
34static const UChar C_PASS[] = {0x70, 0x61, 0x73, 0x73, 0}; // pass
35static const UChar C_FAIL[] = {0x66, 0x61, 0x69, 0x6C, 0}; // fail
36
37static const UChar C_DESC[] = {0x64, 0x65, 0x73, 0x63, 0}; // desc
38static const UChar C_USESTD3ASCIIRULES[] = {0x55, 0x73, 0x65, 0x53, 0x54, 0x44,
39       0x33, 0x41, 0x53, 0x43, 0x49, 0x49, 0x52, 0x75, 0x6C, 0x65, 0x73, 0}; // UseSTD3ASCIIRules
40
41IdnaConfTest::IdnaConfTest(){
42    base = NULL;
43    len = 0;
44    curOffset = 0;
45
46    type = option = passfail = -1;
47    namebase.setToBogus();
48    namezone.setToBogus();
49}
50IdnaConfTest::~IdnaConfTest(){
51    delete [] base;
52}
53
54#if !UCONFIG_NO_IDNA
55/* this function is modified from RBBITest::ReadAndConvertFile()
56 *
57 */
58UBool IdnaConfTest::ReadAndConvertFile(){
59
60    char * source = NULL;
61    size_t source_len;
62
63    // read the test data file to memory
64    FILE* f    = NULL;
65    UErrorCode  status  = U_ZERO_ERROR;
66
67    const char *path = IntlTest::getSourceTestData(status);
68    if (U_FAILURE(status)) {
69        errln("%s", u_errorName(status));
70        return FALSE;
71    }
72
73    const char* name = "idna_conf.txt";     // test data file
74    int t = strlen(path) + strlen(name) + 1;
75    char* absolute_name = new char[t];
76    strcpy(absolute_name, path);
77    strcat(absolute_name, name);
78    f = fopen(absolute_name, "rb");
79    delete [] absolute_name;
80
81    if (f == NULL){
82        dataerrln("fopen error on %s", name);
83        return FALSE;
84    }
85
86    fseek( f, 0, SEEK_END);
87    if ((source_len = ftell(f)) <= 0){
88        errln("Error reading test data file.");
89        fclose(f);
90        return FALSE;
91    }
92
93    source = new char[source_len];
94    fseek(f, 0, SEEK_SET);
95    if (fread(source, 1, source_len, f) != source_len) {
96        errln("Error reading test data file.");
97        delete [] source;
98        fclose(f);
99        return FALSE;
100    }
101    fclose(f);
102
103    // convert the UTF-8 encoded stream to UTF-16 stream
104    UConverter* conv = ucnv_open("utf-8", &status);
105    int dest_len = ucnv_toUChars(conv,
106                                NULL,           //  dest,
107                                0,              //  destCapacity,
108                                source,
109                                source_len,
110                                &status);
111    if (status == U_BUFFER_OVERFLOW_ERROR) {
112        // Buffer Overflow is expected from the preflight operation.
113        status = U_ZERO_ERROR;
114        UChar * dest = NULL;
115        dest = new UChar[ dest_len + 1];
116        ucnv_toUChars(conv, dest, dest_len + 1, source, source_len, &status);
117        // Do not know the "if possible" behavior of ucnv_toUChars()
118        // Do it by ourself.
119        dest[dest_len] = 0;
120        len = dest_len;
121        base = dest;
122        delete [] source;
123        ucnv_close(conv);
124        return TRUE;    // The buffer will owned by caller.
125    }
126    errln("UConverter error: %s", u_errorName(status));
127    delete [] source;
128    ucnv_close(conv);
129    return FALSE;
130}
131
132int IdnaConfTest::isNewlineMark(){
133    static const UChar LF        = 0x0a;
134    static const UChar CR        = 0x0d;
135    UChar c = base[curOffset];
136    // CR LF
137    if ( c == CR && curOffset + 1 < len && base[curOffset + 1] == LF){
138        return 2;
139    }
140
141    // CR or LF
142    if ( c == CR || c == LF) {
143        return 1;
144    }
145
146    return 0;
147}
148
149/* Read a logical line.
150 *
151 * All lines ending in a backslash (\) and immediately followed by a newline
152 * character are joined with the next line in the source file forming logical
153 * lines from the physical lines.
154 *
155 */
156UBool IdnaConfTest::ReadOneLine(UnicodeString& buf){
157    if ( !(curOffset < len) ) return FALSE; // stream end
158
159    static const UChar BACKSLASH = 0x5c;
160    buf.remove();
161    int t = 0;
162    while (curOffset < len){
163        if ((t = isNewlineMark())) {  // end of line
164            curOffset += t;
165            break;
166        }
167        UChar c = base[curOffset];
168        if (c == BACKSLASH && curOffset < len -1){  // escaped new line mark
169            if ((t = isNewlineMark())){
170                curOffset += 1 + t;  // BACKSLAH and NewlineMark
171                continue;
172            }
173        };
174        buf.append(c);
175        curOffset++;
176    }
177    return TRUE;
178}
179
180//
181//===============================================================
182//
183
184/* Explain <xxxxx> tag to a native value
185 *
186 * Since <xxxxx> is always larger than the native value,
187 * the operation will replace the tag directly in the buffer,
188 * and, of course, will shift tail elements.
189 */
190void IdnaConfTest::ExplainCodePointTag(UnicodeString& buf){
191    buf.append((UChar)0);    // add a terminal NULL
192    UChar* bufBase = buf.getBuffer(buf.length());
193    UChar* p = bufBase;
194    while (*p != 0){
195        if ( *p != 0x3C){    // <
196            *bufBase++ = *p++;
197        } else {
198            p++;    // skip <
199            UChar32 cp = 0;
200            for ( ;*p != 0x3E; p++){   // >
201                if (0x30 <= *p && *p <= 0x39){        // 0-9
202                    cp = (cp * 16) + (*p - 0x30);
203                } else if (0x61 <= *p && *p <= 0x66){ // a-f
204                    cp = (cp * 16) + (*p - 0x61) + 10;
205                } else if (0x41 <= *p && *p <= 0x46) {// A-F
206                    cp = (cp * 16) + (*p - 0x41) + 10;
207                }
208                // no else. hope everything is good.
209            }
210            p++;    // skip >
211            if (U_IS_BMP(cp)){
212                *bufBase++ = cp;
213            } else {
214                *bufBase++ = U16_LEAD(cp);
215                *bufBase++ = U16_TRAIL(cp);
216            }
217        }
218    }
219    *bufBase = 0;  // close our buffer
220    buf.releaseBuffer();
221}
222
223void IdnaConfTest::Call(){
224    if (type == -1 || option == -1 || passfail == -1 || namebase.isBogus() || namezone.isBogus()){
225        errln("Incomplete record");
226    } else {
227        UErrorCode status = U_ZERO_ERROR;
228        UChar result[200] = {0,};   // simple life
229        const UChar *p = namebase.getTerminatedBuffer();
230        const int p_len = namebase.length();
231
232        if (type == 0 && option == 0){
233            uidna_IDNToASCII(p, p_len, result, 200, UIDNA_USE_STD3_RULES, NULL, &status);
234        } else if (type == 0 && option == 1){
235            uidna_IDNToASCII(p, p_len, result, 200, UIDNA_ALLOW_UNASSIGNED, NULL, &status);
236        } else if (type == 1 && option == 0){
237            uidna_IDNToUnicode(p, p_len, result, 200, UIDNA_USE_STD3_RULES, NULL, &status);
238        } else if (type == 1 && option == 1){
239            uidna_IDNToUnicode(p, p_len, result, 200, UIDNA_ALLOW_UNASSIGNED, NULL, &status);
240        }
241        if (passfail == 0){
242            if (U_FAILURE(status)){
243                id.append(" should pass, but failed. - ");
244                id.append(u_errorName(status));
245                errcheckln(status, id);
246            } else{
247                if (namezone.compare(result, -1) == 0){
248                    // expected
249                    logln(UnicodeString("namebase: ") + prettify(namebase) + UnicodeString(" result: ") + prettify(result));
250                } else {
251                    id.append(" no error, but result is not as expected.");
252                    errln(id);
253                }
254            }
255        } else if (passfail == 1){
256            if (U_FAILURE(status)){
257                // expected
258                // TODO: Uncomment this when U_IDNA_ZERO_LENGTH_LABEL_ERROR is added to u_errorName
259                //logln("Got the expected error: " + UnicodeString(u_errorName(status)));
260            } else{
261                if (namebase.compare(result, -1) == 0){
262                    // garbage in -> garbage out
263                    logln(UnicodeString("ICU will not recognize malformed ACE-Prefixes or incorrect ACE-Prefixes. ") + UnicodeString("namebase: ") + prettify(namebase) + UnicodeString(" result: ") + prettify(result));
264                } else {
265                    id.append(" should fail, but not failed. ");
266                    id.append(u_errorName(status));
267                    errln(id);
268                }
269            }
270        }
271    }
272    type = option = passfail = -1;
273    namebase.setToBogus();
274    namezone.setToBogus();
275    id.remove();
276    return;
277}
278
279void IdnaConfTest::Test(void){
280    if (!ReadAndConvertFile())return;
281
282    UnicodeString s;
283    UnicodeString key;
284    UnicodeString value;
285
286    // skip everything before the first "=====" and "=====" itself
287    do {
288        if (!ReadOneLine(s)) {
289            errln("End of file prematurely found");
290            break;
291        }
292    }
293    while (s.compare(C_TAG, -1) != 0);   //"====="
294
295    while(ReadOneLine(s)){
296        s.trim();
297        key.remove();
298        value.remove();
299        if (s.compare(C_TAG, -1) == 0){   //"====="
300            Call();
301       } else {
302            // explain      key:value
303            int p = s.indexOf((UChar)0x3A);    // :
304            key.setTo(s,0,p).trim();
305            value.setTo(s,p+1).trim();
306            if (key.compare(C_TYPE, -1) == 0){
307                if (value.compare(C_TOASCII, -1) == 0) {
308                    type = 0;
309                } else if (value.compare(C_TOUNICODE, -1) == 0){
310                    type = 1;
311                }
312            } else if (key.compare(C_PASSFAIL, -1) == 0){
313                if (value.compare(C_PASS, -1) == 0){
314                    passfail = 0;
315                } else if (value.compare(C_FAIL, -1) == 0){
316                    passfail = 1;
317                }
318            } else if (key.compare(C_DESC, -1) == 0){
319                if (value.indexOf(C_USESTD3ASCIIRULES, u_strlen(C_USESTD3ASCIIRULES), 0) == -1){
320                    option = 1; // not found
321                } else {
322                    option = 0;
323                }
324                id.setTo(value, 0, value.indexOf((UChar)0x20));    // space
325            } else if (key.compare(C_NAMEZONE, -1) == 0){
326                ExplainCodePointTag(value);
327                namezone.setTo(value);
328            } else if (key.compare(C_NAMEBASE, -1) == 0){
329                ExplainCodePointTag(value);
330                namebase.setTo(value);
331            }
332            // just skip other lines
333        }
334    }
335
336    Call(); // for last record
337}
338#else
339void IdnaConfTest::Test(void)
340{
341  // test nothing...
342}
343#endif
344
345void IdnaConfTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/){
346    switch (index) {
347        TESTCASE(0,Test);
348        default: name = ""; break;
349    }
350}
351
352#endif
353