1/*
2 * Copyright (c) 1998-2003,2011,2014 Apple Inc. All Rights Reserved.
3 *
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please
7 * obtain a copy of the License at http://www.apple.com/publicsource and
8 * read it before using this file.
9 *
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
12 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
13 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
14 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
15 * Please see the License for the specific language governing rights and
16 * limitations under the License.
17 *
18 * cuEnc64.c - encode/decode in 64-char IA5 format, per RFC 1421
19 */
20
21#include "cuEnc64.h"
22#include <stdlib.h>
23
24#ifndef	NULL
25#define NULL ((void *)0)
26#endif	/* NULL */
27
28/*
29 * map a 6-bit binary value to a printable character.
30 */
31static const
32unsigned char bintoasc[] =
33	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
34
35/*
36 * Map an 7-bit printable character to its corresponding binary value.
37 * Any illegal characters return high bit set.
38 */
39static const
40unsigned char asctobin[] =
41{
42    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
43    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
44    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
45    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
46    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
47    0x80, 0x80, 0x80, 0x3e, 0x80, 0x80, 0x80, 0x3f,
48    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
49    0x3c, 0x3d, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
50    0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
51    0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
52    0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
53    0x17, 0x18, 0x19, 0x80, 0x80, 0x80, 0x80, 0x80,
54    0x80, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
55    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
56    0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
57    0x31, 0x32, 0x33, 0x80, 0x80, 0x80, 0x80, 0x80
58};
59
60/*
61 * map 6 bits to a printing char
62 */
63#define ENC(c) (bintoasc[((c) & 0x3f)])
64
65#define PAD		'='
66
67/*
68 * map one group of up to 3 bytes at inp to 4 bytes at outp.
69 * Count is number of valid bytes in *inp; if less than 3, the
70 * 1 or two extras must be zeros.
71 */
72static void encChunk(const unsigned char *inp,
73	unsigned char *outp,
74	int count)
75{
76	unsigned char c1, c2, c3, c4;
77
78	c1 = *inp >> 2;
79	c2 = ((inp[0] << 4) & 0x30) | ((inp[1] >> 4) & 0xf);
80	c3 = ((inp[1] << 2) & 0x3c) | ((inp[2] >> 6) & 0x3);
81	c4 = inp[2] & 0x3f;
82	*outp++ = ENC(c1);
83	*outp++ = ENC(c2);
84	if (count == 1) {
85	    *outp++ = PAD;
86	    *outp   = PAD;
87	} else {
88	    *outp++ = ENC(c3);
89	    if (count == 2) {
90		*outp = PAD;
91	    }
92	    else {
93		*outp = ENC(c4);
94	    }
95	}
96}
97
98/*
99 * Given input buffer inbuf, length inlen, encode to 64-char IA5 format.
100 * Result is fmalloc'd and returned; it is terminated by Microsoft-style
101 * newline and NULL. Its length (including the trailing newline and NULL)
102 * is returned in *outlen.
103 */
104
105unsigned char *cuEnc64(const unsigned char *inbuf,
106	unsigned inlen,
107	unsigned *outlen)		// RETURNED
108{
109	return cuEnc64WithLines(inbuf, inlen, 0, outlen);
110}
111
112unsigned char *cuEnc64WithLines(const unsigned char *inbuf,
113	unsigned inlen,
114	unsigned linelen,
115	unsigned *outlen)
116{
117	unsigned		outTextLen;
118	unsigned 		len;			// to malloc, liberal
119	unsigned		olen = 0;		// actual output size
120	unsigned char 	*outbuf;
121	unsigned char 	endbuf[3];
122	unsigned		i;
123	unsigned char 	*outp;
124	unsigned		numLines;
125	unsigned		thisLine;
126
127	outTextLen = ((inlen + 2) / 3) * 4;
128	if(linelen) {
129	    /*
130	     * linelen must be 0 mod 4 for this to work; round up...
131	     */
132	    if((linelen & 0x03) != 0) {
133	        linelen = (linelen + 3) & 0xfffffffc;
134	    }
135	    numLines = (outTextLen + linelen - 1)/ linelen;
136	}
137	else {
138	    numLines = 1;
139	}
140
141	/*
142	 * Total output size = encoded text size plus one newline per
143	 * line of output, plus trailing NULL. We always generate newlines
144	 * as \n; when decoding, we tolerate \r\n (Microsoft) or \n.
145	 */
146	len = outTextLen + (2 * numLines) + 1;
147	outbuf = (unsigned char*)malloc(len);
148	outp = outbuf;
149	thisLine = 0;
150
151	while(inlen) {
152	    if(inlen < 3) {
153			for(i=0; i<3; i++) {
154				if(i < inlen) {
155					endbuf[i] = inbuf[i];
156				}
157				else {
158					endbuf[i] = 0;
159				}
160			}
161			encChunk(endbuf, outp, inlen);
162			inlen = 0;
163	    }
164	    else {
165			encChunk(inbuf, outp, 3);
166			inlen -= 3;
167			inbuf += 3;
168	    }
169	    outp += 4;
170	    thisLine += 4;
171	    olen += 4;
172	    if((linelen != 0) && (thisLine >= linelen) && inlen) {
173	        /*
174			 * last trailing newline added below
175			 * Note we don't split 4-byte output chunks over newlines
176			 */
177	    	*outp++ = '\n';
178			olen++;
179			thisLine = 0;
180	    }
181	}
182	*outp++ = '\n';
183	*outp = '\0';
184	olen += 2;
185	*outlen = olen;
186	return outbuf;
187}
188
189static inline int isWhite(unsigned char c)
190{
191	switch(c) {
192	    case '\n':
193	    case '\r':
194	    case ' ':
195	    case '\t':
196	    case '\0':
197			return 1;
198	    default:
199			return 0;
200	}
201}
202
203/*
204 * Strip off all whitespace from a (supposedly) enc64-format string.
205 * Returns a malloc'd string.
206 */
207static unsigned char *stringCleanse(const unsigned char *inbuf,
208	unsigned inlen,
209	unsigned *outlen)
210{
211	unsigned char	*news;			// cleansed inbuf
212	unsigned		newsDex;		// index into news
213	unsigned		i;
214
215	news = (unsigned char*)malloc(inlen);
216	newsDex = 0;
217	for(i=0; i<inlen; i++) {
218	    if(!isWhite(inbuf[i])) {
219	        news[newsDex++] = inbuf[i];
220	    }
221	}
222	*outlen = newsDex;
223	return news;
224}
225
226/*
227 * Given input buffer inbuf, length inlen, decode from 64-char IA5 format to
228 * binary. Result is malloced and returned; its length is returned in *outlen.
229 * NULL return indicates corrupted input.
230 *
231 * All whitespace in input is ignored.
232 */
233unsigned char *cuDec64(const unsigned char *inbuf,
234	unsigned inlen,
235	unsigned *outlen)
236{
237	unsigned char 		*outbuf;
238	unsigned char 		*outp;			// malloc'd outbuf size
239	unsigned 			obuflen;
240	const unsigned char	*bp;
241	unsigned 			olen = 0;		// actual output size
242	unsigned char 		c1, c2, c3, c4;
243	unsigned char 		j;
244	unsigned			thisOlen;
245	unsigned char		*news;			// cleansed inbuf
246	unsigned			newsLen;
247
248	/*
249	 * Strip out all whitespace; remainder must be multiple of four
250	 * characters
251	 */
252	news = stringCleanse(inbuf, inlen, &newsLen);
253	if((newsLen & 0x03) != 0) {
254	    free(news);
255	    return (unsigned char*) NULL;
256	}
257	inlen = newsLen;
258	bp = news;
259
260	obuflen = (inlen / 4) * 3;
261	outbuf = (unsigned char*)malloc(obuflen);
262	outp = outbuf;
263
264	while (inlen) {
265	    /*
266	     * Note inlen is always a multiple of four here
267	     */
268	    if (*bp & 0x80 || (c1 = asctobin[*bp]) & 0x80) {
269	        goto errorOut;
270	    }
271	    inlen--;
272	    bp++;
273	    if (*bp & 0x80 || (c2 = asctobin[*bp]) & 0x80){
274	        goto errorOut;
275	    }
276	    inlen--;
277	    bp++;
278	    if (*bp == PAD) {
279			/*
280			 * two input bytes, one output byte
281			 */
282			c3 = c4 = 0;
283			thisOlen = 1;
284			if (c2 & 0xf) {
285				goto errorOut;
286			}
287			bp++;
288			inlen--;
289			if (*bp == PAD) {
290				bp++;
291				inlen--;
292				if(inlen > 0) {
293					goto errorOut;
294				}
295			}
296			else {
297				goto errorOut;
298			}
299	    } else if (*bp & 0x80 || (c3 = asctobin[*bp]) & 0x80) {
300	    	goto errorOut;
301	    } else {
302	        bp++;
303		inlen--;
304		if (*bp == PAD) {
305		    /*
306		     * Three input bytes, two output
307		     */
308		    c4 = 0;
309		    thisOlen = 2;
310		    if (c3 & 3) {
311				goto errorOut;
312		    }
313		} else if (*bp & 0x80 || (c4 = asctobin[*bp]) & 0x80) {
314		    goto errorOut;
315		} else {
316		    /*
317		     * Normal non-pad case
318		     */
319		    thisOlen = 3;
320		}
321		bp++;
322		inlen--;
323	    }
324	    j = (c1 << 2) | (c2 >> 4);
325	    *outp++ = j;
326	    if(thisOlen > 1) {
327			j = (c2 << 4) | (c3 >> 2);
328			*outp++ = j;
329			if(thisOlen == 3) {
330				j = (c3 << 6) | c4;
331				*outp++ = j;
332			}
333	    }
334	    olen += thisOlen;
335	}
336	free(news);
337	*outlen = olen;
338	return outbuf;			/* normal return */
339
340errorOut:
341	free(news);
342	free(outbuf);
343	return (unsigned char*) NULL;
344}
345
346/*
347 * Determine if specified input data is valid enc64 format. Returns 1
348 * if valid, 0 if not.
349 * This doesn't do a full enc64 parse job; it scans for legal characters
350 * and proper sync when a possible pad is found.
351 */
352int cuIsValidEnc64(const unsigned char *inbuf,
353	unsigned inlen)
354{
355	int padChars = 0;	// running count of PAD chars
356	int validEncChars = 0;
357	unsigned char c;
358
359	/*
360	 *   -- scan inbuf
361	 *   -- skip whitespace
362	 *   -- count valid chars
363	 *   -- ensure not more than 2 PAD chars, only at end
364	 *   -- ensure valid chars mod 4 == 0
365	 */
366
367	while(inlen) {
368	    c = *inbuf++;
369	    inlen--;
370	    if(isWhite(c)) {
371	        continue;
372	    }
373	    if(c == PAD) {
374			if(++padChars > 2) {
375				return 0;		// max of 2 PAD chars at end
376			}
377	    }
378	    else if(padChars > 0) {
379			return 0;		// no normal chars after seeing PAD
380	    }
381	    else if((c & 0x80) || ((asctobin[c]) & 0x80)) {
382			return 0;		// invalid encoded char
383	    }
384	    validEncChars++;
385	}
386	if((validEncChars & 0x03) != 0) {
387	    return 0;
388	}
389	else {
390	    return 1;
391	}
392}
393