1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 // some code to process bittorrent files
19 module iv
.btenc
/*is aliced*/;
23 import iv
.strex
: quote
;
27 enum Type
{ UInt
, Str
, List
, Dict
}
28 Type type
= Type
.UInt
;
32 BTField
[string
] vdict
;
34 this (ulong v
) @safe nothrow @nogc { type
= Type
.UInt
; vuint
= v
; }
35 this (string v
) @safe nothrow @nogc { type
= Type
.Str
; vstr
= v
; }
36 this (const(ubyte[]) v
) @safe nothrow { type
= Type
.Str
; vstr
= cast(string
)(v
.idup
); }
38 const @safe pure nothrow @nogc {
39 @property const(ubyte)[] bytes () { return cast(const(ubyte)[])vstr
; }
40 @property bool isUInt () { return (type
== Type
.UInt
); }
41 @property bool isStr () { return (type
== Type
.Str
); }
42 @property bool isList () { return (type
== Type
.List
); }
43 @property bool isDict () { return (type
== Type
.Dict
); }
46 static BTField
load (string fname
) {
47 import std
.stdio
: File
;
48 return load(File(fname
));
51 static BTField
load(ST
) (auto ref ST st
) if (isReadableStream
!ST
) {
53 st
.rawReadExact((&tch
)[0..1]);
54 return loadElement(st
, tch
);
57 void save (string fname
) {
58 import std
.stdio
: File
;
59 return save(File(fname
, "w"));
62 void save(ST
) (auto ref ST st
) const if (isWriteableStream
!ST
) {
63 void putNum (ulong n
) {
65 // 18446744073709551615
67 usize pos
= str.length
;
68 do str[--pos
] = cast(char)('0'+n
%10); while ((n
/= 10) != 0);
69 st
.rawWriteExact(str[pos
..$]);
72 void putChar (char ch
) {
73 st
.rawWriteExact((&ch
)[0..1]);
76 //{ import std.conv : to; import iv.writer; writeln(to!string(type)); }
77 if (type
== Type
.UInt
) {
81 } else if (type
== Type
.Str
) {
84 st
.rawWriteExact(vstr
);
85 } else if (type
== Type
.List
) {
87 foreach (const ref BTField
fld; vlist
) fld.save(st
);
89 } else if (type
== Type
.Dict
) {
90 // we have to sort keys by standard
91 import std
.algorithm
: sort
;
93 foreach (string key
; sort(vdict
.keys
)) {
96 st
.rawWriteExact(key
);
105 ref BTField
opIndex (string path
) {
106 auto res
= byPath
!"throw"(path
);
110 BTField
* opBinaryRight(string op
="in") (string path
) { return byPath(path
); }
114 BTField
* byPath(string opt
="") (string path
) {
115 static assert(opt
.length
== 0 || opt
== "throw", "invalid mode; only 'throw' allowed");
117 static if (opt
== "throw") {
118 auto origpath
= path
;
120 void error (string msg
=null) {
121 //import iv.strex : quote;
122 if (msg
.length
== 0) {
123 throw new Exception("invalid path: "~quote(origpath
));
125 throw new Exception(msg
~" at path: "~quote(origpath
));
131 while (path
.length
> 0) {
132 while (path
.length
> 0 && path
[0] == '/') path
= path
[1..$];
133 if (path
.length
== 0) break;
136 while (epos
< path
.length
&& path
[epos
] != '/') ++epos
;
137 auto part
= path
[0..epos
];
138 path
= path
[epos
..$];
139 if (cf
.type
== Type
.List
) {
140 // this should be number
142 if (part
[0] == '-') { neg = true; part
= part
[1..$]; }
143 else if (part
[0] == '+') part
= part
[1..$];
144 if (part
.length
== 0) {
145 static if (opt
== "throw") error("invalid number");
149 while (part
.length
> 0) {
150 if (part
[0] < '0' || part
[0] > '9') {
151 static if (opt
== "throw") error("invalid number");
156 static if (opt
== "throw") error("numeric overflow");
161 static if (opt
== "throw") error("numeric overflow");
167 if (idx
>= cf
.vlist
.length
) {
168 static if (opt
== "throw") error("index out of range");
172 } else if (cf
.type
== Type
.Dict
) {
173 cf
= part
in cf
.vdict
;
175 static if (opt
== "throw") {
176 //import iv.strex : quote;
177 error("dictionary item not found ("~quote(part
)~")");
183 static if (opt
== "throw") error();
191 static BTField
loadElement(ST
) (auto ref ST st
, char tch
) if (isReadableStream
!ST
) {
192 ulong loadInteger (char ech
, char fch
=0) {
194 if (fch
>= '0' && fch
<= '9') n
= fch
-'0';
197 st
.rawReadExact((&ch
)[0..1]);
198 if (ch
== ech
) break;
199 if (ch
>= '0' && ch
<= '9') {
201 if (n1
< n
) throw new Exception("integer overflow");
203 if (n1
< n
) throw new Exception("integer overflow");
206 //{ import iv.writer; writeln(ch); }
207 throw new Exception("invalid file format");
213 string
loadStr (char fch
=0) {
214 auto len
= loadInteger(':', fch
);
215 if (len
> 1024*1024*8) throw new Exception("string overflow");
217 import std
.exception
: assumeUnique
;
218 auto res
= new char[](cast(usize
)len
);
219 st
.rawReadExact(res
);
220 return res
.assumeUnique
;
226 void loadUInt (ref BTField
fld) {
227 fld.type
= Type
.UInt
;
228 fld.vuint
= loadInteger('e');
231 void loadList (ref BTField
fld) {
232 fld.type
= Type
.List
;
235 st
.rawReadExact((&tch
)[0..1]);
236 if (tch
== 'e') break;
237 fld.vlist
~= loadElement(st
, tch
);
241 void loadDict (ref BTField
fld) {
242 fld.type
= Type
.Dict
;
245 st
.rawReadExact((&tch
)[0..1]);
246 if (tch
== 'e') break;
247 if (tch
< '0' || tch
> '9') throw new Exception("string overflow");
248 auto name
= loadStr(tch
);
249 st
.rawReadExact((&tch
)[0..1]);
250 fld.vdict
[name
] = loadElement(st
, tch
);
256 debug { import iv
.writer
; writeln("LIST"); }
258 } else if (tch
== 'd') {
259 debug { import iv
.writer
; writeln("DICT"); }
261 } else if (tch
== 'i') {
262 debug { import iv
.writer
; writeln("UINT"); }
264 } else if (tch
>= '0' && tch
<= '9') {
265 debug { import iv
.writer
; writeln("STR"); }
267 res
.vstr
= loadStr(tch
);
269 debug { import iv
.writer
; writeln(tch
); }
270 throw new Exception("invalid file format");
277 ubyte[20] btInfoHash (string fname
) {
278 import std
.stdio
: File
;
279 return btInfoHash(File(fname
));
283 ubyte[20] btInfoHash(ST
) (auto ref ST st
) if (isReadableStream
!ST
) {
284 import std
.digest
.sha
: SHA1
;
292 st
.rawReadExact((&ch
)[0..1]);
293 if (doHash
) dig
.put(cast(ubyte)ch
);
297 ulong getUInt (char ech
, char fch
=0) {
300 if (fch
>= '0' && fch
<= '9') n
= fch
-'0';
303 if (ch
== ech
) break;
304 if (ch
>= '0' && ch
<= '9') {
306 if (n1
< n
) throw new Exception("integer overflow");
308 if (n1
< n
) throw new Exception("integer overflow");
311 throw new Exception("invalid file format");
317 void skipBytes (ulong n
) {
318 ubyte[256] buf
= void;
320 usize rd
= cast(usize
)(n
> buf
.length ? buf
.length
: n
);
321 st
.rawReadExact(buf
[0..rd
]);
322 if (doHash
) dig
.put(buf
[0..rd
]);
327 void skipElement (char ch
) {
332 if (ch
!= 'e' && (ch
< '0' || ch
> '9')) throw new Exception("invalid torrent file element");
334 } else if (ch
== 'l') {
338 if (ch
== 'e') break;
341 } else if (ch
== 'd') {
345 if (ch
== 'e') break;
346 if (ch
< '0' || ch
> '9') throw new Exception("invalid torrent file element");
348 auto n
= getUInt(':', ch
);
354 } else if (ch
>= '0' && ch
<= '9') {
355 auto n
= getUInt(':', ch
);
359 throw new Exception("invalid torrent file element");
363 // torrent file must be dictionary
365 if (ch
!= 'd') throw new Exception("invalid torrent file: not a dictionary");
366 // now find "info" key
369 if (ch
< '0' || ch
> '9') break;
371 auto n
= getUInt(':', ch
);
374 st
.rawReadExact(buf
[]);
375 if (buf
[] == "info") {
376 // wow! this is "info" section, hash it
379 if (ch
!= 'd') throw new Exception("invalid torrent file: \"info\" section is not a dictionary");
391 throw new Exception("invalid torrent file: no \"info\" section");