Start 1.33.0 development cycle
[oweals/busybox.git] / libbb / uuencode.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Copyright 2003, Glenn McGrath
4  * Copyright 2006, Rob Landley <rob@landley.net>
5  * Copyright 2010, Denys Vlasenko
6  *
7  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8  */
9 #include "libbb.h"
10
11 /* Conversion table.  for base 64 */
12 const char bb_uuenc_tbl_base64[65 + 1] ALIGN1 = {
13         'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
14         'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
15         'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
16         'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
17         'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
18         'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
19         'w', 'x', 'y', 'z', '0', '1', '2', '3',
20         '4', '5', '6', '7', '8', '9', '+', '/',
21         '=' /* termination character */,
22         '\0' /* needed for uudecode.c only */
23 };
24
25 const char bb_uuenc_tbl_std[65] ALIGN1 = {
26         '`', '!', '"', '#', '$', '%', '&', '\'',
27         '(', ')', '*', '+', ',', '-', '.', '/',
28         '0', '1', '2', '3', '4', '5', '6', '7',
29         '8', '9', ':', ';', '<', '=', '>', '?',
30         '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
31         'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
32         'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
33         'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
34         '`' /* termination character */
35 };
36
37 /*
38  * Encode bytes at S of length LENGTH to uuencode or base64 format and place it
39  * to STORE.  STORE will be 0-terminated, and must point to a writable
40  * buffer of at least 1+BASE64_LENGTH(length) bytes.
41  * where BASE64_LENGTH(len) = (4 * ((LENGTH + 2) / 3))
42  */
43 void FAST_FUNC bb_uuencode(char *p, const void *src, int length, const char *tbl)
44 {
45         const unsigned char *s = src;
46
47         /* Transform the 3x8 bits to 4x6 bits */
48         while (length > 0) {
49                 unsigned s1, s2;
50
51                 /* Are s[1], s[2] valid or should be assumed 0? */
52                 s1 = s2 = 0;
53                 length -= 3; /* can be >=0, -1, -2 */
54                 if (length >= -1) {
55                         s1 = s[1];
56                         if (length >= 0)
57                                 s2 = s[2];
58                 }
59                 *p++ = tbl[s[0] >> 2];
60                 *p++ = tbl[((s[0] & 3) << 4) + (s1 >> 4)];
61                 *p++ = tbl[((s1 & 0xf) << 2) + (s2 >> 6)];
62                 *p++ = tbl[s2 & 0x3f];
63                 s += 3;
64         }
65         /* Zero-terminate */
66         *p = '\0';
67         /* If length is -2 or -1, pad last char or two */
68         while (length) {
69                 *--p = tbl[64];
70                 length++;
71         }
72 }
73
74 /*
75  * Decode base64 encoded string. Stops on '\0'.
76  *
77  * Returns: pointer to the undecoded part of source.
78  * If points to '\0', then the source was fully decoded.
79  * (*pp_dst): advanced past the last written byte.
80  */
81 const char* FAST_FUNC decode_base64(char **pp_dst, const char *src)
82 {
83         char *dst = *pp_dst;
84         const char *src_tail;
85
86         while (1) {
87                 unsigned char six_bit[4];
88                 int count = 0;
89
90                 /* Fetch up to four 6-bit values */
91                 src_tail = src;
92                 while (count < 4) {
93                         char *table_ptr;
94                         int ch;
95
96                         /* Get next _valid_ character.
97                          * bb_uuenc_tbl_base64[] contains this string:
98                          *  0         1         2         3         4         5         6
99                          *  01234567890123456789012345678901234567890123456789012345678901234
100                          * "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
101                          */
102                         do {
103                                 ch = *src;
104                                 if (ch == '\0') {
105                                         if (count == 0) {
106                                                 /* Example:
107                                                  * If we decode "QUJD <NUL>", we want
108                                                  * to return ptr to NUL, not to ' ',
109                                                  * because we did fully decode
110                                                  * the string (to "ABC").
111                                                  */
112                                                 src_tail = src;
113                                         }
114                                         goto ret;
115                                 }
116                                 src++;
117                                 table_ptr = strchr(bb_uuenc_tbl_base64, ch);
118 //TODO: add BASE64_FLAG_foo to die on bad char?
119                         } while (!table_ptr);
120
121                         /* Convert encoded character to decimal */
122                         ch = table_ptr - bb_uuenc_tbl_base64;
123
124                         /* ch is 64 if char was '=', otherwise 0..63 */
125                         if (ch == 64)
126                                 break;
127                         six_bit[count] = ch;
128                         count++;
129                 }
130
131                 /* Transform 6-bit values to 8-bit ones.
132                  * count can be < 4 when we decode the tail:
133                  * "eQ==" -> "y", not "y NUL NUL".
134                  * Note that (count > 1) is always true,
135                  * "x===" encoding is not valid:
136                  * even a single zero byte encodes as "AA==".
137                  * However, with current logic we come here with count == 1
138                  * when we decode "==" tail.
139                  */
140                 if (count > 1)
141                         *dst++ = six_bit[0] << 2 | six_bit[1] >> 4;
142                 if (count > 2)
143                         *dst++ = six_bit[1] << 4 | six_bit[2] >> 2;
144                 if (count > 3)
145                         *dst++ = six_bit[2] << 6 | six_bit[3];
146                 /* Note that if we decode "AA==" and ate first '=',
147                  * we just decoded one char (count == 2) and now we'll
148                  * do the loop once more to decode second '='.
149                  */
150         } /* while (1) */
151  ret:
152         *pp_dst = dst;
153         return src_tail;
154 }
155
156 /*
157  * Decode base64 encoded stream.
158  * Can stop on EOF, specified char, or on uuencode-style "====" line:
159  * flags argument controls it.
160  */
161 void FAST_FUNC read_base64(FILE *src_stream, FILE *dst_stream, int flags)
162 {
163 /* Note that EOF _can_ be passed as exit_char too */
164 #define exit_char    ((int)(signed char)flags)
165 #define uu_style_end (flags & BASE64_FLAG_UU_STOP)
166
167         /* uuencoded files have 61 byte lines. Use 64 byte buffer
168          * to process line at a time.
169          */
170         enum { BUFFER_SIZE = 64 };
171
172         char in_buf[BUFFER_SIZE + 2];
173         char out_buf[BUFFER_SIZE / 4 * 3 + 2];
174         char *out_tail;
175         const char *in_tail;
176         int term_seen = 0;
177         int in_count = 0;
178
179         while (1) {
180                 while (in_count < BUFFER_SIZE) {
181                         int ch = fgetc(src_stream);
182                         if (ch == exit_char) {
183                                 if (in_count == 0)
184                                         return;
185                                 term_seen = 1;
186                                 break;
187                         }
188                         if (ch == EOF) {
189                                 term_seen = 1;
190                                 break;
191                         }
192                         /* Prevent "====" line to be split: stop if we see '\n'.
193                          * We can also skip other whitespace and skirt the problem
194                          * of files with NULs by stopping on any control char or space:
195                          */
196                         if (ch <= ' ')
197                                 break;
198                         in_buf[in_count++] = ch;
199                 }
200                 in_buf[in_count] = '\0';
201
202                 /* Did we encounter "====" line? */
203                 if (uu_style_end && strcmp(in_buf, "====") == 0)
204                         return;
205
206                 out_tail = out_buf;
207                 in_tail = decode_base64(&out_tail, in_buf);
208
209                 fwrite(out_buf, (out_tail - out_buf), 1, dst_stream);
210
211                 if (term_seen) {
212                         /* Did we consume ALL characters? */
213                         if (*in_tail == '\0')
214                                 return;
215                         /* No */
216                         bb_simple_error_msg_and_die("truncated base64 input");
217                 }
218
219                 /* It was partial decode */
220                 in_count = strlen(in_tail);
221                 memmove(in_buf, in_tail, in_count);
222         }
223 }