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 	/// private
74 	this(OutputStream output, ulong max_bytes_per_line, bool dummy)
75 	{
76 		m_out = output;
77 		m_maxBytesPerLine = max_bytes_per_line;
78 	}
79 
80 	static if (is(typeof(.OutputStream.outputStreamVersion)) && .OutputStream.outputStreamVersion > 1) {
81 		size_t write(scope const(ubyte)[] bytes_, IOMode mode) @safe { return doWrite(bytes_, mode); }
82 	} else {
83 		size_t write(in ubyte[] bytes_, IOMode mode) @safe { return doWrite(bytes_, mode); }
84 	}
85 
86 	private size_t doWrite(scope const(ubyte)[] bytes_, IOMode)
87 	@trusted { // StreamOutputRange is not @safe
88 		import vibe.stream.wrapper;
89 
90 		const(ubyte)[] bytes = bytes_;
91 
92 		auto rng = streamOutputRange(m_out);
93 
94 		size_t nwritten = 0;
95 
96 		while (bytes.length > 0) {
97 			if (m_bytesInCurrentLine + bytes.length >= m_maxBytesPerLine) {
98 				size_t bts = cast(size_t)(m_maxBytesPerLine - m_bytesInCurrentLine);
99 				B64.encode(bytes[0 .. bts], &rng);
100 				rng.put("\r\n");
101 				bytes = bytes[bts .. $];
102 				m_bytesInCurrentLine = 0;
103 				nwritten += bts;
104 			} else {
105 				B64.encode(bytes, &rng);
106 				m_bytesInCurrentLine += bytes.length;
107 				nwritten += bytes.length;
108 				break;
109 			}
110 		}
111 
112 		return nwritten;
113 	}
114 
115 	alias write = .OutputStream.write;
116 
117 	void flush()
118 	{
119 		m_out.flush();
120 	}
121 
122 	void finalize()
123 	{
124 		flush();
125 	}
126 }
127 
128 /+
129 /**
130 	MIME compatible Base64 decoding stream.
131 */
132 alias Base64InputStream = Base64InputStreamImpl!('+', '/');
133 
134 /**
135 	URL safe Base64 decoding stream (using '-' and '_' for non-alphabetic values).
136 */
137 alias Base64URLInputStream = Base64InputStreamImpl!('-', '_');
138 class Base64InputStream(char C62, char C63, char CPAD = '=') : InputStream {
139 	private {
140 		InputStream m_in;
141 		FixedRingBuffer!(ubyte, 1024) m_buffer;
142 	}
143 
144 	private alias B64 = Base64Impl!(C62, C63, CPAD);
145 
146 	this(InputStream input)
147 	{
148 		m_in = input;
149 		fillBuffer();
150 	}
151 
152 	bool empty() const { return m_buffer.empty; }
153 	ulong leastSize() const { return m_buffer.length; }
154 	const(ubyte)[] peek() const { return m_buffer.peek; }
155 
156 	void read(ubyte[] dst)
157 	{
158 		ubyte[74] inbuf;
159 		while (!dst.empty) {
160 			enforce(!empty, "Reading past end of base-64 stream.");
161 			auto sz = min(dst.length, m_buffer.length);
162 			m_buffer.read(dst[0 .. sz]);
163 			dst = dst[sz .. $];
164 			fillBuffer();
165 		}
166 	}
167 
168 	private void fillBuffer()
169 	{
170 		ubyte[74] buf;
171 		size_t buf_fill = 0;
172 		while (!m_buffer.full || !m_in.empty) {
173 			auto insz = m_in.leastSize;
174 			auto sz = min(insz, (m_buffer.freeSpace/3)*4);
175 			if (sz == 0) {
176 				m_in.read(buf[buf_fill .. buf_fill+insz]);
177 				buf_fill += insz;
178 			}
179 			auto csz = min(sz, buf.length);
180 			m_in.read(buf[0 .. csz])
181 			B64.decode();
182 
183 			m_in.read(m_buffer.peekDst[0 .. min(sz, $)]);
184 		}
185 	}
186 }
187 +/
188 
189 unittest {
190 	void test(in ubyte[] data, string encoded, ulong bytes_per_line = 57)
191 	{
192 		import vibe.stream.memory;
193 		auto encstr = createMemoryOutputStream();
194 		auto bostr = createBase64OutputStream(encstr, bytes_per_line);
195 		bostr.write(data);
196 		assert(encstr.data == encoded);
197 		/*encstr.seek(0);
198 		auto bistr = new Base64InputStream(encstr);
199 		assert(bistr.readAll() == data);*/
200 	}
201 
202 	test([0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e], "FPucA9l+");
203 
204 	ubyte[200] data;
205 	foreach (i, ref b; data) b = (i * 1337) % 256;
206 	string encoded =
207 		"ADlyq+QdVo/IATpzrOUeV5DJAjt0reYfWJHKAzx1rucgWZLLBD12r+ghWpPMBT53sOkiW5TNBj94\r\n" ~
208 		"seojXJXOB0B5suskXZbPCEF6s+wlXpfQCUJ7tO0mX5jRCkN8te4nYJnSC0R9tu8oYZrTDEV+t/Ap\r\n" ~
209 		"YpvUDUZ/uPEqY5zVDkeAufIrZJ3WD0iBuvMsZZ7XEEmCu/QtZp/YEUqDvPUuZ6DZEkuEvfYvaKHa\r\n" ~
210 		"E0yFvvcwaaLbFE2Gv/gxaqPcFU6HwPkya6TdFk8=";
211 	test(data, encoded);
212 }