#include <iostream> #include <fstream> #include <sstream> #include <vector> #include <list> #include <map> #include <string> #include <assert.h> #include "sha1.hpp" #include "alexshatorrent05.cpp" #define verbosedecode 0 #define print_error_bad_torrent 1 #define debugprint 0 #define how_many_bytes 10 using namespace std; string path(string n){ char backslash=0x5c; std::size_t slashpos = n.rfind(backslash); if (slashpos==string::npos) return ""; else return n.substr(0,slashpos);} //does not include the slash at end string filename(string n){ char backslash=0x5c; std::size_t slashpos = n.rfind(backslash); if (slashpos==string::npos) return n; else return n.substr(slashpos+1);} // does not include slashes void printvector(vector<string> &v){ for (int i=0;i<v.size();i++) cout<<v[i]<<endl; } void printvectorskipslash(vector<string> &v){ for (int i=0;i<v.size();i++) cout<<v[i].substr(1)<<endl; } bool is_digit(char n){ return ('0'<=n and n<='9');} string indent(int n){ return string(4*n,' '); } string hex(string n){ string return_string; char hex[]="0123456789abcdef"; for (int i=0;i<how_many_bytes;i++){ return_string.append(1,hex[(n[i] & 0xf0)>>4]); return_string.append(1,hex[(n[i] & 15)]); } return return_string; } int string_to_int(string n){ istringstream instr(n); int output; instr >> output; return output; } // There are two functions that are similarily named. // Because the same functionality is needed to decode strings // for map keys I have two functions this decodes the string and outputs in in r // and the other takes that r and saves it in the btitem int string_decode_string(ifstream &n,int depth, string &r){ string str; char ch; n.get(ch); if(!(is_digit(ch))) return 1; while(is_digit(ch)){ str.append(1,ch); n.get(ch); } if(!(ch==':')) return 1; int size(string_to_int(str)); for (int i=0;i<size;i++){ n.get(ch); r.append(1,ch); } if (verbosedecode){ if(r.size()<200) cout<<indent(depth)<<r<<endl; else cout<<indent(depth)<<hex(r)<<"... hashstring size()="<<r.size()<<"\n";} return 0; } long long int string_to_long_int(string n){ istringstream instr(n); long long int output; instr >> output; return output; } class btitem { public: btitem(){ itemtype=0; depth=1; i=0; } btitem(const btitem &n){ itemtype=n.itemtype; depth=n.depth; s=n.s; i=n.i; l=n.l; m=n.m; } int decode_string(ifstream &n){ itemtype=1; if (string_decode_string(n, depth, s)) return 1; return 0; } int decode_integer(ifstream &n){ itemtype=2; string str; char ch; n.get(ch); if(!(ch=='i')) return 1; n.get(ch); while ( '0' <= ch and ch <= '9' or ch=='-'){ str.append(1,ch); n.get(ch); } if(!(ch=='e')) return 1; i = string_to_long_int(str); if (verbosedecode) cout<<indent(depth)<<i<<endl; return 0; } int decode_list(ifstream &n){ itemtype=3; if (verbosedecode) cout<<indent(depth)<<"list{\n"; char ch; n.get(ch); if(!(ch=='l')) return 1; while (n.peek()!='e'){ btitem tempbtitem; tempbtitem.depth=depth+1; if (tempbtitem.decode_item(n)) return 1; l.push_back(tempbtitem); } n.get(ch); if(!(ch=='e')) return 1; if (verbosedecode) cout<<indent(depth)<<"}\n"; return 0; } int decode_dictionary(ifstream &n){ itemtype=4; if (verbosedecode) cout<<indent(depth)<<"map{\n"; char ch; n.get(ch); if(!(ch=='d')) return 1; while (is_digit(n.peek())){ // could do not equal e for closing string tempstring; if (string_decode_string(n,depth+1,tempstring)) return 1; btitem tempbtitem; tempbtitem.depth=depth+1; if (tempbtitem.decode_item(n)) return 1; m[tempstring]=tempbtitem; if (verbosedecode) cout<<endl; } n.get(ch); if(!(ch=='e')) return 1; if (verbosedecode) cout<<indent(depth)<<"}\n"; return 0; } int decode_item(ifstream &input){ switch (input.peek()){ case 'd': return decode_dictionary(input); break; case 'i': return decode_integer(input); break; case 'l': return decode_list(input); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return decode_string(input); break; default: return 1; } } int decode_item_from_file(const string &n){ ifstream in(n, std::ios::binary); if(!in.is_open()) return 2; if (decode_item(in)) return 1; in.close(); return 0; } void print(){ switch (itemtype){ case 1: if (s.size()<200) cout<<indent(depth)<<s<<endl; else cout<<indent(depth)<<hex(s)<<"... #pieces= "<<s.size()/20<<"\n"<<endl; break; case 2: cout<<indent(depth)<<i<<endl; break; case 3: cout<<indent(depth)<<"list{\n"; for (int i=0;i<l.size();i++) (l[i]).print(); cout<<indent(depth)<<"}\n"; break; case 4: cout<<indent(depth)<<"map{\n"; for (std::map<string,btitem>::iterator mit=m.begin();mit!=m.end();mit++){ cout<<indent(depth+1)<<mit->first<<endl; (mit->second).print(); cout<<endl; } cout<<indent(depth)<<"}\n"; break; case 0: assert(0); cout<<"Empty btitem\n"; default : assert(0); } } vector<string> get_filenamelist(string dir){ char slash[] = {0x5c, 0x00}; vector<string> filenamelist; if (!m["info"].m.count("files")){ filenamelist.push_back(dir+slash+m["info"].m["name"].s); return filenamelist; } for (int fn_i=0;fn_i<m["info"].m["files"].l.size();fn_i++){ string r; r.append(dir); r.append(slash); vector<btitem> &paths = m["info"].m["files"].l[fn_i].m["path"].l; int size=paths.size(); for (int i=0;i<size-1;i++){ r.append(paths[i].s); r.append(slash); } r.append(paths[size-1].s); filenamelist.push_back(r); } return filenamelist; } int get_piecelength(){ return m["info"].m["piece length"].i; } int get_numberofpieces(){ return m["info"].m["pieces"].s.size()/20; } vector<uint32_t> get_hashints(int index){ vector<uint32_t> r; string &hash = m["info"].m["pieces"].s; // uint32_t a[5]; int i=index*20; for (int j=0;j<5;j++) { r.push_back( (hash[j*4+i+3] & 0xff) | (hash[j*4+i+2] & 0xff)<<8 | (hash[j*4+i+1] & 0xff)<<16 | (hash[j*4+i+0] & 0xff)<<24 ); // cout<<r[j]<<endl; } // cout<<endl; return r; } int itemtype; // 1 string 2 integer 3 list 4 dictionary int depth; string s; long long int i; vector<btitem> l; map<string,btitem> m; }; #define printtorrent 1 #define debug 1 #define vbv 1 class torrent{ public: torrent(string &tor, string &dir){ torrent_filename=tor; contents_directory=dir; } torrent(int c, const char*& a, const char*&b){ if (debug) cout<<c<<"openconstructor\n"; if (c>=2) torrent_filename=a; else return; if (debug) cout<<"fn assigned\n"; if (c>=3) contents_directory=b; else contents_directory=path(torrent_filename); if (debug) cout<<"dir assigned\n"; if (debug) cout<<torrent_filename<<endl<<contents_directory<<endl<<"endconstructor"; } long long int get_combined_size(){ //if(d.m.count["info"] and d.m["info"].m["files"]) long long int r=0; if (debug) cout<<d.m["info"].m["files"].l.size()<<endl; for (int i=0;i<d.m["info"].m["files"].l.size();i++) r+= d.m["info"].m["files"].l[i].m["length"].i; return r; } string checksum_number(int index){ char hex[]="0123456789abcdef"; string return_string; for (int i=index*20;i<index*20+20;i++){ return_string.append(1,hex[(d.m["info"].m["pieces"].s[i] & 0xf0)>>4]); return_string.append(1,hex[(d.m["info"].m["pieces"].s[i] & 15)]); } return return_string; } int hashpiece(std::ifstream &is, int hashindex){ SHA1 sha; int sentinel=sha.update_hashpiece(is, piecelength, fileindex, filenamelist); if (sentinel) return sentinel; if (sha.final()==checksum_number(hashindex)) return 0; return 1; } string loadpiecefromstream(std::ifstream &is){ string r,small; #define read_size_exponent 15 int read_size=1<<read_size_exponent; #define read_size 64 // #define fbuffer_size 64 // #define BLOCK_BYTES 64 // int gcount=0; #define printswitchfile 0 int prevgcount=0; while (r.size()<piecelength and fileindex<filenamelist.size()){ char buffer[read_size]; is.read(buffer,read_size); r.append(buffer,is.gcount()); prevgcount=is.gcount(); while(prevgcount<read_size){ is.close(); if (++fileindex==filenamelist.size()) break; is.open(filenamelist[fileindex], std::ios::binary); if(!is.is_open()) return ""; if (printswitchfile) cout<<"switching to next file\n"<<filenamelist[fileindex]<<endl; prevgcount=is.gcount(); is.read(buffer, read_size - is.gcount()); r.append(buffer, is.gcount()); prevgcount+=is.gcount(); } } return r; } int hashpiecestring(std::ifstream &is, int hashindex){ string piece=loadpiecefromstream(is); assert(piece.size()==piecelength or fileindex==filenamelist.size()); SHA1 sha; sha.update(piece); if (sha.final()==checksum_number(hashindex)) return 0; return 1; } int load_stream_and_filelist(std::ifstream &stream){ // contents in contents_directory // works for single file torrents because I coded that case in get_filenamelist() filenamelist = d.get_filenamelist(contents_directory); stream.open(filenamelist[0], std::ios::binary); if (stream.is_open()) return 0; // next try a subdirectory with the torrent name filenamelist.clear(); if (d.m.count("info") and d.m["info"].m.count("name") ) contents_directory = path(torrent_filename)+"\\"+d.m["info"].m["name"].s; filenamelist = d.get_filenamelist(contents_directory); stream.open(filenamelist[0], std::ios::binary); if (stream.is_open()) return 0; // backup routine to directly input single file torrent filenamelist.clear(); filenamelist.push_back(contents_directory); if (debug){printvector(filenamelist); cout<<"filenamelistsize="<<filenamelist.size()<<endl<<"contents_directory= "<<contents_directory<<endl;} stream.open(contents_directory, std::ios::binary); if (stream.is_open()) return 0; return 1; } int verify_torrent(){ if (debug){ cout<<"torrent_filename="<<torrent_filename<<endl<<"contents_directory="<<contents_directory<<endl; } int r; r = d.decode_item_from_file(torrent_filename); if (r) {cout<<"d.decode_item_from_file() failed\nThat means source is invalid bencoded file\n"; return r;} else cout<<"Torrent sucessfully decoded.\n"; if (printtorrent) d.print(); ifstream stream; r=load_stream_and_filelist(stream); if (debug) cout<<"load_stream_and_filelist(stream) returned:"<<r<<"\nfilenamelist.size()="<<filenamelist.size()<<endl; if (r) return r; if (vbv) { vector<string> nopath=d.get_filenamelist(""); cout<<"begin filenamelist\n"; printvectorskipslash(nopath); // printvector(filenamelist); cout<<"end filenamelist\n\n";} if (vbv) cout<<"combined size="<<get_combined_size(); piecelength = d.get_piecelength(); if (vbv) cout<<" piecelength= "<<piecelength<<endl; numberofpieces = d.get_numberofpieces(); if (vbv) {cout<<"numberofpieces= "<<numberofpieces<<endl<<"filenamelist[0]="<<filenamelist[0]<<"\nhashing";} time_t starttime=time(0); fileindex=0; int counter=0; for (int pieceindex=0;pieceindex<numberofpieces;pieceindex++){ if (debug) cout<<"."; if ( hashpiecestring(stream, pieceindex) == 0 ) counter++; else break; } cout<<"counter= "<<counter<<endl; if (counter==numberofpieces){ time_t stoptime=time(0); // double elapsed=difftime(stoptime,starttime); double elapsed=stoptime-starttime; cout<<"\ntorrent verified\n"; cout<<setprecision(3)<<"difftime(stoptime,starttime)="<<elapsed<<"\n"<<numberofpieces*piecelength/1000000/elapsed<<" MB/s\n";} else cout<<"\ntorrent verification failed"; } string torrent_filename; string contents_directory; btitem d; int piecelength; int numberofpieces; int fileindex; vector<string> filenamelist; // std::ifstream is; }; void printhelp(){ cout<<"\nAlex's Torrenthc\n"; cout<<"first argument: torrentfile\n"; cout<<"second argument: directory containing files or filename of single file \n";} int main(int argc, const char *argv[]){ cout<<"argc="<<argc<<endl; if (argc<2) {printhelp(); return 0;} torrent t(argc, argv[1], argv[2]); cout<<"constructor finished\n"; t.verify_torrent(); char c; if (debug) cout<<"Program ran correctly. Press enter to exit.\n"; cin.get(c); return 0; }