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 }