1 module bencode;
2 
3 import std.variant: Algebraic;
4 import std.conv: to;
5 import std.algorithm.searching: countUntil;
6 import std.array: appender, replicate;
7 import std.meta: AliasSeq, staticIndexOf;
8 import std.bigint: BigInt;
9 
10 private class BEncodeException : Exception
11 {
12     this(string msg, string file = __FILE__, size_t line = __LINE__) pure nothrow @safe @nogc
13     {
14         super(msg, file, line);
15     }
16 }
17 
18 private BStr getBStr(ref ubyte[] file) pure @trusted
19 {
20 	auto i = countUntil(file, ':');
21 	
22 	if (i == -1)
23 		throw new BEncodeException("No : symbol");
24 		
25 	auto len = to!int(cast(string)file[0 .. i]);
26 	
27 	if (len+i+1 > file.length)
28 		throw new BEncodeException("String too long");
29 	
30 	auto ret = file[i+1 .. len+i+1];
31 	file = file[len+i+1 .. $];
32 	return cast(string)ret;
33 }
34 
35 private BInt getInt(ref ubyte[] file) pure @trusted
36 {
37 	if (file[0] != 'i')
38 		throw new BEncodeException("No i symbol");
39 		
40 	auto i = countUntil(file, 'e');
41 	
42 	if (i == -1)
43 		throw new BEncodeException("No e symbol");
44 	
45 	BigInt ret;
46 	try {
47 		ret = BigInt(cast(string)file[1 .. i]);
48 	} catch (Exception ex) {
49 		throw new BEncodeException(ex.msg);
50 	}
51 	
52 	file = file[i+1 .. $];
53 	return ret;
54 }
55 
56 private enum EL_TYPE
57 {
58 	BYTE_STR,
59 	INT,
60 	LIST,
61 	DICT,
62 	END
63 }
64 
65 private EL_TYPE GetType(ref ubyte[] file) pure @safe
66 {
67 	switch (file[0]) with(EL_TYPE) {
68 		case '0': .. case '9':
69 			return BYTE_STR;
70 		case 'i':
71 			return INT;
72 		case 'l':
73 			return LIST;
74 		case 'd':
75 			return DICT;
76 		case 'e':
77 			return END;
78 		default:
79 			throw new BEncodeException("Unknown element type");
80 	}
81 }
82 
83 public struct OrderedAA(K, V) {
84 	import std.array;
85 	
86 	public struct KV
87 	{
88 		private K _key;
89 		private V _value;
90 		
91 		@property K key() pure nothrow @safe @nogc
92 		{
93 			return _key;
94 		}
95 		
96 		@property void key(K val) pure nothrow @safe @nogc
97 		{
98 			_key = val;
99 		}
100 		
101 		@property ref V value() pure nothrow @safe @nogc
102 		{
103 			return _value;
104 		}
105 		
106 		@property void value(V val) @trusted
107 		{
108 			_value = val;
109 		}
110 		
111 		this(K k, V v) pure nothrow @safe @nogc
112 		{
113 			_key = k;
114 			_value = v;
115 		}
116 	}
117 	
118 	private KV[] list;
119 	
120 	KV[] byKeyValue() pure nothrow @nogc
121 	{
122 	    return list;
123 	}
124 	
125 	auto byKey() pure nothrow @nogc
126 	{
127 		import std.algorithm.iteration;
128 	    return list.map!(x => x.key);
129 	}
130 	
131 	auto byValue() pure nothrow @nogc
132 	{
133 	    import std.algorithm.iteration;
134 	    return list.map!(x => x.value);
135 	}
136 	
137 	void opIndexAssign(V v, K k) nothrow @safe
138 	{
139 		auto i = countUntil!"a.key == b"(list, k);
140 		if (i == -1) {
141 			list ~= KV(k, v);
142 		} else {
143 			try {
144 				list[i].value = v;
145 			} catch (Exception ex) {
146 				assert(false, ex.msg);
147 			}
148 		}
149 	}
150 	
151 	KV *opBinaryRight(string op : "in")(K k) @trusted
152 	{
153 		auto i = countUntil!"a.key == b"(list, k);
154 		if (i == -1)
155 			return null;
156 		return &list[i];
157 	}
158 	
159 	KV opIndex(K k) @trusted
160 	{
161 		auto pKV = opBinaryRight!"in"(k);
162 		
163 		if (pKV is null)
164 			throw new BEncodeException("Item with key \"" ~ cast(string)k ~ "\" not found");
165 		else return *pKV;
166 	}
167 }
168 
169 alias BInt = BigInt;
170 alias BStr = string;
171 alias BDict = OrderedAA!(BStr, BElement);
172 alias BList = BElement[];
173 
174 alias BContent = Algebraic!(BAllowedTypes);
175 
176 alias BAllowedTypes = AliasSeq!(BStr, BInt, BDict, BList, BElement);
177 
178 private enum isInAllowed(T)() {
179 	return staticIndexOf!(T, BAllowedTypes) != -1 || isNumeric!T || is(T == BContent);
180 }
181 
182 import std.stdio;
183 import std.traits;
184 import std.range.primitives;
185 
186 private BElement parseRecursive(T)(T inp)
187 {
188 	auto base = new BElement();
189 	base.elSet(parseIRecursive(inp));
190 	return base;
191 }
192 
193 private BContent parseIRecursive(T)(T inp, int printLevel = 0)
194 {
195 	static if (isInAllowed!T) {
196 		static if (isNumeric!T)
197 			return BContent(BigInt(to!string(inp)));
198 		else
199 			return BContent(inp);
200 		
201 		
202 	} else static if (isAssociativeArray!T) {
203 		BDict dict;
204 		
205 		foreach (item; inp.byKeyValue()) {
206 			BElement newEl = new BElement();
207 			newEl.elSet(parseIRecursive(item.value, printLevel + 1));
208 			dict[item.key] = newEl;
209 		}
210 		
211 		return BContent(dict);
212 	} else static if (isInputRange!T && !isSomeString!T) {
213 		BList list;
214 		
215 		foreach (item; inp) {
216 			BElement newEl = new BElement();
217 			newEl.elSet(parseIRecursive(item, printLevel + 1));
218 			list ~= newEl;
219 		}
220 		
221 		return BContent(list);
222 	} else
223 		static assert(false, "Unsupported type " ~ fullyQualifiedName!T);
224 }
225 
226 public class BElement
227 {
228 	private BContent el;
229 	
230 	public void elSet(T)(T set) {
231 		if (!isInAllowed!T)
232 			throw new BEncodeException("Cannot store " ~ fullyQualifiedName!T);
233 		el = set;
234 	}
235 	
236 	public this() {  }
237 	public this(V)(V content) if (!isInAllowed!V) { el = parseRecursive(content); }
238 	public this(V)(V content) if (isInAllowed!V) {
239 		static if (isNumeric!V)
240 			el = BigInt(to!string(content));
241 		else
242 			el = content;
243 	}
244 	
245 	private string toIString(BElement el, int level)
246 	{
247 		auto ret = appender!string();
248 		
249 		if (el.el.type == typeid(BStr)) {
250 			ret.put("\t".replicate(level) ~ cast(string)*el.str() ~ "\n");
251 		} else if (el.el.type == typeid(BInt)) {
252 			ret.put("\t".replicate(level) ~ to!string(*el.integer()) ~ "\n");
253 		} else if (el.el.type == typeid(BDict)) {
254 			foreach (item; (*el.dict()).byKeyValue()) {
255 				ret.put("\t".replicate(level) ~ cast(string)item.key ~ ": " ~ "\n");
256 				ret.put(toIString(item.value, level + 1));
257 			}
258 		} else if (el.el.type == typeid(BList)) {
259 			foreach (item; *el.list()) {
260 				ret.put(toIString(item, level + 1));
261 			}
262 		} else if (el.el.type == typeid(BElement)) {
263 			ret.put(toIString(*el.element(), level));
264 		}
265 	
266 		return ret.data;
267 	}
268 	
269 	override string toString() @trusted
270 	{
271 		return toIString(this, 0);
272 	}
273 	
274 	public BStr serialize()
275 	{
276 		BStr serializeBStr(BStr inp) {
277 			return to!string(inp.length) ~ ':' ~ inp;
278 		}
279 		
280 		auto ret = appender!BStr();
281 		
282 		if (el.type == typeid(BStr)) {
283 			ret.put(serializeBStr(*bstr()));
284 		} else if (el.type == typeid(BInt))
285 			ret.put('i' ~ to!string(*integer()) ~ 'e');
286 		else if (el.type == typeid(BDict)) {
287 			ret.put('d');
288 			foreach (item; (*dict()).byKeyValue()) {
289 				ret.put(serializeBStr(item.key));
290 				ret.put(item.value.serialize());
291 			}
292 			ret.put('e');
293 		} else if (el.type == typeid(BList)) {
294 			ret.put('l');
295 			foreach (item; *list()) {
296 				ret.put(item.serialize());
297 			}
298 			ret.put('e');
299 		} else if (el.type == typeid(BElement)) {
300 			ret.put((*element()).serialize());
301 		}
302 	
303 		return ret.data;
304 	}
305 	
306 	public BElement opIndex(BStr v) {
307 		if (el.type != typeid(BDict))
308 			throw new BEncodeException("Element is not a dictionary");
309 		
310 		BDict.KV *kv = (*dict()).opBinaryRight!"in"(v);
311 		
312 		if (kv is null)
313 			return null;
314 		else
315 			return (*kv).value;
316 	}
317 	
318 	public BElement opIndex(uint v) {
319 		assert(el.type == typeid(BDict) || el.type == typeid(BList), "Element is not a dictionary");
320 		
321 		if (el.type == typeid(BDict))
322 			return ((*dict()).byValue())[v];
323 		else
324 			return (*list())[v];
325 	}
326 	
327 	BStr *bstr() { return el.peek!BStr(); }
328 	string *str() { return cast(string*)bstr(); }
329 	BInt *integer() { return el.peek!BInt(); }
330 	BDict *dict() { return el.peek!BDict(); }
331 	BList *list() { return el.peek!BList(); }
332 	BElement *element() { return el.peek!BElement(); }
333 	
334 	void opAssign(BStr str) { el = str; }
335 	void opAssign(BInt integer) { el = integer; }
336 	void opAssign(BDict dict) { el = dict; }
337 	void opAssign(BList list) { el = list; }
338 	
339 	void opOpAssign(string op : "~", V)(V v)
340 	{
341 		assert(el.type == typeid(BList), "Base type is not a list");
342 		
343 		*list() ~= parseRecursive(v);
344 	}
345 	
346 	private auto innerType(T)(T val) {
347 		static if (is (T == BElement)) {
348 			if (val.el.type == BElement)
349 				return innerType(val.el.get!BElement());
350 		}
351 		return val.type;
352 	}
353 	
354 	void opIndexOpAssign(string op : "~", V, K)(V v, K k)
355 	{
356 		assert(el.type == typeid(BDict), "Base type is not a dictionary");
357 		assert(is(K == typeof((*dict()).byKey()[0])), "Dictionary keys are not of type " ~ fullyQualifiedName!K ~ " (" ~ fullyQualifiedName!(typeof((*dict()).byKey()[0])) ~ " expected)");
358 		
359 		auto i = k in *dict();
360 		
361 		if (i !is null) {
362 			if (innerType((*i).value.el) == typeid(BList)) {
363 				(*i).value.opOpAssign!("~", V)(v);
364 			} else {
365 				static if (isAssociativeArray!V) {
366 					auto aa = (*i).value.dict();
367 					foreach(item; v.byKeyValue()) {
368 						(*aa)[item.key] = parseRecursive(item.value);
369 					}
370 				} else
371 					throw new BEncodeException("Cannot add non-associative array to dictionary");
372 			}
373 		} else
374 			(*dict())[k] = parseRecursive(v);
375 	}
376 }
377 
378 private BElement _iParse(T)(ref ubyte[] file, ref uint sC, ref uint eC)
379 {
380 	BStr curKey = null;
381 	BElement ret = new BElement();
382 	ret.el = T.init;
383 
384 	static if (is(T == BStr)) {
385 		ret.el = getBStr(file);
386 		
387 		if (file.length != 0)
388 			throw new BEncodeException("Unexpected data after a string");
389 		
390 		return ret;
391 	} else static if (is(T == BInt)) {
392 		ret.el = getInt(file);
393 		
394 		if (file.length != 0)
395 			throw new BEncodeException("Unexpected data after a int");
396 		
397 		return ret;
398 	}
399 
400 	while (file.length != 0) {
401 		auto type = GetType(file);
402 		
403 		BElement el;
404 		
405 		switch (type) with (EL_TYPE) {
406 			case BYTE_STR:
407 			case INT:
408 				if (type == BYTE_STR)
409 					el = new BElement(getBStr(file));
410 				else
411 					el = new BElement(getInt(file));
412 			
413 				if (curKey is null) {
414 					if (ret.el.type == typeid(BList)) {
415 						(*ret.list()) ~= el;
416 						continue;
417 					}
418 					
419 					if (type == BYTE_STR)
420 						curKey = *el.str();
421 					else
422 						throw new BEncodeException("Integer key at " ~ cast(string)file[0 .. $] ~ "...");
423 						
424 					continue;
425 				}
426 				
427 				if (ret.el.type == typeid(BDict))
428 					(*(ret.dict()))[curKey] = el;
429 				else
430 					throw new BEncodeException("Invalid element base container");
431 					
432 				curKey = null;
433 				break;
434 			case LIST:
435 			case DICT:
436 				sC++;
437 				file = file[1 .. $];
438 			
439 				if (type == LIST)
440 					el = new BElement(_iParse!BList(file, sC, eC).el);
441 				else
442 					el = new BElement(_iParse!BDict(file, sC, eC).el);
443 				
444 				if (ret.el.type == typeid(BList))
445 					ret.el ~= el;
446 				else if (ret.el.type == typeid(BDict)) {
447 					if (curKey is null)
448 						throw new BEncodeException("No key on dictionary");
449 					else
450 						(*(ret.dict()))[curKey] = el;
451 						
452 					curKey = null;
453 				} else
454 					throw new BEncodeException("Invalid element base container");
455 
456 				curKey = null;
457 				break;
458 			case END:
459 				eC++;
460 				file = file[1 .. $];
461 				return ret;
462 			default:
463 				throw new BEncodeException("Unknown element type");
464 		}
465 	}
466 
467 	return ret;
468 }
469 
470 BElement bencodeParse(ubyte[] file)
471 {
472 	auto type = GetType(file);
473 	
474 	uint startCount = 0, endCount = 0;
475 	
476 	BElement ret;
477 	
478 	final switch (type) with (EL_TYPE) {
479 		case BYTE_STR:
480 			ret = _iParse!BStr(file, startCount, endCount);
481 			break;
482 		case INT:
483 			ret = _iParse!BInt(file, startCount, endCount);
484 			break;
485 		case LIST:
486 			startCount++;
487 			file = file[1 .. $];
488 			ret = _iParse!BList(file, startCount, endCount);
489 			break;
490 		case DICT:
491 			startCount++;
492 			file = file[1 .. $];
493 			ret = _iParse!BDict(file, startCount, endCount);
494 			break;
495 		case END:
496 			throw new BEncodeException("End of unstarted list/dict");
497 	}
498 	
499 	if (startCount != endCount)
500 		throw new BEncodeException("d/l and e count doesn't match (start " ~ to!string(startCount) ~ ", end " ~ to!string(endCount) ~ ")");
501 		
502 	return ret;
503 }
504 
505 unittest
506 {
507 	import std.file: read;
508 	import std.algorithm.comparison: equal;
509 	import std.exception: assertThrown;
510 	
511 	auto file = cast(ubyte[])read("OnlyHuman-e-0.21.7-20170411.torrent");
512 	
513 	void testNumber() {
514 		static auto c = 0;
515 		c++;
516 		writeln("test ", c);
517 	}
518 	
519 	testNumber();
520 	auto bencode = bencodeParse(file);
521 	
522 	testNumber();
523 	assert(equal(bencode.serialize(), file));
524 	
525 	testNumber();
526 	assert(equal(*bencode["comment"].str(), "OnlyHuman-e-0.21.7-20170411"));
527 	
528 	testNumber();
529 	assert(equal(*bencode["info"]["files"][6]["path"][0].str(), "OnlyHuman-e-0.21.7-20170411.txt"));
530 	
531 	testNumber();
532 	// Trying to add value without key
533 	assertThrown!BEncodeException(bencode["info"] ~= "test");
534 	
535 	testNumber();
536 	// Add item to list - OK
537 	bencode["info"]["files"] ~= "test";
538 	
539 	testNumber();
540 	// Add list to list - OK
541 	bencode["info"]["files"] ~= [ "test2", "test3" ];
542 	
543 	testNumber();
544 	// Add dictionary to list - OK
545 	bencode["info"]["files"] ~= [ "test4": new BElement(1000), "test5": new BElement([ "very nice" ]) ];
546 	
547 	testNumber();
548 	assert(equal(bencode.serialize(), cast(string)read("mod1.torrent")));
549 	
550 	bencode = new BElement(BDict.init);
551 	
552 	testNumber(); // 10
553 	// Add key (length) and value (1000) to root dictionary
554 	bencode["length"] ~= 1000;
555 	
556 	testNumber();
557 	// Add key (very) and value (nicep) to "path" dictionary
558 	bencode["path"] ~= [ "very": "nicep" ];
559 	
560 	testNumber();
561 	// Add key (very good) and value (nice) to "path" dictionary
562 	bencode["path"] ~= [ "very good": "nice" ];
563 	
564 	testNumber();
565 	// Trying to add value to "path" dictionary
566 	assertThrown!BEncodeException(bencode["path"] ~= "nice");
567 	
568 	testNumber();
569 	// Overwrite nicep
570 	bencode["path"] ~= [ "very": "nice" ];
571 	
572 	testNumber();
573 	assert(bencode.serialize() == "d6:lengthi1000e4:pathd4:very4:nice9:very good4:niceee");
574 	
575 	testNumber();
576 	assert(bencodeParse(cast(ubyte[])"d4:spam3:egge").serialize() == "d4:spam3:egge");
577 	
578 	testNumber(); // 17
579 	assert(bencodeParse(cast(ubyte[])"de").serialize() == "de");
580 	
581 	auto invalid = [ "d", "ade", ":de" /* 20 */, "-de", "1de", "d4:spam3:egg", "d ", "l-something-e", "4", "a", ":", "-", "." /* 30 */, "e", "l", "l4:spam", "l ", "l:",
582 		"1e", "1234567890e", "iasdfe", "ie", "5:spam" /* 40 */, "100000:spam", "4spam", "10spam", "4;spam", "spam", "interesting", "0", "1:spam" ];
583 	auto valid = [ "l4:spame", "l4:spami42ee" /* 50 */, "le", "i0e", "i1e", "i3e", "i42e", "i100e", "i1234567890e", "i165183413841846846518341685135165e",
584 		"i-0e", "i-1e" /* 60 */, "i-3e", "i-42e", "i-100e", "i-1234567890e", "i-165183413841846846518341685135165e", "4:spam", "9:spam:eggs", "3:\x00\x01\x00", "i-e" /* 69 */, "d0:i1ee" ];
585 	
586 	foreach (item; invalid) {
587 		testNumber();
588 		assertThrown!BEncodeException(bencodeParse(cast(ubyte[])item));
589 	}
590 		
591 	foreach (item; valid) {
592 		testNumber();
593 		bencodeParse(cast(ubyte[])item);
594 	}
595 }