iv.ssarray: it is now possible to "extract" data pages, and re-insert them
[iv.d.git] / btenc.d
blob30bb04bf86024f00064f1d67b6791231840d269d
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*/;
21 import iv.alice;
22 import iv.vfs;
23 import iv.strex : quote;
26 struct BTField {
27 enum Type { UInt, Str, List, Dict }
28 Type type = Type.UInt;
29 string vstr;
30 ulong vuint;
31 BTField[] vlist;
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) {
52 char tch;
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) {
64 // 4294967295
65 // 18446744073709551615
66 char[32] str;
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) {
78 putChar('i');
79 putNum(vuint);
80 putChar('e');
81 } else if (type == Type.Str) {
82 putNum(vstr.length);
83 putChar(':');
84 st.rawWriteExact(vstr);
85 } else if (type == Type.List) {
86 putChar('l');
87 foreach (const ref BTField fld; vlist) fld.save(st);
88 putChar('e');
89 } else if (type == Type.Dict) {
90 // we have to sort keys by standard
91 import std.algorithm : sort;
92 putChar('d');
93 foreach (string key; sort(vdict.keys)) {
94 putNum(key.length);
95 putChar(':');
96 st.rawWriteExact(key);
97 vdict[key].save(st);
99 putChar('e');
100 } else {
101 assert(0);
105 ref BTField opIndex (string path) {
106 auto res = byPath!"throw"(path);
107 return *res;
110 BTField* opBinaryRight(string op="in") (string path) { return byPath(path); }
112 private:
113 // opt: "throw"
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));
124 } else {
125 throw new Exception(msg~" at path: "~quote(origpath));
130 BTField* cf = &this;
131 while (path.length > 0) {
132 while (path.length > 0 && path[0] == '/') path = path[1..$];
133 if (path.length == 0) break;
134 // extract component
135 usize epos = 1;
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
141 bool neg = false;
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");
146 else return null;
148 usize idx = 0;
149 while (part.length > 0) {
150 if (part[0] < '0' || part[0] > '9') {
151 static if (opt == "throw") error("invalid number");
152 else return null;
154 usize n1 = idx*10;
155 if (n1 < idx) {
156 static if (opt == "throw") error("numeric overflow");
157 else return null;
159 n1 += part[0]-'0';
160 if (n1 < idx) {
161 static if (opt == "throw") error("numeric overflow");
162 else return null;
164 idx = n1;
165 part = part[1..$];
167 if (idx >= cf.vlist.length) {
168 static if (opt == "throw") error("index out of range");
169 else return null;
171 cf = &cf.vlist[idx];
172 } else if (cf.type == Type.Dict) {
173 cf = part in cf.vdict;
174 if (cf is null) {
175 static if (opt == "throw") {
176 //import iv.strex : quote;
177 error("dictionary item not found ("~quote(part)~")");
178 } else {
179 return null;
182 } else {
183 static if (opt == "throw") error();
184 else return null;
187 return cf;
191 static BTField loadElement(ST) (auto ref ST st, char tch) if (isReadableStream!ST) {
192 ulong loadInteger (char ech, char fch=0) {
193 ulong n = 0;
194 if (fch >= '0' && fch <= '9') n = fch-'0';
195 for (;;) {
196 char ch;
197 st.rawReadExact((&ch)[0..1]);
198 if (ch == ech) break;
199 if (ch >= '0' && ch <= '9') {
200 ulong n1 = n*10;
201 if (n1 < n) throw new Exception("integer overflow");
202 n1 += ch-'0';
203 if (n1 < n) throw new Exception("integer overflow");
204 n = n1;
205 } else {
206 //{ import iv.writer; writeln(ch); }
207 throw new Exception("invalid file format");
210 return n;
213 string loadStr (char fch=0) {
214 auto len = loadInteger(':', fch);
215 if (len > 1024*1024*8) throw new Exception("string overflow");
216 if (len > 0) {
217 import std.exception : assumeUnique;
218 auto res = new char[](cast(usize)len);
219 st.rawReadExact(res);
220 return res.assumeUnique;
221 } else {
222 return "";
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;
233 for (;;) {
234 char tch;
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;
243 for (;;) {
244 char tch;
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);
254 BTField res;
255 if (tch == 'l') {
256 debug { import iv.writer; writeln("LIST"); }
257 loadList(res);
258 } else if (tch == 'd') {
259 debug { import iv.writer; writeln("DICT"); }
260 loadDict(res);
261 } else if (tch == 'i') {
262 debug { import iv.writer; writeln("UINT"); }
263 loadUInt(res);
264 } else if (tch >= '0' && tch <= '9') {
265 debug { import iv.writer; writeln("STR"); }
266 res.type = Type.Str;
267 res.vstr = loadStr(tch);
268 } else {
269 debug { import iv.writer; writeln(tch); }
270 throw new Exception("invalid file format");
272 return res;
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;
286 SHA1 dig;
287 dig.start();
288 bool doHash = false;
290 char getChar () {
291 char ch;
292 st.rawReadExact((&ch)[0..1]);
293 if (doHash) dig.put(cast(ubyte)ch);
294 return ch;
297 ulong getUInt (char ech, char fch=0) {
298 ulong n = 0;
299 char ch;
300 if (fch >= '0' && fch <= '9') n = fch-'0';
301 for (;;) {
302 ch = getChar();
303 if (ch == ech) break;
304 if (ch >= '0' && ch <= '9') {
305 ulong n1 = n*10;
306 if (n1 < n) throw new Exception("integer overflow");
307 n1 += ch-'0';
308 if (n1 < n) throw new Exception("integer overflow");
309 n = n1;
310 } else {
311 throw new Exception("invalid file format");
314 return n;
317 void skipBytes (ulong n) {
318 ubyte[256] buf = void;
319 while (n > 0) {
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]);
323 n -= rd;
327 void skipElement (char ch) {
328 if (ch == 'i') {
329 // skip until 'e'
330 do {
331 ch = getChar();
332 if (ch != 'e' && (ch < '0' || ch > '9')) throw new Exception("invalid torrent file element");
333 } while (ch != 'e');
334 } else if (ch == 'l') {
335 // list
336 for (;;) {
337 ch = getChar();
338 if (ch == 'e') break;
339 skipElement(ch);
341 } else if (ch == 'd') {
342 // dictionary
343 for (;;) {
344 ch = getChar();
345 if (ch == 'e') break;
346 if (ch < '0' || ch > '9') throw new Exception("invalid torrent file element");
347 // skip key name
348 auto n = getUInt(':', ch);
349 skipBytes(n);
350 // skip key value
351 ch = getChar();
352 skipElement(ch);
354 } else if (ch >= '0' && ch <= '9') {
355 auto n = getUInt(':', ch);
356 // skip string
357 skipBytes(n);
358 } else {
359 throw new Exception("invalid torrent file element");
363 // torrent file must be dictionary
364 char ch = getChar();
365 if (ch != 'd') throw new Exception("invalid torrent file: not a dictionary");
366 // now find "info" key
367 for (;;) {
368 ch = getChar();
369 if (ch < '0' || ch > '9') break;
370 // key name
371 auto n = getUInt(':', ch);
372 if (n == 4) {
373 char[4] buf;
374 st.rawReadExact(buf[]);
375 if (buf[] == "info") {
376 // wow! this is "info" section, hash it
377 doHash = true;
378 ch = getChar();
379 if (ch != 'd') throw new Exception("invalid torrent file: \"info\" section is not a dictionary");
380 skipElement(ch);
381 return dig.finish();
383 } else {
384 // skip name
385 skipBytes(n);
387 // skip data
388 ch = getChar();
389 skipElement(ch);
391 throw new Exception("invalid torrent file: no \"info\" section");