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 }