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