1 /**
2 	Base64 encoding routines
3 
4 	Copyright: © 2012-2016 Sönke Ludwig
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Jan Krüger, Sönke Ludwig
7 */
8 module vibe.stream.base64;
9 
10 import vibe.core.stream;
11 
12 import std.base64;
13 
14 /** Creates a Base64 encoding stream.max_bytes_per_line
15 
16 	By default, the stream generates a MIME compatible Base64 encoding.
17 
18 	Params:
19 		output = The output sink to which the encoded result is written.
20 		max_bytes_per_line = The maximum number of input bytes after which a
21 			line break is inserted into the output. Defaults to 57,
22 			according to the MIME standard.
23 */
24 Base64OutputStreamImpl!(C62, C63, CPAD, OutputStream) createBase64OutputStream(char C62 = '+', char C63 = '/', char CPAD = '=', OutputStream)(OutputStream output, ulong max_bytes_per_line = 57)
25 	if (isOutputStream!OutputStream)
26 {
27 	return new Base64OutputStreamImpl!(C62, C63, CPAD, OutputStream)(output, max_bytes_per_line, true);
28 }
29 
30 /** Creates a URL safe Base64 encoding stream (using '-' and '_' for non-alphabetic values).
31 
32 	Params:
33 		output = The output sink to which the encoded result is written.
34 		max_bytes_per_line = The maximum number of input bytes after which a
35 			line break is inserted into the output. Defaults to 57,
36 			according to the MIME standard.
37 */
38 Base64OutputStreamImpl!('-', '_', '=', OutputStream) createBase64URLOutputStream(OutputStream)(OutputStream output, ulong max_bytes_per_line = 57)
39 	if (isOutputStream!OutputStream)
40 {
41 	return craeteBase64OutputStream!('-', '_')(output, max_bytes_per_line);
42 }
43 
44 
45 /**
46 	MIME compatible Base64 encoding stream.
47 */
48 alias Base64OutputStream = Base64OutputStreamImpl!('+', '/');
49 
50 /**
51 	URL safe Base64 encoding stream (using '-' and '_' for non-alphabetic values).
52 */
53 alias Base64URLOutputStream = Base64OutputStreamImpl!('-', '_');
54 
55 /**
56 	Generic Base64 encoding output stream.
57 
58 	The template arguments C62 and C63 determine which non-alphabetic characters
59 	are used to represent the 62nd and 63rd code units. CPAD is the character
60 	used for padding the end of the result if necessary.
61 */
62 final class Base64OutputStreamImpl(char C62, char C63, char CPAD = '=', OutputStream = .OutputStream) : .OutputStream
63 	if (isOutputStream!OutputStream)
64 {
65 	private {
66 		OutputStream m_out;
67 		ulong m_maxBytesPerLine;
68 		ulong m_bytesInCurrentLine = 0;
69 	}
70 
71 	private alias B64 = Base64Impl!(C62, C63, CPAD);
72 
73 	deprecated("Use `createBase64OutputStream` or `createBase64URLOutputStream` instead.")
74 	this(OutputStream output, ulong max_bytes_per_line = 57)
75 	{
76 		this(output, max_bytes_per_line, true);
77 	}
78 
79 	/// private
80 	this(OutputStream output, ulong max_bytes_per_line, bool dummy)
81 	{
82 		m_out = output;
83 		m_maxBytesPerLine = max_bytes_per_line;
84 	}
85 
86 
87 	size_t write(in ubyte[] bytes_, IOMode)
88 	@trusted { // StreamOutputRange is not @safe
89 		import vibe.stream.wrapper;
90 
91 		const(ubyte)[] bytes = bytes_;
92 
93 		auto rng = streamOutputRange(m_out);
94 
95 		size_t nwritten = 0;
96 
97 		while (bytes.length > 0) {
98 			if (m_bytesInCurrentLine + bytes.length >= m_maxBytesPerLine) {
99 				size_t bts = cast(size_t)(m_maxBytesPerLine - m_bytesInCurrentLine);
100 				B64.encode(bytes[0 .. bts], &rng);
101 				rng.put("\r\n");
102 				bytes = bytes[bts .. $];
103 				m_bytesInCurrentLine = 0;
104 				nwritten += bts;
105 			} else {
106 				B64.encode(bytes, &rng);
107 				m_bytesInCurrentLine += bytes.length;
108 				nwritten += bytes.length;
109 				break;
110 			}
111 		}
112 
113 		return nwritten;
114 	}
115 
116 	alias write = .OutputStream.write;
117 
118 	void flush()
119 	{
120 		m_out.flush();
121 	}
122 
123 	void finalize()
124 	{
125 		flush();
126 	}
127 }
128 
129 /+
130 /**
131 	MIME compatible Base64 decoding stream.
132 */
133 alias Base64InputStream = Base64InputStreamImpl!('+', '/');
134 
135 /**
136 	URL safe Base64 decoding stream (using '-' and '_' for non-alphabetic values).
137 */
138 alias Base64URLInputStream = Base64InputStreamImpl!('-', '_');
139 class Base64InputStream(char C62, char C63, char CPAD = '=') : InputStream {
140 	private {
141 		InputStream m_in;
142 		FixedRingBuffer!(ubyte, 1024) m_buffer;
143 	}
144 
145 	private alias B64 = Base64Impl!(C62, C63, CPAD);
146 
147 	this(InputStream input)
148 	{
149 		m_in = input;
150 		fillBuffer();
151 	}
152 
153 	bool empty() const { return m_buffer.empty; }
154 	ulong leastSize() const { return m_buffer.length; }
155 	const(ubyte)[] peek() const { return m_buffer.peek; }
156 
157 	void read(ubyte[] dst)
158 	{
159 		ubyte[74] inbuf;
160 		while (!dst.empty) {
161 			enforce(!empty, "Reading past end of base-64 stream.");
162 			auto sz = min(dst.length, m_buffer.length);
163 			m_buffer.read(dst[0 .. sz]);
164 			dst = dst[sz .. $];
165 			fillBuffer();
166 		}
167 	}
168 
169 	private void fillBuffer()
170 	{
171 		ubyte[74] buf;
172 		size_t buf_fill = 0;
173 		while (!m_buffer.full || !m_in.empty) {
174 			auto insz = m_in.leastSize;
175 			auto sz = min(insz, (m_buffer.freeSpace/3)*4);
176 			if (sz == 0) {
177 				m_in.read(buf[buf_fill .. buf_fill+insz]);
178 				buf_fill += insz;
179 			}
180 			auto csz = min(sz, buf.length);
181 			m_in.read(buf[0 .. csz])
182 			B64.decode();
183 
184 			m_in.read(m_buffer.peekDst[0 .. min(sz, $)]);
185 		}
186 	}
187 }
188 +/
189 
190 unittest {
191 	void test(in ubyte[] data, string encoded, ulong bytes_per_line = 57)
192 	{
193 		import vibe.stream.memory;
194 		auto encstr = createMemoryOutputStream();
195 		auto bostr = createBase64OutputStream(encstr, bytes_per_line);
196 		bostr.write(data);
197 		assert(encstr.data == encoded);
198 		/*encstr.seek(0);
199 		auto bistr = new Base64InputStream(encstr);
200 		assert(bistr.readAll() == data);*/
201 	}
202 
203 	test([0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e], "FPucA9l+");
204 
205 	ubyte[200] data;
206 	foreach (i, ref b; data) b = (i * 1337) % 256;
207 	string encoded =
208 		"ADlyq+QdVo/IATpzrOUeV5DJAjt0reYfWJHKAzx1rucgWZLLBD12r+ghWpPMBT53sOkiW5TNBj94\r\n" ~
209 		"seojXJXOB0B5suskXZbPCEF6s+wlXpfQCUJ7tO0mX5jRCkN8te4nYJnSC0R9tu8oYZrTDEV+t/Ap\r\n" ~
210 		"YpvUDUZ/uPEqY5zVDkeAufIrZJ3WD0iBuvMsZZ7XEEmCu/QtZp/YEUqDvPUuZ6DZEkuEvfYvaKHa\r\n" ~
211 		"E0yFvvcwaaLbFE2Gv/gxaqPcFU6HwPkya6TdFk8=";
212 	test(data, encoded);
213 }