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, version 3 of the License ONLY.
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.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // some code to process bittorrent files
18 module iv
.btenc
/*is aliced*/;
22 import iv
.strex
: quote
;
26 enum Type
{ UInt
, Str
, List
, Dict
}
27 Type type
= Type
.UInt
;
31 BTField
[string
] vdict
;
33 this (ulong v
) @safe nothrow @nogc { type
= Type
.UInt
; vuint
= v
; }
34 this (string v
) @safe nothrow @nogc { type
= Type
.Str
; vstr
= v
; }
35 this (const(ubyte[]) v
) @safe nothrow { type
= Type
.Str
; vstr
= cast(string
)(v
.idup
); }
37 const @safe pure nothrow @nogc {
38 @property const(ubyte)[] bytes () { return cast(const(ubyte)[])vstr
; }
39 @property bool isUInt () { return (type
== Type
.UInt
); }
40 @property bool isStr () { return (type
== Type
.Str
); }
41 @property bool isList () { return (type
== Type
.List
); }
42 @property bool isDict () { return (type
== Type
.Dict
); }
45 static BTField
load (string fname
) {
46 import std
.stdio
: File
;
47 return load(File(fname
));
50 static BTField
load(ST
) (auto ref ST st
) if (isReadableStream
!ST
) {
52 st
.rawReadExact((&tch
)[0..1]);
53 return loadElement(st
, tch
);
56 void save (string fname
) {
57 import std
.stdio
: File
;
58 return save(File(fname
, "w"));
61 void save(ST
) (auto ref ST st
) const if (isWriteableStream
!ST
) {
62 void putNum (ulong n
) {
64 // 18446744073709551615
66 usize pos
= str.length
;
67 do str[--pos
] = cast(char)('0'+n
%10); while ((n
/= 10) != 0);
68 st
.rawWriteExact(str[pos
..$]);
71 void putChar (char ch
) {
72 st
.rawWriteExact((&ch
)[0..1]);
75 //{ import std.conv : to; import iv.writer; writeln(to!string(type)); }
76 if (type
== Type
.UInt
) {
80 } else if (type
== Type
.Str
) {
83 st
.rawWriteExact(vstr
);
84 } else if (type
== Type
.List
) {
86 foreach (const ref BTField
fld; vlist
) fld.save(st
);
88 } else if (type
== Type
.Dict
) {
89 // we have to sort keys by standard
90 import std
.algorithm
: sort
;
92 foreach (string key
; sort(vdict
.keys
)) {
95 st
.rawWriteExact(key
);
104 ref BTField
opIndex (string path
) {
105 auto res
= byPath
!"throw"(path
);
109 BTField
* opBinaryRight(string op
="in") (string path
) { return byPath(path
); }
113 BTField
* byPath(string opt
="") (string path
) {
114 static assert(opt
.length
== 0 || opt
== "throw", "invalid mode; only 'throw' allowed");
116 static if (opt
== "throw") {
117 auto origpath
= path
;
119 void error (string msg
=null) {
120 //import iv.strex : quote;
121 if (msg
.length
== 0) {
122 throw new Exception("invalid path: "~quote(origpath
));
124 throw new Exception(msg
~" at path: "~quote(origpath
));
130 while (path
.length
> 0) {
131 while (path
.length
> 0 && path
[0] == '/') path
= path
[1..$];
132 if (path
.length
== 0) break;
135 while (epos
< path
.length
&& path
[epos
] != '/') ++epos
;
136 auto part
= path
[0..epos
];
137 path
= path
[epos
..$];
138 if (cf
.type
== Type
.List
) {
139 // this should be number
141 if (part
[0] == '-') { neg = true; part
= part
[1..$]; }
142 else if (part
[0] == '+') part
= part
[1..$];
143 if (part
.length
== 0) {
144 static if (opt
== "throw") error("invalid number");
148 while (part
.length
> 0) {
149 if (part
[0] < '0' || part
[0] > '9') {
150 static if (opt
== "throw") error("invalid number");
155 static if (opt
== "throw") error("numeric overflow");
160 static if (opt
== "throw") error("numeric overflow");
166 if (idx
>= cf
.vlist
.length
) {
167 static if (opt
== "throw") error("index out of range");
171 } else if (cf
.type
== Type
.Dict
) {
172 cf
= part
in cf
.vdict
;
174 static if (opt
== "throw") {
175 //import iv.strex : quote;
176 error("dictionary item not found ("~quote(part
)~")");
182 static if (opt
== "throw") error();
190 static BTField
loadElement(ST
) (auto ref ST st
, char tch
) if (isReadableStream
!ST
) {
191 ulong loadInteger (char ech
, char fch
=0) {
193 if (fch
>= '0' && fch
<= '9') n
= fch
-'0';
196 st
.rawReadExact((&ch
)[0..1]);
197 if (ch
== ech
) break;
198 if (ch
>= '0' && ch
<= '9') {
200 if (n1
< n
) throw new Exception("integer overflow");
202 if (n1
< n
) throw new Exception("integer overflow");
205 //{ import iv.writer; writeln(ch); }
206 throw new Exception("invalid file format");
212 string
loadStr (char fch
=0) {
213 auto len
= loadInteger(':', fch
);
214 if (len
> 1024*1024*8) throw new Exception("string overflow");
216 import std
.exception
: assumeUnique
;
217 auto res
= new char[](cast(usize
)len
);
218 st
.rawReadExact(res
);
219 return res
.assumeUnique
;
225 void loadUInt (ref BTField
fld) {
226 fld.type
= Type
.UInt
;
227 fld.vuint
= loadInteger('e');
230 void loadList (ref BTField
fld) {
231 fld.type
= Type
.List
;
234 st
.rawReadExact((&tch
)[0..1]);
235 if (tch
== 'e') break;
236 fld.vlist
~= loadElement(st
, tch
);
240 void loadDict (ref BTField
fld) {
241 fld.type
= Type
.Dict
;
244 st
.rawReadExact((&tch
)[0..1]);
245 if (tch
== 'e') break;
246 if (tch
< '0' || tch
> '9') throw new Exception("string overflow");
247 auto name
= loadStr(tch
);
248 st
.rawReadExact((&tch
)[0..1]);
249 fld.vdict
[name
] = loadElement(st
, tch
);
255 debug { import iv
.writer
; writeln("LIST"); }
257 } else if (tch
== 'd') {
258 debug { import iv
.writer
; writeln("DICT"); }
260 } else if (tch
== 'i') {
261 debug { import iv
.writer
; writeln("UINT"); }
263 } else if (tch
>= '0' && tch
<= '9') {
264 debug { import iv
.writer
; writeln("STR"); }
266 res
.vstr
= loadStr(tch
);
268 debug { import iv
.writer
; writeln(tch
); }
269 throw new Exception("invalid file format");
276 ubyte[20] btInfoHash (string fname
) {
277 import std
.stdio
: File
;
278 return btInfoHash(File(fname
));
282 ubyte[20] btInfoHash(ST
) (auto ref ST st
) if (isReadableStream
!ST
) {
283 import std
.digest
.sha
: SHA1
;
291 st
.rawReadExact((&ch
)[0..1]);
292 if (doHash
) dig
.put(cast(ubyte)ch
);
296 ulong getUInt (char ech
, char fch
=0) {
299 if (fch
>= '0' && fch
<= '9') n
= fch
-'0';
302 if (ch
== ech
) break;
303 if (ch
>= '0' && ch
<= '9') {
305 if (n1
< n
) throw new Exception("integer overflow");
307 if (n1
< n
) throw new Exception("integer overflow");
310 throw new Exception("invalid file format");
316 void skipBytes (ulong n
) {
317 ubyte[256] buf
= void;
319 usize rd
= cast(usize
)(n
> buf
.length ? buf
.length
: n
);
320 st
.rawReadExact(buf
[0..rd
]);
321 if (doHash
) dig
.put(buf
[0..rd
]);
326 void skipElement (char ch
) {
331 if (ch
!= 'e' && (ch
< '0' || ch
> '9')) throw new Exception("invalid torrent file element");
333 } else if (ch
== 'l') {
337 if (ch
== 'e') break;
340 } else if (ch
== 'd') {
344 if (ch
== 'e') break;
345 if (ch
< '0' || ch
> '9') throw new Exception("invalid torrent file element");
347 auto n
= getUInt(':', ch
);
353 } else if (ch
>= '0' && ch
<= '9') {
354 auto n
= getUInt(':', ch
);
358 throw new Exception("invalid torrent file element");
362 // torrent file must be dictionary
364 if (ch
!= 'd') throw new Exception("invalid torrent file: not a dictionary");
365 // now find "info" key
368 if (ch
< '0' || ch
> '9') break;
370 auto n
= getUInt(':', ch
);
373 st
.rawReadExact(buf
[]);
374 if (buf
[] == "info") {
375 // wow! this is "info" section, hash it
378 if (ch
!= 'd') throw new Exception("invalid torrent file: \"info\" section is not a dictionary");
390 throw new Exception("invalid torrent file: no \"info\" section");