1 /* FreeTDS - Library of routines accessing Sybase and Microsoft databases
2  * Copyright (C) 2015  Frediano Ziglio
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19 #undef NDEBUG
20 #include "common.h"
21 #include <freetds/iconv.h>
22 #include "md5.h"
23 
24 #undef NDEBUG
25 #include <ctype.h>
26 #include <assert.h>
27 
28 #if HAVE_UNISTD_H
29 #undef getpid
30 #include <unistd.h>
31 #elif defined(_WIN32)
32 # include <io.h>
33 # undef isatty
34 # define isatty(fd) _isatty(fd)
35 #endif /* HAVE_UNISTD_H */
36 
37 static TDSSOCKET *tds;
38 
39 static void
get_coll_md5(const char * name,char * digest)40 get_coll_md5(const char *name, char *digest)
41 {
42 	MD5_CTX ctx;
43 	char *p = NULL, *sql = NULL;
44 	int ret;
45 	TDSRET rc;
46 	TDS_INT result_type;
47 	int done_flags, i;
48 	unsigned char dig[16];
49 
50 	ret = asprintf(&sql, "convert(nvarchar(1024), bin) collate %s", name);
51 	assert(ret >= 0);
52 
53 	ret = asprintf(&p, "convert(varchar(4096), %s)", sql);
54 	assert(ret >= 0);
55 	free(sql);
56 	sql = NULL;
57 
58 	ret = asprintf(&sql, "select convert(varbinary(8000), %s) from #all_chars order by id", p);
59 	assert(ret >= 0);
60 	free(p);
61 	p = NULL;
62 
63 	rc = tds_submit_query(tds, sql);
64 	assert(rc == TDS_SUCCESS);
65 	free(sql);
66 	sql = NULL;
67 
68 	MD5Init(&ctx);
69 
70 	while ((rc = tds_process_tokens(tds, &result_type, &done_flags, TDS_RETURN_ROW)) == TDS_SUCCESS) {
71 		TDSCOLUMN *curcol;
72 
73 		assert(result_type == TDS_ROW_RESULT);
74 
75 		curcol = tds->current_results->columns[0];
76 
77 		assert(!is_blob_col(curcol));
78 		assert(curcol->on_server.column_type == XSYBVARBINARY);
79 
80 		MD5Update(&ctx, curcol->column_data, curcol->column_cur_size);
81 	}
82 	assert(rc == TDS_NO_MORE_RESULTS);
83 
84 	memset(dig, 0, sizeof(dig));
85 	MD5Final(&ctx, dig);
86 	for (i = 0; i < 16; ++i)
87 		sprintf(digest + i * 2, "%02x", dig[i]);
88 }
89 
90 static void
get_encoding_coll(TDS71_COLLATION * coll,char * digest,char * cp,const char * name)91 get_encoding_coll(TDS71_COLLATION *coll, char *digest, char *cp, const char *name)
92 {
93 	int rc;
94 	int done_flags;
95 	TDS_INT result_type;
96 	char sql[512];
97 	static TDS71_COLLATION old_coll = { 0, 0, 0};
98 	static char old_digest[33];
99 
100 	sprintf(sql, "SELECT CAST(CAST('a' AS NVARCHAR(10)) COLLATE %s AS VARCHAR(10)) COLLATE %s", name, name);
101 
102 	/* do a select and check all results */
103 	rc = tds_submit_query(tds, sql);
104 	assert(rc == TDS_SUCCESS);
105 
106 	assert(tds_process_tokens(tds, &result_type, NULL, TDS_TOKEN_RESULTS) == TDS_SUCCESS);
107 
108 	if (result_type == TDS_DONE_RESULT)
109 		return;
110 
111 	assert(result_type == TDS_ROWFMT_RESULT);
112 
113 	assert(tds_process_tokens(tds, &result_type, NULL, TDS_TOKEN_RESULTS) == TDS_SUCCESS);
114 
115 	assert(result_type == TDS_ROW_RESULT);
116 
117 	while ((rc = tds_process_tokens(tds, &result_type, NULL, TDS_STOPAT_ROWFMT|TDS_STOPAT_DONE|TDS_RETURN_ROW|TDS_RETURN_COMPUTE)) == TDS_SUCCESS) {
118 
119 		TDSCOLUMN *curcol;
120 		TDS_UCHAR *c;
121 
122 		if (result_type != TDS_ROW_RESULT)
123 			break;
124 
125 		curcol = tds->current_results->columns[0];
126 
127 		strcpy(cp, curcol->char_conv->to.charset.name);
128 
129 		c = curcol->column_collation;
130 		coll->locale_id = c[0] + 256 * c[1];
131 		coll->flags = c[2] + 256 * c[3];
132 		coll->charset_id = c[4];
133 	}
134 
135 	if (rc != TDS_SUCCESS || result_type == TDS_ROW_RESULT || result_type == TDS_COMPUTE_RESULT) {
136 		fprintf(stderr, "tds_process_tokens() unexpected return\n");
137 		exit(1);
138 	}
139 
140 	while ((rc = tds_process_tokens(tds, &result_type, &done_flags, TDS_TOKEN_RESULTS)) == TDS_SUCCESS) {
141 		switch (result_type) {
142 		case TDS_NO_MORE_RESULTS:
143 			return;
144 
145 		case TDS_DONE_RESULT:
146 		case TDS_DONEPROC_RESULT:
147 		case TDS_DONEINPROC_RESULT:
148 			if (!(done_flags & TDS_DONE_ERROR))
149 				break;
150 
151 		default:
152 			fprintf(stderr, "tds_proces_tokens() unexpected result_type\n");
153 			exit(1);
154 			break;
155 		}
156 	}
157 
158 	if (old_coll.locale_id && old_coll.locale_id == coll->locale_id && old_coll.charset_id == coll->charset_id) {
159 		memcpy(digest, old_digest, 33);
160 	} else {
161 		get_coll_md5(name, digest);
162 	}
163 
164 	memcpy(old_digest, digest, 33);
165 	memcpy(&old_coll, coll, sizeof(*coll));
166 }
167 
168 static void
test_column_encoding(void)169 test_column_encoding(void)
170 {
171 	FILE *f = fopen("collations.txt", "r");
172 	char line[1024];
173 
174 	assert(f);
175 	while (fgets(line, sizeof(line), f)) {
176 		TDS71_COLLATION coll;
177 		char cp[128], digest[33];
178 		char *s = strtok(line, " \n");
179 
180 		if (!s[0])
181 			continue;
182 
183 		memset(&coll, 0, sizeof(coll));
184 		cp[0] = 0;
185 		digest[0] = 0;
186 
187 		get_encoding_coll(&coll, digest, cp, s);
188 		printf("%s %d %d %d %s %s\n", s, coll.locale_id, coll.flags, coll.charset_id, cp, digest);
189 	}
190 	fclose(f);
191 }
192 
193 static void
add_couple(unsigned n)194 add_couple(unsigned n)
195 {
196 	enum { CHUNK = 512 };
197 	static char buf[CHUNK * 4+1];
198 	static int cnt = 0;
199 	static int id = 0;
200 
201 	char *sql = NULL;
202 	int ret;
203 
204 	sprintf(buf + cnt * 4, "%02x%02x", n & 0xff, (n >> 8) & 0xff);
205 
206 	/* time to insert the command into database ? */
207 	if (++cnt != CHUNK)
208 		return;
209 
210 	cnt = 0;
211 	++id;
212 	ret = asprintf(&sql, "insert into #all_chars values(%d, convert(varbinary(2048), 0x%s))", id, buf);
213 	assert(ret >= 0);
214 
215 	if (isatty(fileno(stdout))) {
216 		printf("\rInserting: %d", id);
217 		fflush(stdout);
218 	}
219 
220 	ret = run_query(tds, sql);
221 	assert(ret == TDS_SUCCESS);
222 
223 	free(sql);
224 }
225 
226 static void
add_plane0(void)227 add_plane0(void)
228 {
229 	unsigned n;
230 	for (n = 0; n < 65536; ++n)
231 		add_couple(n);
232 }
233 
234 static void
add_couples(void)235 add_couples(void)
236 {
237 	unsigned n;
238 	for (n = 0; n <= 0xfffff; ++n) {
239 		add_couple( (n >> 10) + 0xd800 );
240 		add_couple( (n & 0x3ff) + 0xdc00 );
241 	}
242 }
243 
244 static void
prepare_all_chars(void)245 prepare_all_chars(void)
246 {
247 	int ret;
248 
249 	ret = run_query(tds, "CREATE TABLE #all_chars(id int, bin varbinary(2048))");
250 	assert(ret == TDS_SUCCESS);
251 
252 	add_plane0();
253 	add_couples();
254 	printf("\n");
255 }
256 
257 static void
extract_collations(void)258 extract_collations(void)
259 {
260 	TDS_INT result_type;
261 	int done_flags;
262 	TDSRET rc;
263 	FILE *f;
264 
265 	f = fopen("collations.txt", "w");
266 	assert(f);
267 
268 	rc = tds_submit_query(tds, "select name from ::fn_helpcollations() order by name");
269 	assert(rc == TDS_SUCCESS);
270 
271 	while ((rc = tds_process_tokens(tds, &result_type, &done_flags, TDS_RETURN_ROW)) == TDS_SUCCESS) {
272 		TDSCOLUMN *curcol;
273 		int len;
274 
275 		assert(result_type == TDS_ROW_RESULT);
276 
277 		curcol = tds->current_results->columns[0];
278 
279 		assert(!is_blob_col(curcol));
280 		assert(curcol->column_type == SYBVARCHAR);
281 
282 		len = curcol->column_cur_size;
283 		fprintf(f, "%*.*s\n", len, len, curcol->column_data);
284 	}
285 	assert(rc == TDS_NO_MORE_RESULTS);
286 
287 	fclose(f);
288 }
289 
290 int
main(int argc,char ** argv)291 main(int argc, char **argv)
292 {
293 	TDSLOGIN *login;
294 	int ret;
295 	int verbose = 0;
296 
297 	/* use UTF-8 as our coding */
298 	strcpy(CHARSET, "UTF-8");
299 
300 	ret = try_tds_login(&login, &tds, __FILE__, verbose);
301 	if (ret != TDS_SUCCESS) {
302 		fprintf(stderr, "try_tds_login() failed\n");
303 		return 1;
304 	}
305 
306 	if (IS_TDS71_PLUS(tds->conn)) {
307 		printf("Preparing table with all characters\n");
308 		prepare_all_chars();
309 
310 		printf("Extracting collation list\n");
311 		extract_collations();
312 
313 		printf("Testing encodings\n");
314 		test_column_encoding();
315 	}
316 
317 	try_tds_logout(login, tds, verbose);
318 	return 0;
319 }
320