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 }