1 //this file is part of MimeTools (plugin for Notepad++) 2 //Copyright (C)2019 Don HO <don.h@free.fr> 3 // 4 //This program is free software; you can redistribute it and/or 5 //modify it under the terms of the GNU General Public License 6 //as published by the Free Software Foundation; either 7 //version 2 of the License, or (at your option) any later version. 8 // 9 //This program is distributed in the hope that it will be useful, 10 //but WITHOUT ANY WARRANTY; without even the implied warranty of 11 //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 //GNU General Public License for more details. 13 // 14 //You should have received a copy of the GNU General Public License 15 //along with this program; if not, write to the Free Software 16 //Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 module npp_mimetools.qp; 18 19 20 private static import core.stdc.string; 21 22 enum QP_ENCODED_LINE_LEN_MAX = 76; 23 enum line_buf_length = QP_ENCODED_LINE_LEN_MAX + 2 + 1; 24 25 struct QuotedPrintable 26 { 27 public: 28 pure nothrow 29 string encode(const char[] str) 30 31 in 32 { 33 assert(str[$ - 1] == '\0'); 34 } 35 36 out(result) 37 { 38 if (result != null) { 39 assert(result[$ - 1] == '\0'); 40 } 41 } 42 43 do 44 { 45 this.initVar(); 46 this._bufLen = str.length * 2; 47 this._buffer = new char[this._bufLen]; 48 size_t len = core.stdc..string.strlen(&(str[0])); 49 50 for (size_t i = 0 ; i < len ; i++) { 51 this.getQPChar(str[i]); 52 this.putQPChar(); 53 } 54 55 this._buffer[this._i] = '\0'; 56 this._i++; 57 58 return this._buffer[0 .. this._i].idup; 59 } 60 61 pure nothrow 62 string decode(const char[] str) 63 64 in 65 { 66 assert(str[$ - 1] == '\0'); 67 } 68 69 out(result) 70 { 71 if (result != null) { 72 assert(result[$ - 1] == '\0'); 73 } 74 } 75 76 do 77 { 78 this.initVar(); 79 char[.line_buf_length] line; 80 this._bufLen = str.length + 1; 81 this._buffer = new char[this._bufLen]; 82 size_t len = core.stdc..string.strlen(&(str[0])); 83 84 for (size_t j = 0; (j < len) && (str[j] != '\0'); ) { 85 if (this.readQPLine(str, j, line) == -1) { 86 return null; 87 } 88 89 if (!this.translate(line)) { 90 return null; 91 } 92 } 93 94 this._buffer[this._i] = '\0'; 95 this._i++; 96 97 return this._buffer[0 .. this._i].idup; 98 } 99 100 private: 101 char[] _buffer; 102 size_t _bufLen; 103 size_t _i; 104 int _nbCharInLine; 105 106 int _nbChar; 107 char[4] _chars; 108 109 pure nothrow @nogc 110 int readQPLine(const char[] pStr, ref size_t j, ref char[.line_buf_length] lineBuf) 111 112 in 113 { 114 assert(pStr[$ - 1] == '\0'); 115 } 116 117 do 118 { 119 size_t i = 0; 120 size_t len = core.stdc..string.strlen(&(pStr[j])); 121 122 for (; i < len ; i++) { 123 if (i >= (.line_buf_length)) { 124 return -1; 125 } 126 127 char c = pStr[j + i]; 128 129 if (c == 0x0D) { 130 lineBuf[i] = c; 131 i++; 132 133 if ((i >= len) || (i >= (.line_buf_length))) { 134 return -1; 135 } 136 137 if (pStr[j + i] != cast(char)(0x0A)) { 138 return -1; 139 } 140 141 lineBuf[i] = pStr[j + i]; 142 i++; 143 144 if (i >= (.line_buf_length)) { 145 return -1; 146 } 147 148 lineBuf[i] = '\0'; 149 j += i; 150 151 // Make sure there's no soft line break. 152 if ((i >= 3) && (lineBuf[i - 3] == '=')) { 153 lineBuf[i - 3] = '\0'; 154 155 return cast(int)(i - 3); 156 } 157 158 return cast(int)(i); 159 } else if (c == 0x0A) { 160 return -1; 161 } else { 162 lineBuf[i] = c; 163 } 164 } 165 166 j += i; 167 lineBuf[i] = '\0'; 168 169 return cast(int)(i); 170 } 171 172 pure nothrow @nogc 173 bool translate(ref char[.line_buf_length] line2Trans) 174 175 in 176 { 177 bool is_null_found = false; 178 179 for (size_t i = 0; i < line2Trans.length; i++) { 180 if (line2Trans[i] == '\0') { 181 is_null_found = true; 182 183 break; 184 } 185 } 186 187 assert(is_null_found); 188 } 189 190 do 191 { 192 size_t len = core.stdc..string.strlen(&(line2Trans[0])); 193 194 for (size_t i = 0 ; i < len ; i++) { 195 if (line2Trans[i] == '=') { 196 if ((i == len) || ((i + 1) == len) || ((i + 2) == len)) { 197 return false; 198 } 199 200 ubyte restoredChar; 201 // 202 203 restoredChar = this.makeChar(line2Trans[i + 1], line2Trans[i + 2]); 204 i += 2; 205 206 if (!restoredChar) { 207 return false; 208 } 209 210 this._buffer[this._i] = restoredChar; 211 this._i++; 212 } else { 213 this._buffer[this._i] = line2Trans[i]; 214 this._i++; 215 } 216 } 217 218 return true; 219 } 220 221 pure nothrow @safe 222 void putQPChar() 223 224 do 225 { 226 // it happens rarely, but it happens 227 if (this._i >= this._bufLen) { 228 size_t oldLen = this._bufLen; 229 this._bufLen *= 2; 230 char[] newBuf = new char[this._bufLen]; 231 232 for (size_t i = 0 ; i < oldLen ; i++) { 233 newBuf[i] = this._buffer[i]; 234 } 235 236 //char* tmp = &(this._buffer[0]); 237 this._buffer = newBuf; 238 } 239 240 for (size_t i = 0 ; i < this._nbChar ; i++) { 241 this._buffer[this._i++] = this._chars[i]; 242 } 243 } 244 245 pure nothrow @safe @nogc 246 void getQPChar(char c) 247 248 do 249 { 250 bool crlf = false; 251 252 if (((c != '=') && (c > 32) && (c < 127)) || (c == ' ') || (c == ' ') || (c == 0x0D)) { 253 this._chars[0] = c; 254 this._nbChar = 1; 255 } else if (c == 0x0A) { 256 this._chars[0] = c; 257 this._nbChar = 1; 258 crlf = true; 259 } else { 260 ubyte uc = c; 261 this._chars[0] = '='; 262 this._chars[1] = this.toChar(uc >> 4); 263 this._chars[2] = this.toChar(uc & 15); 264 this._chars[3] = '\0'; 265 this._nbChar = 3; 266 } 267 268 if (crlf) { 269 this._nbCharInLine = this._nbChar; 270 } else { 271 this._nbCharInLine += this._nbChar; 272 } 273 274 if (this._nbCharInLine >= 76) { 275 this._buffer[this._i++] = '='; 276 this._buffer[this._i++] = 0x0D; 277 this._buffer[this._i++] = 0x0A; 278 this._nbCharInLine = this._nbChar; 279 } 280 } 281 282 //core.stdc.stdint.int32_t charToDigit(char c) const 283 pure nothrow @safe @nogc 284 int charToDigit(char c) const 285 286 do 287 { 288 if ((c >= '0') && (c <= '9')) { 289 return (c - '0'); 290 } 291 292 if ((c >= 'A') && (c <= 'F')) { 293 return (10 + c - 'A'); 294 } 295 296 return -1; 297 } 298 299 pure nothrow @safe @nogc 300 ubyte makeChar(char hiChar, char loChar) const 301 302 do 303 { 304 auto hi = this.charToDigit(hiChar); 305 306 if (hi == -1) { 307 return 0; 308 } 309 310 auto lo = this.charToDigit(loChar); 311 312 if (lo == -1) { 313 return 0; 314 } 315 316 return cast(ubyte)(hi << 4 | lo); 317 } 318 319 pure nothrow @safe @nogc 320 void initVar() 321 322 do 323 { 324 if (this._buffer != null) { 325 this._buffer = null; 326 } 327 328 this._bufLen = 0; 329 this._i = 0; 330 this._nbChar = 0; 331 this._nbCharInLine = 0; 332 } 333 334 pure nothrow @safe @nogc 335 char toChar(int i) const 336 337 in 338 { 339 assert(i >= 0); 340 } 341 342 do 343 { 344 if (i < 10) { 345 return cast(char)('0' + i); 346 } else { 347 return cast(char)('A' + i - 10); 348 } 349 } 350 }