1/*-
2 * Copyright (c) 2012 Michihiro NAKAJIMA
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "archive_platform.h"
27
28#ifdef HAVE_ERRNO_H
29#include <errno.h>
30#endif
31#ifdef HAVE_STDLIB_H
32#include <stdlib.h>
33#endif
34#ifdef HAVE_STRING_H
35#include <string.h>
36#endif
37
38#include "archive.h"
39#include "archive_private.h"
40#include "archive_string.h"
41#include "archive_write_private.h"
42
43#define LBYTES 45
44
45struct private_uuencode {
46	int			mode;
47	struct archive_string	name;
48	struct archive_string	encoded_buff;
49	size_t			bs;
50	size_t			hold_len;
51	unsigned char		hold[LBYTES];
52};
53
54static int archive_filter_uuencode_options(struct archive_write_filter *,
55    const char *, const char *);
56static int archive_filter_uuencode_open(struct archive_write_filter *);
57static int archive_filter_uuencode_write(struct archive_write_filter *,
58    const void *, size_t);
59static int archive_filter_uuencode_close(struct archive_write_filter *);
60static int archive_filter_uuencode_free(struct archive_write_filter *);
61static void uu_encode(struct archive_string *, const unsigned char *, size_t);
62static int64_t atol8(const char *, size_t);
63
64/*
65 * Add a compress filter to this write handle.
66 */
67int
68archive_write_add_filter_uuencode(struct archive *_a)
69{
70	struct archive_write *a = (struct archive_write *)_a;
71	struct archive_write_filter *f = __archive_write_allocate_filter(_a);
72	struct private_uuencode *state;
73
74	archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC,
75	    ARCHIVE_STATE_NEW, "archive_write_add_filter_uu");
76
77	state = (struct private_uuencode *)calloc(1, sizeof(*state));
78	if (state == NULL) {
79		archive_set_error(f->archive, ENOMEM,
80		    "Can't allocate data for uuencode filter");
81		return (ARCHIVE_FATAL);
82	}
83	archive_strcpy(&state->name, "-");
84	state->mode = 0644;
85
86	f->data = state;
87	f->name = "uuencode";
88	f->code = ARCHIVE_FILTER_UU;
89	f->open = archive_filter_uuencode_open;
90	f->options = archive_filter_uuencode_options;
91	f->write = archive_filter_uuencode_write;
92	f->close = archive_filter_uuencode_close;
93	f->free = archive_filter_uuencode_free;
94
95	return (ARCHIVE_OK);
96}
97
98/*
99 * Set write options.
100 */
101static int
102archive_filter_uuencode_options(struct archive_write_filter *f, const char *key,
103    const char *value)
104{
105	struct private_uuencode *state = (struct private_uuencode *)f->data;
106
107	if (strcmp(key, "mode") == 0) {
108		if (value == NULL) {
109			archive_set_error(f->archive, ARCHIVE_ERRNO_MISC,
110			    "mode option requires octal digits");
111			return (ARCHIVE_FAILED);
112		}
113		state->mode = (int)atol8(value, strlen(value)) & 0777;
114		return (ARCHIVE_OK);
115	} else if (strcmp(key, "name") == 0) {
116		if (value == NULL) {
117			archive_set_error(f->archive, ARCHIVE_ERRNO_MISC,
118			    "name option requires a string");
119			return (ARCHIVE_FAILED);
120		}
121		archive_strcpy(&state->name, value);
122		return (ARCHIVE_OK);
123	}
124
125	/* Note: The "warn" return is just to inform the options
126	 * supervisor that we didn't handle it.  It will generate
127	 * a suitable error if no one used this option. */
128	return (ARCHIVE_WARN);
129}
130
131/*
132 * Setup callback.
133 */
134static int
135archive_filter_uuencode_open(struct archive_write_filter *f)
136{
137	struct private_uuencode *state = (struct private_uuencode *)f->data;
138	size_t bs = 65536, bpb;
139
140	if (f->archive->magic == ARCHIVE_WRITE_MAGIC) {
141		/* Buffer size should be a multiple number of the of bytes
142		 * per block for performance. */
143		bpb = archive_write_get_bytes_per_block(f->archive);
144		if (bpb > bs)
145			bs = bpb;
146		else if (bpb != 0)
147			bs -= bs % bpb;
148	}
149
150	state->bs = bs;
151	if (archive_string_ensure(&state->encoded_buff, bs + 512) == NULL) {
152		archive_set_error(f->archive, ENOMEM,
153		    "Can't allocate data for uuencode buffer");
154		return (ARCHIVE_FATAL);
155	}
156
157	archive_string_sprintf(&state->encoded_buff, "begin %o %s\n",
158	    state->mode, state->name.s);
159
160	f->data = state;
161	return (0);
162}
163
164static void
165uu_encode(struct archive_string *as, const unsigned char *p, size_t len)
166{
167	int c;
168
169	c = (int)len;
170	archive_strappend_char(as, c?c + 0x20:'`');
171	for (; len >= 3; p += 3, len -= 3) {
172		c = p[0] >> 2;
173		archive_strappend_char(as, c?c + 0x20:'`');
174		c = ((p[0] & 0x03) << 4) | ((p[1] & 0xf0) >> 4);
175		archive_strappend_char(as, c?c + 0x20:'`');
176		c = ((p[1] & 0x0f) << 2) | ((p[2] & 0xc0) >> 6);
177		archive_strappend_char(as, c?c + 0x20:'`');
178		c = p[2] & 0x3f;
179		archive_strappend_char(as, c?c + 0x20:'`');
180	}
181	if (len > 0) {
182		c = p[0] >> 2;
183		archive_strappend_char(as, c?c + 0x20:'`');
184		c = (p[0] & 0x03) << 4;
185		if (len == 1) {
186			archive_strappend_char(as, c?c + 0x20:'`');
187			archive_strappend_char(as, '`');
188			archive_strappend_char(as, '`');
189		} else {
190			c |= (p[1] & 0xf0) >> 4;
191			archive_strappend_char(as, c?c + 0x20:'`');
192			c = (p[1] & 0x0f) << 2;
193			archive_strappend_char(as, c?c + 0x20:'`');
194			archive_strappend_char(as, '`');
195		}
196	}
197	archive_strappend_char(as, '\n');
198}
199
200/*
201 * Write data to the encoded stream.
202 */
203static int
204archive_filter_uuencode_write(struct archive_write_filter *f, const void *buff,
205    size_t length)
206{
207	struct private_uuencode *state = (struct private_uuencode *)f->data;
208	const unsigned char *p = buff;
209	int ret = ARCHIVE_OK;
210
211	if (length == 0)
212		return (ret);
213
214	if (state->hold_len) {
215		while (state->hold_len < LBYTES && length > 0) {
216			state->hold[state->hold_len++] = *p++;
217			length--;
218		}
219		if (state->hold_len < LBYTES)
220			return (ret);
221		uu_encode(&state->encoded_buff, state->hold, LBYTES);
222		state->hold_len = 0;
223	}
224
225	for (; length >= LBYTES; length -= LBYTES, p += LBYTES)
226		uu_encode(&state->encoded_buff, p, LBYTES);
227
228	/* Save remaining bytes. */
229	if (length > 0) {
230		memcpy(state->hold, p, length);
231		state->hold_len = length;
232	}
233	while (archive_strlen(&state->encoded_buff) >= state->bs) {
234		ret = __archive_write_filter(f->next_filter,
235		    state->encoded_buff.s, state->bs);
236		memmove(state->encoded_buff.s,
237		    state->encoded_buff.s + state->bs,
238		    state->encoded_buff.length - state->bs);
239		state->encoded_buff.length -= state->bs;
240	}
241
242	return (ret);
243}
244
245
246/*
247 * Finish the compression...
248 */
249static int
250archive_filter_uuencode_close(struct archive_write_filter *f)
251{
252	struct private_uuencode *state = (struct private_uuencode *)f->data;
253
254	/* Flush remaining bytes. */
255	if (state->hold_len != 0)
256		uu_encode(&state->encoded_buff, state->hold, state->hold_len);
257	archive_string_sprintf(&state->encoded_buff, "`\nend\n");
258	/* Write the last block */
259	archive_write_set_bytes_in_last_block(f->archive, 1);
260	return __archive_write_filter(f->next_filter,
261	    state->encoded_buff.s, archive_strlen(&state->encoded_buff));
262}
263
264static int
265archive_filter_uuencode_free(struct archive_write_filter *f)
266{
267	struct private_uuencode *state = (struct private_uuencode *)f->data;
268
269	archive_string_free(&state->name);
270	archive_string_free(&state->encoded_buff);
271	free(state);
272	return (ARCHIVE_OK);
273}
274
275static int64_t
276atol8(const char *p, size_t char_cnt)
277{
278	int64_t l;
279	int digit;
280
281	l = 0;
282	while (char_cnt-- > 0) {
283		if (*p >= '0' && *p <= '7')
284			digit = *p - '0';
285		else
286			break;
287		p++;
288		l <<= 3;
289		l |= digit;
290	}
291	return (l);
292}
293
294