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 }