1 /*
2  * tkimg.c --
3  *
4  *  Generic interface to XML parsers.
5  *
6  * Copyright (c) 2002 Andreas Kupries <andreas_kupries@users.sourceforge.net>
7  *
8  * Zveno Pty Ltd makes this software and associated documentation
9  * available free of charge for any purpose.  You may make copies
10  * of the software but you must include all of this notice on any copy.
11  *
12  * Zveno Pty Ltd does not warrant that this software is error free
13  * or fit for any purpose.  Zveno Pty Ltd disclaims any liability for
14  * all claims, expenses, losses, damages and costs any user may incur
15  * as a result of using, copying or modifying the software.
16  */
17 
18 #include "tkimg.h"
19 
20 /*
21  * Prototypes for procedures defined later in this file:
22  */
23 
24 /* Variables needed for optional read buffer. See tkimg_ReadBuffer. */
25 #define BUFLEN 4096
26 static int useReadBuf = 0;
27 static int bufStart = -1;
28 static int bufEnd = -1;
29 static char readBuf[BUFLEN];
30 
31 
32 /*
33  *--------------------------------------------------------------------------
34  * char64 --
35  *
36  *	This procedure converts a base64 ascii character into its binary
37  *	equivalent. This code is a slightly modified version of the
38  *	char64 proc in N. Borenstein's metamail decoder.
39  *
40  * Results:
41  *	The binary value, or an error code.
42  *
43  * Side effects:
44  *	None.
45  *--------------------------------------------------------------------------
46  */
47 
char64(int c)48 static int char64(
49     int c
50 ) {
51     switch(c) {
52 	case 'A': return 0;	case 'B': return 1;	case 'C': return 2;
53 	case 'D': return 3;	case 'E': return 4;	case 'F': return 5;
54 	case 'G': return 6;	case 'H': return 7;	case 'I': return 8;
55 	case 'J': return 9;	case 'K': return 10;	case 'L': return 11;
56 	case 'M': return 12;	case 'N': return 13;	case 'O': return 14;
57 	case 'P': return 15;	case 'Q': return 16;	case 'R': return 17;
58 	case 'S': return 18;	case 'T': return 19;	case 'U': return 20;
59 	case 'V': return 21;	case 'W': return 22;	case 'X': return 23;
60 	case 'Y': return 24;	case 'Z': return 25;	case 'a': return 26;
61 	case 'b': return 27;	case 'c': return 28;	case 'd': return 29;
62 	case 'e': return 30;	case 'f': return 31;	case 'g': return 32;
63 	case 'h': return 33;	case 'i': return 34;	case 'j': return 35;
64 	case 'k': return 36;	case 'l': return 37;	case 'm': return 38;
65 	case 'n': return 39;	case 'o': return 40;	case 'p': return 41;
66 	case 'q': return 42;	case 'r': return 43;	case 's': return 44;
67 	case 't': return 45;	case 'u': return 46;	case 'v': return 47;
68 	case 'w': return 48;	case 'x': return 49;	case 'y': return 50;
69 	case 'z': return 51;	case '0': return 52;	case '1': return 53;
70 	case '2': return 54;	case '3': return 55;	case '4': return 56;
71 	case '5': return 57;	case '6': return 58;	case '7': return 59;
72 	case '8': return 60;	case '9': return 61;	case '+': return 62;
73 	case '/': return 63;
74 
75 	case ' ': case '\t': case '\n': case '\r': case '\f': return IMG_SPACE;
76 	case '=': return IMG_PAD;
77 	case '\0': return IMG_DONE;
78 	default: return IMG_BAD;
79     }
80 }
81 
82 /*
83  *--------------------------------------------------------------
84  *
85  * tkimg_ReadBuffer --
86  *	Initialize optional read buffer.
87  *
88  *	The optional read buffer may be used for compressed image file
89  *	formats (ex. RLE), where the file has to be read byte by byte.
90  *	This option is only available when reading from an image file,
91  *	i.e. "image create -file ..."
92  *
93  *	CAUTION:
94  *	- Use this option only, when you do NOT use file seeks.
95  *    	- Use tkimg_ReadBuffer (1) to initialize the read buffer before usage.
96  *	  Use tkimg_ReadBuffer (0) to switch off the read buffer after usage.
97  *
98  * Results:
99  *	None
100  *
101  * Side effects:
102  *	Changes the static variables needed for the read buffer.
103  *
104  *--------------------------------------------------------------
105  */
106 
tkimg_ReadBuffer(int onOff)107 void tkimg_ReadBuffer(
108 	int onOff
109 ) {
110     useReadBuf = onOff;
111     if (onOff) {
112 	memset(readBuf, 0, BUFLEN);
113 	bufStart = -1;
114 	bufEnd   = -1;
115     }
116 }
117 
118 /*
119  *--------------------------------------------------------------------------
120  * tkimg_Read --
121  *
122  *  This procedure returns a buffer from the stream input. This stream
123  *  could be anything from a base-64 encoded string to a Channel.
124  *
125  * Results:
126  *  The number of characters successfully read from the input
127  *
128  * Side effects:
129  *  The tkimg_MFile state could change.
130  *--------------------------------------------------------------------------
131  */
132 
tkimg_Read(tkimg_MFile * handle,char * dst,int count)133 int tkimg_Read(
134 	tkimg_MFile *handle /* mmdecode "file" handle */,
135 	char *dst /* where to put the result */,
136 	int count /* number of bytes */
137 ) {
138     int i, c;
139     int bytesRead, bytesToRead;
140     char *dstPtr;
141 
142     switch (handle->state) {
143       case IMG_STRING:
144 	if ((unsigned int)count > handle->length) {
145 	    count = handle->length;
146 	}
147 	if (count) {
148 	    memcpy(dst, handle->data, count);
149 	    handle->length -= count;
150 	    handle->data += count;
151 	}
152 	return count;
153       case IMG_CHAN:
154 	if (!useReadBuf) {
155 	    return (int) Tcl_Read((Tcl_Channel) handle->data, dst, count);
156 	}
157 	dstPtr = dst;
158 	bytesToRead = count;
159 	bytesRead = 0;
160 	while (bytesToRead > 0) {
161 #ifdef DEBUG_LOCAL
162 		printf ("bytesToRead=%d bytesRead=%d (bufStart=%d bufEnd=%d)\n",
163 			 bytesToRead, bytesRead, bufStart, bufEnd);
164 #endif
165 	    if (bufStart < 0) {
166 		bufEnd = Tcl_Read((Tcl_Channel)handle->data, readBuf, BUFLEN)-1;
167 #ifdef DEBUG_LOCAL
168 		    printf ("Reading new %d bytes into buffer "
169                             "(bufStart=%d bufEnd=%d)\n",
170                             BUFLEN, bufStart, bufEnd);
171 #endif
172 		bufStart = 0;
173 	   	if (bufEnd < 0)
174 		    return bufEnd;
175 	    }
176 	    if (bufStart + bytesToRead <= bufEnd +1) {
177 #ifdef DEBUG_LOCAL
178 		    printf("All in buffer: memcpy %d bytes\n", bytesToRead);
179 #endif
180 		/* All bytes already in the buffer. Just copy them to dst. */
181 		memcpy(dstPtr, readBuf + bufStart, bytesToRead);
182 		bufStart += bytesToRead;
183 		if (bufStart > BUFLEN)
184 		    bufStart = -1;
185 		return bytesRead + bytesToRead;
186 	    } else {
187 #ifdef DEBUG_LOCAL
188 		    printf("Copy rest of buffer: memcpy %d bytes\n",
189                             bufEnd+1-bufStart);
190 #endif
191 		memcpy (dstPtr, readBuf + bufStart, bufEnd+1 - bufStart);
192 		bytesRead += (bufEnd +1 - bufStart);
193 		bytesToRead -= (bufEnd+1 - bufStart);
194 		bufStart = -1;
195 		dstPtr += bytesRead;
196 	    }
197 	}
198     }
199 
200     for(i = 0; i < count && (c = tkimg_Getc(handle)) != IMG_DONE; i++) {
201 	*dst++ = c;
202     }
203     return i;
204 }
205 
206 /*
207  *--------------------------------------------------------------------------
208  * tkimg_Read2 --
209  *
210  *  This procedure returns a buffer from the stream input. This stream
211  *  could be anything from a base-64 encoded string to a Channel.
212  *
213  * Results:
214  *  The number of characters successfully read from the input
215  *
216  * Side effects:
217  *  The tkimg_MFile state could change.
218  *--------------------------------------------------------------------------
219  */
220 
tkimg_Read2(tkimg_MFile * handle,char * dst,size_t count)221 size_t tkimg_Read2(
222 	tkimg_MFile *handle /* mmdecode "file" handle */,
223 	char *dst /* where to put the result */,
224 	size_t count /* number of bytes */
225 ) {
226     size_t i;
227     int c;
228     size_t bytesRead, bytesToRead;
229     char *dstPtr;
230 
231     switch (handle->state) {
232       case IMG_STRING:
233 	if (count > handle->length) {
234 	    count = handle->length;
235 	}
236 	if (count) {
237 	    memcpy(dst, handle->data, count);
238 	    handle->length -= count;
239 	    handle->data += count;
240 	}
241 	return count;
242       case IMG_CHAN:
243 	if (!useReadBuf) {
244 	    return (size_t) Tcl_Read((Tcl_Channel) handle->data, dst, count);
245 	}
246 	dstPtr = dst;
247 	bytesToRead = count;
248 	bytesRead = 0;
249 	while (bytesToRead > 0) {
250 #ifdef DEBUG_LOCAL
251 		printf ("bytesToRead=%d bytesRead=%d (bufStart=%d bufEnd=%d)\n",
252 			 bytesToRead, bytesRead, bufStart, bufEnd);
253 #endif
254 	    if (bufStart < 0) {
255 		bufEnd = Tcl_Read((Tcl_Channel)handle->data, readBuf, BUFLEN)-1;
256 #ifdef DEBUG_LOCAL
257 		    printf ("Reading new %d bytes into buffer "
258                             "(bufStart=%d bufEnd=%d)\n",
259                             BUFLEN, bufStart, bufEnd);
260 #endif
261 		bufStart = 0;
262 	   	if (bufEnd < 0)
263 		    return bufEnd;
264 	    }
265 	    if (bufStart + (int)bytesToRead <= bufEnd + 1) {
266 #ifdef DEBUG_LOCAL
267 		    printf("All in buffer: memcpy %d bytes\n", bytesToRead);
268 #endif
269 		/* All bytes already in the buffer. Just copy them to dst. */
270 		memcpy(dstPtr, readBuf + bufStart, bytesToRead);
271 		bufStart += bytesToRead;
272 		if (bufStart > BUFLEN)
273 		    bufStart = -1;
274 		return bytesRead + bytesToRead;
275 	    } else {
276 #ifdef DEBUG_LOCAL
277 		    printf("Copy rest of buffer: memcpy %d bytes\n",
278                             bufEnd+1-bufStart);
279 #endif
280 		memcpy (dstPtr, readBuf + bufStart, bufEnd+1 - bufStart);
281 		bytesRead += (bufEnd +1 - bufStart);
282 		bytesToRead -= (bufEnd+1 - bufStart);
283 		bufStart = -1;
284 		dstPtr += bytesRead;
285 	    }
286 	}
287     }
288 
289     for(i = 0; i < count && (c = tkimg_Getc(handle)) != IMG_DONE; i++) {
290 	*dst++ = c;
291     }
292     return i;
293 }
294 
295 /*
296  *--------------------------------------------------------------------------
297  *
298  * tkimg_Getc --
299  *
300  *  This procedure returns the next input byte from a stream. This stream
301  *  could be anything from a base-64 encoded string to a Channel.
302  *
303  * Results:
304  *  The next byte (or IMG_DONE) is returned.
305  *
306  * Side effects:
307  *  The tkimg_MFile state could change.
308  *
309  *--------------------------------------------------------------------------
310  */
311 
tkimg_Getc(tkimg_MFile * handle)312 int tkimg_Getc(
313 	tkimg_MFile *handle /* Input stream handle */
314 ) {
315     int c;
316     int result = 0;			/* Initialization needed only to prevent
317 					 * gcc compiler warning */
318     if (handle->state == IMG_DONE) {
319 	return IMG_DONE;
320     }
321 
322     if (handle->state == IMG_STRING) {
323 	if (!handle->length--) {
324 	    handle->state = IMG_DONE;
325 	    return IMG_DONE;
326 	}
327 	return *handle->data++;
328     }
329 
330     do {
331 	if (!handle->length--) {
332 	    handle->state = IMG_DONE;
333 	    return IMG_DONE;
334 	}
335 	c = char64(*handle->data++);
336     } while (c == IMG_SPACE);
337 
338     if (c > IMG_SPECIAL) {
339 	handle->state = IMG_DONE;
340 	return IMG_DONE;
341     }
342 
343     switch (handle->state++) {
344 	case 0:
345 	    handle->c = c<<2;
346 	    result = tkimg_Getc(handle);
347 	    break;
348 	case 1:
349 	    result = handle->c | (c>>4);
350 	    handle->c = (c&0xF)<<4;
351 	    break;
352 	case 2:
353 	    result = handle->c | (c>>2);
354 	    handle->c = (c&0x3)<<6;
355 	    break;
356 	case 3:
357 	    result = handle->c | c;
358 	    handle->state = 0;
359 	    break;
360     }
361     return result;
362 }
363 
364 /*
365  *-----------------------------------------------------------------------
366  * tkimg_Write --
367  *
368  *  This procedure is invoked to put imaged data into a stream
369  *  using tkimg_Putc.
370  *
371  * Results:
372  *  The return value is the number of characters "written"
373  *
374  * Side effects:
375  *  The base64 handle will change state.
376  *
377  *-----------------------------------------------------------------------
378  */
379 
tkimg_Write(tkimg_MFile * handle,const char * src,int count)380 int tkimg_Write(
381     tkimg_MFile *handle /* mmencode "file" handle */,
382     const char *src /* where to get the data */,
383     int count /* number of bytes */
384 ) {
385     int i;
386     int curcount, bufcount;
387 
388     if (handle->state == IMG_CHAN) {
389 	return Tcl_Write((Tcl_Channel) handle->data, (char *) src, count);
390     }
391     curcount = handle->data - Tcl_DStringValue(handle->buffer);
392     bufcount = curcount + count + count/3 + count/52 + 1024;
393 
394     /* make sure that the DString contains enough space */
395     if (bufcount >= (handle->buffer->spaceAvl)) {
396 	Tcl_DStringSetLength(handle->buffer, bufcount + 4096);
397 	handle->data = Tcl_DStringValue(handle->buffer) + curcount;
398     }
399     /* write the data */
400     for (i=0; (i<count) && (tkimg_Putc(*src++, handle) != IMG_DONE); i++) {
401 	/* empty loop body */
402     }
403     return i;
404 }
405 
406 /*
407  *-----------------------------------------------------------------------
408  * tkimg_Write2 --
409  *
410  *  This procedure is invoked to put imaged data into a stream
411  *  using tkimg_Putc.
412  *
413  * Results:
414  *  The return value is the number of characters "written"
415  *
416  * Side effects:
417  *  The base64 handle will change state.
418  *
419  *-----------------------------------------------------------------------
420  */
421 
tkimg_Write2(tkimg_MFile * handle,const char * src,size_t count)422 size_t tkimg_Write2(
423     tkimg_MFile *handle /* mmencode "file" handle */,
424     const char *src /* where to get the data */,
425     size_t count /* number of bytes */
426 ) {
427     size_t i, curcount, bufcount;
428 
429     if (handle->state == IMG_CHAN) {
430 	return Tcl_Write((Tcl_Channel) handle->data, (char *) src, count);
431     }
432     curcount = handle->data - Tcl_DStringValue(handle->buffer);
433     bufcount = curcount + count + count/3 + count/52 + 1024;
434 
435     /* make sure that the DString contains enough space */
436     if (bufcount >= (size_t)(handle->buffer->spaceAvl)) {
437 	Tcl_DStringSetLength(handle->buffer, bufcount + 4096);
438 	handle->data = Tcl_DStringValue(handle->buffer) + curcount;
439     }
440     /* write the data */
441     for (i=0; (i<count) && (tkimg_Putc(*src++, handle) != IMG_DONE); i++) {
442 	/* empty loop body */
443     }
444     return i;
445 }
446 
447 /*
448  *-----------------------------------------------------------------------
449  *
450  * tkimg_Putc --
451  *
452  *  This procedure encodes and writes the next byte to a base64
453  *  encoded string.
454  *
455  * Results:
456  *  The written byte is returned.
457  *
458  * Side effects:
459  *  the base64 handle will change state.
460  *
461  *-----------------------------------------------------------------------
462  */
463 
464 static char const base64_table[64] = {
465     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
466     'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
467     'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
468     'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
469     'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
470     'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
471     'w', 'x', 'y', 'z', '0', '1', '2', '3',
472     '4', '5', '6', '7', '8', '9', '+', '/'
473 };
474 
tkimg_Putc(int c,tkimg_MFile * handle)475 int tkimg_Putc(
476     int c /* character to be written */,
477     tkimg_MFile *handle /* handle containing decoder data and state */
478 ) {
479     /* In fact, here should be checked first if the dynamic
480      * string contains enough space for the next character.
481      * This would be very expensive to do for each character.
482      * Therefore we just allocate 1024 bytes immediately in
483      * the beginning and also take a 1024 bytes margin inside
484      * every tkimg_Write. At least this check is done then only
485      * every 256 bytes, which is much faster. Because the GIF
486      * header is less than 1024 bytes and pixel data is
487      * written in 256 byte portions, this should be safe.
488      */
489 
490     if (c == IMG_DONE) {
491 	switch(handle->state) {
492 	    case 0:
493 		break;
494 	    case 1:
495 		*handle->data++ = base64_table[(handle->c<<4)&63];
496 		*handle->data++ = '='; *handle->data++ = '='; break;
497 	    case 2:
498 		*handle->data++ = base64_table[(handle->c<<2)&63];
499 		*handle->data++ = '='; break;
500 	    default:
501 		handle->state = IMG_DONE;
502 		return IMG_DONE;
503 	}
504 	Tcl_DStringSetLength(handle->buffer,
505 		(handle->data) - Tcl_DStringValue(handle->buffer));
506 	handle->state = IMG_DONE;
507 	return IMG_DONE;
508     }
509 
510     if (handle->state == IMG_CHAN) {
511 	char ch = (char) c;
512 	return (Tcl_Write((Tcl_Channel) handle->data, &ch, 1)+1>1) ? c : IMG_DONE;
513     }
514 
515     c &= 0xff;
516     switch (handle->state++) {
517 	case 0:
518 	    *handle->data++ = base64_table[(c>>2)&63]; break;
519 	case 1:
520 	    c |= handle->c << 8;
521 	    *handle->data++ = base64_table[(c>>4)&63]; break;
522 	case 2:
523 	    handle->state = 0;
524 	    c |= handle->c << 8;
525 	    *handle->data++ = base64_table[(c>>6)&63];
526 	    *handle->data++ = base64_table[c&63]; break;
527     }
528     handle->c = c;
529     if (handle->length++ > 52) {
530 	handle->length = 0;
531 	*handle->data++ = '\n';
532     }
533     return c & 0xff;
534 };
535 
536 /*
537  *-------------------------------------------------------------------------
538  * tkimg_WriteInit --
539  *  This procedure initializes a base64 decoder handle for writing
540  *
541  * Results:
542  *  none
543  *
544  * Side effects:
545  *  the base64 handle is initialized
546  *
547  *-------------------------------------------------------------------------
548  */
549 
tkimg_WriteInit(Tcl_DString * buffer,tkimg_MFile * handle)550 void tkimg_WriteInit(
551 	Tcl_DString *buffer,
552 	tkimg_MFile *handle /* mmencode "file" handle */
553 ) {
554     Tcl_DStringSetLength(buffer, buffer->spaceAvl);
555     handle->buffer = buffer;
556     handle->data = Tcl_DStringValue(buffer);
557     handle->state = 0;
558     handle->length = 0;
559 }
560 
561 /*
562  *-------------------------------------------------------------------------
563  * tkimg_ReadInit --
564  *  This procedure initializes a base64 decoder handle for reading.
565  *
566  * Results:
567  *  none
568  *
569  * Side effects:
570  *  the base64 handle is initialized
571  *
572  *-------------------------------------------------------------------------
573  */
574 
tkimg_ReadInit(Tcl_Obj * data,int c,tkimg_MFile * handle)575 int tkimg_ReadInit(
576 	Tcl_Obj *data /* string containing initial mmencoded data */,
577 	int c,
578 	tkimg_MFile *handle /* mmdecode "file" handle */
579 ) {
580     size_t length;
581     handle->data = (char *) tkimg_GetByteArrayFromObj2(data, &length);
582     handle->length = length;
583     if (*handle->data == c) {
584 	handle->state = IMG_STRING;
585 	return 1;
586     }
587     c = base64_table[(c>>2)&63];
588 
589     while ((handle->length) && (char64(*handle->data) == IMG_SPACE)) {
590 	handle->data++;
591 	handle->length--;
592     }
593     if (c != *handle->data) {
594 	handle->state = IMG_DONE;
595 	return 0;
596     }
597     handle->state = 0;
598     return 1;
599 }
600 
601 /*
602  *----------------------------------------------------------------------
603  *
604  * tkimg_OpenFileChannel --
605  *
606  *	Open a file channel in binary mode. If permissions is 0, the
607  *	file will be opened in read mode, otherwise in write mode.
608  *
609  * Results:
610  *	The same as Tcl_OpenFileChannel, only the file will
611  *	always be opened in binary mode without encoding.
612  *
613  * Side effects:
614  *	If function fails, an error message will be left in the
615  *	interpreter.
616  *
617  *----------------------------------------------------------------------
618  */
619 
tkimg_OpenFileChannel(Tcl_Interp * interp,const char * fileName,int permissions)620 Tcl_Channel tkimg_OpenFileChannel(
621 	Tcl_Interp *interp,
622 	const char *fileName,
623 	int permissions
624 ) {
625     Tcl_Channel chan = Tcl_OpenFileChannel(interp, fileName,
626 	    permissions?"w":"r", permissions);
627     if (!chan) {
628 	return NULL;
629     }
630     if (Tcl_SetChannelOption(interp, chan, "-buffersize", "131072") != TCL_OK) {
631         Tcl_Close(interp, chan);
632         return NULL;
633     }
634     if (Tcl_SetChannelOption(interp, chan, "-translation", "binary") != TCL_OK) {
635 	Tcl_Close(interp, chan);
636 	return NULL;
637     }
638     return chan;
639 }
640