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 }