Download.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. namespace EE{
  4. /******************************************************************************
  5. For 'Download' 'Memb' is good for storing data,
  6. because speed of accessing data is not important,
  7. however files can be big and Memb only allocates new chunks of data,
  8. while Memc requires reallocation and copying.
  9. /******************************************************************************/
  10. #define BUF_SIZE (64*1024) // 64 KB
  11. /******************************************************************************/
  12. // DOWNLOAD
  13. /******************************************************************************/
  14. static void EncodeChar(Str8 &text, Char8 c)
  15. {
  16. text+='%';
  17. text+=Digits16[Byte(c)>>4];
  18. text+=Digits16[Byte(c)&15];
  19. }
  20. static Bool AppendUrlPath(Str8 &text, C Str8 &path)
  21. {
  22. Bool has_params=false;
  23. text.reserve(text.length()+path.length());
  24. FREPA(path)
  25. {
  26. Char8 c=path[i];
  27. if(U8(c)<=32 || c=='#' || c=='%' || U8(c)>=127)EncodeChar(text, c);else // these are the only symbols that need to be replaced with %XX hex code
  28. if(c=='\\') // use Unix style paths
  29. {
  30. text+='/';
  31. }else
  32. {
  33. if(c=='?')has_params=true;
  34. text+=c;
  35. }
  36. }
  37. return has_params;
  38. }
  39. static void AppendParam(Str8 &text, C TextParam &param)
  40. {
  41. C Str8 &name=UTF8(param.name);
  42. FREPA(name) // set name
  43. {
  44. Char8 c=name[i];
  45. if(U8(c)<=32 || c=='#' || c=='%' || c=='=' || c=='&' || U8(c)>=127)EncodeChar(text, c); // these are the only symbols that need to be replaced with %XX hex code
  46. else text+=c;
  47. }
  48. text+='=';
  49. C Str8 &value=UTF8(param.value);
  50. FREPA(value) // set value
  51. {
  52. Char8 c=value[i];
  53. if(U8(c)<=32 || c=='#' || c=='%' || c=='&' || c=='+' || U8(c)>=127)EncodeChar(text, c); // these are the only symbols that need to be replaced with %XX hex code
  54. else text+=c;
  55. }
  56. }
  57. Str8 HTTPParam::Encode(C MemPtr<HTTPParam> &params)
  58. {
  59. Str8 s; FREPA(params)
  60. {
  61. if(i)s+='&';
  62. AppendParam(s, params[i]);
  63. }
  64. return s;
  65. }
  66. static Str8 GetHeaders(C Str8 &url, CChar8 *request)
  67. {
  68. CChar8 *name =_SkipStartPath(_SkipStartPath(url, "http://"), "https://");
  69. Str8 headers =S+request+" /"+GetStartNot(name)+" HTTP/1.1\r\nHost: "+_GetStart(name)+"\r\n";
  70. headers+="Connection: close\r\n"; // connection will be closed after receiving all data
  71. #if WINDOWS
  72. headers+="User-Agent: Esenthel Windows\r\n";
  73. #elif MAC
  74. headers+="User-Agent: Esenthel Mac\r\n";
  75. #elif LINUX
  76. headers+="User-Agent: Esenthel Linux\r\n";
  77. #elif ANDROID
  78. headers+="User-Agent: Esenthel Android\r\n";
  79. #elif IOS
  80. headers+=(D.smallSize() ? "User-Agent: Esenthel iPhone\r\n" : "User-Agent: Esenthel iPad\r\n");
  81. #endif
  82. return headers;
  83. }
  84. #if WEB
  85. struct JSDownload
  86. {
  87. Download *download;
  88. };
  89. Memx<JSDownload> JSDownloads;
  90. static SyncLock JSDownloadsLock;
  91. static void OnProgress(Ptr user, Int code, Int done, Int total, UInt timestamp)
  92. {
  93. if(JSDownloads.elms()) // check in case 'JSDownloads' destructor was already called
  94. {
  95. JSDownload &js_download=*(JSDownload*)user;
  96. SyncLocker lock(JSDownloadsLock);
  97. if(Download *download=js_download.download)
  98. {
  99. download->_code=code;
  100. if(timestamp)download->_modif_time.from1970s(timestamp);
  101. if(download->_size) // expecting size
  102. {
  103. download->_done=done;
  104. download->_size=total;
  105. }
  106. download->_state=DWNL_DOWNLOAD;
  107. }
  108. }
  109. }
  110. static void OnDone(Ptr user, Int code, CPtr data, Int data_size, Int content_length, Int content_range, UInt timestamp)
  111. {
  112. if(JSDownloads.elms()) // check in case 'JSDownloads' destructor was already called
  113. {
  114. JSDownload &js_download=*(JSDownload*)user;
  115. {
  116. SyncLocker lock(JSDownloadsLock);
  117. if(Download *download=js_download.download)
  118. {
  119. download->_code=code;
  120. if(timestamp)download->_modif_time.from1970s(timestamp);
  121. if(content_length<0)content_length=data_size;
  122. if(download->_size) // expecting size
  123. {
  124. download->_size=content_length;
  125. download->_done=data_size;
  126. Copy(download->_data=Alloc(data_size), data, data_size);
  127. }else
  128. {
  129. download->_total_size=content_length;
  130. }
  131. if(download->_total_size<0)download->_total_size=((content_range>=0) ? content_range : download->_size);
  132. download->_js_download=null; // unlink
  133. download->finish(); // !! set at the end !!
  134. }
  135. }
  136. JSDownloads.removeData(&js_download);
  137. }
  138. }
  139. static void OnError(Ptr user, Int code)
  140. {
  141. if(JSDownloads.elms()) // check in case 'JSDownloads' destructor was already called
  142. {
  143. JSDownload &js_download=*(JSDownload*)user;
  144. {
  145. SyncLocker lock(JSDownloadsLock);
  146. if(Download *download=js_download.download)
  147. {
  148. download->_code=code;
  149. download->_js_download=null; // unlink
  150. download->_state=DWNL_ERROR; // !! set at the end !!
  151. }
  152. }
  153. JSDownloads.removeData(&js_download);
  154. }
  155. }
  156. #endif
  157. /******************************************************************************/
  158. void Download::parse(Byte *data, Int size)
  159. {
  160. if(data)for(; size>0; )switch(_parse)
  161. {
  162. case PARSE_DATA_SIZE:
  163. {
  164. for(; size>0; )
  165. {
  166. Byte c=*data++; size--;
  167. if(c=='\n')
  168. {
  169. _parse=(_expected_size ? PARSE_DATA : PARSE_END);
  170. break;
  171. }
  172. Int i=CharInt(c);
  173. if(InRange(i, 16))_expected_size=_expected_size*16+i;else continue;
  174. }
  175. }break;
  176. case PARSE_DATA:
  177. {
  178. Int left =Min(size, _expected_size);
  179. Int start=_memb.addNum(left);
  180. REP(left)_memb[start+i]=data[i];
  181. size -=left;
  182. data +=left;
  183. _done +=left;
  184. _expected_size-=left;
  185. if(!_expected_size)_parse=PARSE_SKIP_LINE;
  186. }break;
  187. case PARSE_SKIP_LINE:
  188. {
  189. for(; size>0; )
  190. {
  191. Byte c=*data++; size--;
  192. if(c=='\n')
  193. {
  194. _parse=PARSE_DATA_SIZE;
  195. break;
  196. }
  197. }
  198. }break;
  199. default: return;
  200. }
  201. }
  202. /******************************************************************************/
  203. #if !WEB
  204. #if SUPPORT_MBED_TLS
  205. static int DownloadSendFunc(Ptr ctx, C Byte *buf, size_t len)
  206. {
  207. Download &d=*(Download*)ctx; Int r=d._socket.Socket::send(buf, (Int)len); if(r<0)return Socket::WouldBlock() ? MBEDTLS_ERR_SSL_WANT_WRITE : MBEDTLS_ERR_NET_SEND_FAILED;
  208. d._total_sent+=r;
  209. return r;
  210. }
  211. static int DownloadReceiveFunc(Ptr ctx, Byte *buf, size_t len)
  212. {
  213. Download &d=*(Download*)ctx; Int r=d._socket.Socket::receive(buf, (Int)len); if(r<0)return Socket::WouldBlock() ? MBEDTLS_ERR_SSL_WANT_READ : MBEDTLS_ERR_NET_RECV_FAILED;
  214. d._total_rcvd+=r;
  215. return r;
  216. }
  217. static int DownloadReceiveFuncTimeout(Ptr ctx, Byte *buf, size_t len, uint32_t timeout)
  218. {
  219. Download &d=*(Download*)ctx; if(!d._socket.wait(timeout ? Min(timeout, INT_MAX) : INT_MAX))return MBEDTLS_ERR_SSL_TIMEOUT; // Min to INT_MAX because 'wait' expected Int, if 'timeout' is zero, then use INT_MAX because MBED TLS treats this as unlimited time
  220. return DownloadReceiveFunc(ctx, buf, len);
  221. }
  222. static Bool Again(Int r, Download &d)
  223. {
  224. // every time wait only a short amount of time so we can check '_thread.wantStop' frequently, return true regardless of 'flush'/'wait' result so we can continue making those short checks until download was requested to be stopped
  225. switch(r)
  226. {
  227. case MBEDTLS_ERR_SSL_WANT_READ :
  228. case MBEDTLS_ERR_SSL_TIMEOUT : if(!d._thread.wantStop()){d._socket.wait (DOWNLOAD_WAIT_TIME); return true;} break;
  229. case MBEDTLS_ERR_SSL_WANT_WRITE: if(!d._thread.wantStop()){d._socket.flush(DOWNLOAD_WAIT_TIME); return true;} break;
  230. }
  231. return false;
  232. }
  233. #endif
  234. Int Download::send(CPtr data, Int size)
  235. {
  236. #if SUPPORT_MBED_TLS
  237. if(_socket._secure)
  238. {
  239. again:
  240. Int r=mbedtls_ssl_write(_socket._secure, (Byte*)data, size); if(Again(r, T))goto again;
  241. return r;
  242. }
  243. #endif
  244. Int r=_socket.Socket::send(data, size); if(r>0)_total_sent+=r; return r;
  245. }
  246. Int Download::receive(Ptr data, Int size)
  247. {
  248. #if SUPPORT_MBED_TLS
  249. if(_socket._secure)
  250. {
  251. again:
  252. Int r=mbedtls_ssl_read(_socket._secure, (Byte*)data, size); if(Again(r, T))goto again;
  253. return r;
  254. }
  255. #endif
  256. Int r=_socket.Socket::receive(data, size); if(r>0)_total_rcvd+=r; return r;
  257. }
  258. static Bool DownloadFunc(Thread &thread) {return ((Download*)thread.user)->func();}
  259. Bool Download::func()
  260. {
  261. Byte data[BUF_SIZE];
  262. switch(_state)
  263. {
  264. case DWNL_CONNECTING:
  265. {
  266. // initialize connection
  267. if(!_socket.is())
  268. {
  269. SockAddr addr;
  270. if(!hasAddrsHeader())
  271. {
  272. _flags|=HAS_ADDRS_HEADER;
  273. CChar8 *url=_url_full(); Int port;
  274. if(StartsPath(url, "https://"))
  275. {
  276. url+=8; port=443; // 443 = default HTTPS port
  277. if(!_socket.secure(url))return error();
  278. #if SUPPORT_MBED_TLS
  279. mbedtls_ssl_set_bio(_socket._secure, this, DownloadSendFunc, DownloadReceiveFunc, DownloadReceiveFuncTimeout);
  280. #endif
  281. }else
  282. if(StartsPath(url, "http://"))
  283. {
  284. url+=7; port=80; // 80 = default HTTP port
  285. }else
  286. return error();
  287. Char8 temp[MAX_LONG_PATH], *url_start=_GetStart(url, temp);
  288. if(Char8 *url_port=TextPos(url_start, ':')) // check for port override, example URL "http://www.example.com:8080/path/"
  289. {
  290. port=TextInt(url_port+1); // ":8080" +1 to skip ':'
  291. *url_port='\0'; // have to remove port specifier because 'GetHostAddresses' will fail
  292. }
  293. Memt<SockAddr> addresses;
  294. GetHostAddresses(addresses, url_start, port);
  295. if(!addresses.elms())return error();
  296. addresses.reverseOrder(); // recommended order for processing is from start to end, however since we're going to use 'pop' then reverse order
  297. addr=addresses.pop(); _addrs=addresses; // normally we should get just 1 address, so pop it first hoping that 'addresses' would become empty and copying to '_addrs' won't require memory allocation
  298. }else
  299. {
  300. if(!_addrs.elms())return error();
  301. addr=_addrs.pop();
  302. }
  303. if( !_socket.createTcp(addr))return error();
  304. switch(_socket.connect (addr, DOWNLOAD_WAIT_TIME))
  305. {
  306. case Socket::IN_PROGRESS: break;
  307. case Socket::CONNECTED : FlagDisable(_flags, HAS_ADDRS_HEADER); _state=DWNL_AUTH; goto auth;
  308. default : _socket.del(); break;
  309. }
  310. }else
  311. {
  312. if(_socket.any(DOWNLOAD_WAIT_TIME)){FlagDisable(_flags, HAS_ADDRS_HEADER); _state=DWNL_AUTH; goto auth;}
  313. if(_socket.connectFailed())_socket.del();
  314. }
  315. }break;
  316. case DWNL_AUTH: auth:
  317. {
  318. switch(_socket.handshake())
  319. {
  320. case SecureSocket::OK : ok: _state=DWNL_SENDING; goto sending;
  321. case SecureSocket::NEED_FLUSH: _socket.flush(DOWNLOAD_WAIT_TIME); return true; // flush quickly and return regardless of result, to check if thread want to stop
  322. case SecureSocket::NEED_WAIT : _socket.wait (DOWNLOAD_WAIT_TIME); return true; // wait quickly and return regardless of result, to check if thread want to stop
  323. case SecureSocket::BAD_CERT : _flags|=AUTH_FAILED; if(_flags&AUTH_IGNORE)goto ok; goto error;
  324. default : error: return error(); // error
  325. }
  326. }break;
  327. case DWNL_SENDING: sending:
  328. {
  329. // send message
  330. Int left=_pre_send-_pos_send; if(left>0) // header
  331. {
  332. send_message:;
  333. if(_socket.flush(DOWNLOAD_WAIT_TIME))
  334. {
  335. Int sent=send(_message.data()+_pos_send, left); if(sent<=0)return error();
  336. _pos_send+=sent;
  337. }
  338. }else
  339. {
  340. Long file_left=_to_send-_sent; if(file_left>0) // file data
  341. {
  342. if(_socket.flush(DOWNLOAD_WAIT_TIME))
  343. {
  344. Int buf_left=Min(BUF_SIZE, file_left); if(!_post_file->get(data, buf_left))return error();
  345. Int sent=send(data, buf_left); if(sent<=0)return error();
  346. _sent+=sent;
  347. if(!_post_file->skip(sent-buf_left))return error(); // go back to unsent data
  348. }
  349. }else
  350. {
  351. left=_message.elms()-_pos_send; if(left>0)goto send_message; // footer
  352. _state=DWNL_DOWNLOAD; goto downloading;
  353. }
  354. }
  355. }break;
  356. case DWNL_DOWNLOAD: downloading:
  357. {
  358. if(_socket.wait(DOWNLOAD_WAIT_TIME))
  359. {
  360. if(!hasAddrsHeader()) // download header
  361. {
  362. Byte *rest;
  363. Int rcvd=receive(data, BUF_SIZE); if(rcvd<=0)return error();
  364. _header.reserve(_header.length()+rcvd);
  365. FREP(rcvd)
  366. {
  367. _header+=Char8(data[i]);
  368. if(_header.last()=='\n' && _header[_header.length()-2]=='\r' && _header[_header.length()-3]=='\n' && _header[_header.length()-4]=='\r') // "\r\n\r\n" marks the end of the header
  369. {
  370. _flags|=HAS_ADDRS_HEADER;
  371. i++;
  372. rest =data+i;
  373. rcvd-=i;
  374. break;
  375. }
  376. }
  377. if(hasAddrsHeader())
  378. {
  379. // example header = "HTTP/1.1 200 OK"
  380. if(!Starts(_header, "HTTP/1.1", false, true))return error();
  381. if(CChar8 *t=TextPos(_header, "Location:")) // check if it's a redirect (check this as first before all other data)
  382. {
  383. Int line =TextPosI(t, "\r\n");
  384. if( line>=0)ConstCast(t)[line]=0;
  385. CChar8 *url=_SkipWhiteChars(t+9); // operate on 'url' before clearing the '_header'
  386. if(!Is(url))return error(); // no path specified
  387. if(StartsPath(url, "http://") || StartsPath(url, "https://")) // if this is a full path then just use it
  388. {
  389. AppendUrlPath(_url_full.clear(), url);
  390. }else
  391. { // relative path
  392. if(url[0]=='/') // start of the domain
  393. {
  394. Int pos =TextPosIN(_url_full, '/', 2); // find the 3rd slash in the url "http://www.esenthel.com/index.php"
  395. if( pos>=0)_url_full.clip(pos); // remove the slash and everything after it, leaving "http://www.esenthel.com"
  396. AppendUrlPath(_url_full, url);
  397. }else // relative to current location
  398. {
  399. FREPA(_url_full)if(_url_full[i]=='?'){_url_full.clip(i ); break;} // first remove any parameters that were specified
  400. REPA(_url_full)if(_url_full[i]=='/'){_url_full.clip(i+1); break;} // remove base name, but keep the tail slash
  401. AppendUrlPath(_url_full, url);
  402. //_url_full =NormalizePath(_url_full); // this is not needed because the server will handle this properly
  403. }
  404. }
  405. FlagDisable(_flags, HAS_ADDRS_HEADER);
  406. _header.clear();
  407. _socket.del(); // unsecure and delete the socket because we will need to reconnect to a different address
  408. _state=DWNL_CONNECTING;
  409. _addrs.clear();
  410. _pos_send=0;
  411. Str8 bytes,
  412. command=GetHeaders(_url_full, (_size==0) ? "HEAD" : "GET");
  413. if(_size>0 )bytes =S+"bytes="+_offset+'-'+(_offset+_size-1);else
  414. if(_size && _offset)bytes =S+"bytes="+_offset+'-';
  415. if(bytes.is() )command+=S+"Range: "+bytes+"\r\n";
  416. command+="\r\n";
  417. _pre_send=command.length();
  418. _message.setNum(command.length());
  419. CopyFast(_message.data(), command(), command.length());
  420. break;
  421. }
  422. _code=TextInt(_header()+8+1); // 8=Length("HTTP/1.1")
  423. // !! do not stop here if error code failed, instead continue downloading all data, and set DWNL_STATE at the end based on the code, as there may be some cases in which we want to process output even for error codes !!
  424. Long content_length=-1; if(CChar8 *t=TextPos(_header, "Content-Length:"))content_length=TextLong(t+Length("Content-Length:"));
  425. if(_size)_size=content_length;else _total_size=content_length;
  426. if(_total_size<0)if(CChar8 *t=TextPos(TextPos(_header, "Content-Range:" ), '/'))_total_size=TextLong(t+1);else _total_size=_size;
  427. if(CChar8 *t=TextPos(_header, "Last-Modified:" ))
  428. {
  429. t+=Length("Last-Modified:"); // Thu, 24 Mar 2011 18:35:38 GMT
  430. if(t=_SkipChar(TextPos(t, ',')))
  431. {
  432. CalcValue val; if(t=_SkipWhiteChars(TextValue(t, val)))if(val.type)
  433. {
  434. _modif_time.day=val.asInt();
  435. if(Starts(t, "Jan"))_modif_time.month= 1;else
  436. if(Starts(t, "Feb"))_modif_time.month= 2;else
  437. if(Starts(t, "Mar"))_modif_time.month= 3;else
  438. if(Starts(t, "Apr"))_modif_time.month= 4;else
  439. if(Starts(t, "May"))_modif_time.month= 5;else
  440. if(Starts(t, "Jun"))_modif_time.month= 6;else
  441. if(Starts(t, "Jul"))_modif_time.month= 7;else
  442. if(Starts(t, "Aug"))_modif_time.month= 8;else
  443. if(Starts(t, "Sep"))_modif_time.month= 9;else
  444. if(Starts(t, "Oct"))_modif_time.month=10;else
  445. if(Starts(t, "Nov"))_modif_time.month=11;else
  446. if(Starts(t, "Dec"))_modif_time.month=12;
  447. if(_modif_time.month)if(t=TextValue(TextPos(t, ' '), val))if(val.type)
  448. {
  449. _modif_time.year=val.asInt();
  450. if(t=TextValue(t, val))if(val.type)
  451. {
  452. _modif_time.hour=val.asInt();
  453. if(*t++==':')if(t=TextValue(t, val))if(val.type)
  454. {
  455. _modif_time.minute=val.asInt();
  456. if(*t++==':')
  457. {
  458. TextValue(t, val);
  459. if(val.type)_modif_time.second=val.asInt();
  460. }
  461. }
  462. }
  463. }
  464. }
  465. }
  466. }
  467. // check if it's chunked
  468. if(CChar8 *t=TextPos(_header, "Transfer-Encoding:"))
  469. {
  470. Int line=TextPosI(t, "\r\n");
  471. if( line>=0)((Char8*)t)[line]=0;
  472. if(Contains(t, "chunked"))_flags|=CHUNKED;
  473. }
  474. // process remaining data
  475. if(_size>=0) // known size
  476. {
  477. _data=Alloc(_size);
  478. if(rcvd){CopyFast(_data, rest, rcvd); _done+=rcvd;}
  479. }else // unknown size
  480. if(chunked()) // chunked
  481. {
  482. if(rcvd)parse(rest, rcvd);
  483. }else // remaining data as long as received
  484. {
  485. _memb.addNum(rcvd); REPAO(_memb)=rest[i];
  486. _done+=rcvd;
  487. }
  488. }
  489. }else
  490. {
  491. if(_size>=0) // known size
  492. {
  493. Long left=_size-_done;
  494. if( left>0)
  495. if(_socket.wait(DOWNLOAD_WAIT_TIME))
  496. {
  497. left=receive((Byte*)_data+_done, Min(INT_MAX, left));
  498. if(left>0)_done+=left;else return error();
  499. }
  500. if(_done>=_size)finish(); // set the state at final stage (to avoid multi-threading issues)
  501. }else
  502. if(chunked()) // chunked
  503. {
  504. if(_parse==PARSE_END) // finished
  505. {
  506. _data=Alloc(_memb.elms()); _memb.copyTo((Byte*)_data);
  507. _size=_total_size=_memb.elms();
  508. _memb.del();
  509. finish(); // set the state at final stage (to avoid multi-threading issues)
  510. }else
  511. if(_socket.wait(DOWNLOAD_WAIT_TIME))
  512. {
  513. Int size=receive(data, BUF_SIZE);
  514. if( size>0)parse(data, size);else return error();
  515. }
  516. }else
  517. {
  518. if(_socket.wait(DOWNLOAD_WAIT_TIME))
  519. {
  520. Int rcvd=receive(data, BUF_SIZE);
  521. if( rcvd>0)
  522. {
  523. FREP(rcvd)_memb.add(data[i]);
  524. _done+=rcvd;
  525. }else
  526. {
  527. _data=Alloc(_memb.elms()); _memb.copyTo((Byte*)_data);
  528. _size=_total_size=_memb.elms();
  529. _memb.del();
  530. finish(); // set the state at final stage (to avoid multi-threading issues)
  531. }
  532. }
  533. }
  534. }
  535. }
  536. }break;
  537. default: delPartial(); return false;
  538. }
  539. return true;
  540. }
  541. #endif
  542. /******************************************************************************/
  543. void Download::zero()
  544. {
  545. _flags=_parse=0;
  546. _state=DWNL_NONE;
  547. _code=0;
  548. _expected_size=_pre_send=_pos_send=0;
  549. _offset=_done=_size=_total_size=_sent=_to_send=_total_sent=_total_rcvd=0;
  550. _data=null;
  551. _post_file=null;
  552. _modif_time.zero();
  553. #if WEB
  554. _js_download=null;
  555. #endif
  556. }
  557. Download::Download( ) : _memb(64*1024) {zero(); }
  558. Download::Download(C Str &url, C MemPtr<HTTPParam> &params, File *post, Long max_post_size, Long offset, Long size, Bool paused) : _memb(64*1024) {zero(); create(url, params, post, max_post_size, offset, size, paused);}
  559. void Download::delPartial() // this deletes only members which can't be accessed on the main thread
  560. {
  561. _socket .del ();
  562. _memb .del ();
  563. _addrs .clear();
  564. _message .clear();
  565. _header .clear();
  566. _url_full.clear();
  567. }
  568. Download& Download::del(Int milliseconds)
  569. {
  570. #if WEB
  571. if(_js_download)
  572. {
  573. SyncLocker lock(JSDownloadsLock);
  574. if(JSDownloads.elms()) // check in case 'JSDownloads' destructor was already called
  575. if(JSDownload *js_download=(JSDownload*)_js_download)js_download->download=null; // unlink, however don't remove it, because this will be done through the JS (since JS still holds pointer to this)
  576. }
  577. #endif
  578. stop();
  579. _thread.del(milliseconds);
  580. delPartial();
  581. _url.clear();
  582. Free(_data);
  583. zero(); return T;
  584. }
  585. Download& Download::create(C Str &url, C MemPtr<HTTPParam> &params, File *post, Long max_post_size, Long offset, Long size, Bool paused, Bool ignore_auth_result)
  586. {
  587. #if WEB
  588. post=null; // not yet supported
  589. #endif
  590. del();
  591. _flags =(ignore_auth_result ? AUTH_IGNORE : 0);
  592. _url =url;
  593. _offset =offset;
  594. _size =size;
  595. _total_size=-1; // unknown total size
  596. _to_send =(post ? ((max_post_size<0) ? post->left() : max_post_size) : 0);
  597. _post_file =post;
  598. _state =DWNL_CONNECTING; // this must be set immediately, so that we mark this 'Download' as used
  599. // set url full
  600. Bool url_has_params=AppendUrlPath(_url_full, UTF8(url)), has_post_params=false;
  601. FREPA(params) // add parameters
  602. {
  603. C HTTPParam &param=params[i]; switch(param.type)
  604. {
  605. case HTTP_POST: has_post_params=true; break;
  606. case HTTP_GET :
  607. {
  608. _url_full+=(url_has_params ? '&' : '?'); // first param must be started with '?', others with '&'
  609. url_has_params=true;
  610. AppendParam(_url_full, param);
  611. }break;
  612. }
  613. }
  614. // set message
  615. Str8 prefix, suffix, boundary, bytes,
  616. request=((_post_file || has_post_params) ? "POST" : (_size==0) ? "HEAD" : "GET"),
  617. command=GetHeaders(_url_full, request);
  618. if(_size>0 )bytes =S+"bytes="+_offset+'-'+(_offset+_size-1);else
  619. if(_size && _offset)bytes =S+"bytes="+_offset+'-';
  620. if(bytes.is() )command+=S+"Range: "+bytes+"\r\n";
  621. FREPA(params) // header params
  622. {
  623. C HTTPParam &param=params[i]; if(param.type==HTTP_HEADER)command+=UTF8(param.name)+": "+UTF8(param.value)+"\r\n";
  624. }
  625. if(_post_file)
  626. {
  627. // sub header
  628. boundary="d5ubjyl0q4rwb8j4grz5vaxgnj2l1lpuh4ku"; boundary+=Random(); // 'boundary' must not be present in the file data !! otherwise the upload will fail
  629. FREPA(params) // params
  630. {
  631. C HTTPParam &param=params[i]; if(param.type==HTTP_POST)
  632. {
  633. C Str8 &name=UTF8(param.name), &value=UTF8(param.value);
  634. prefix+=S+"--"+boundary+"\r\n";
  635. prefix+="Content-Disposition: form-data; name=\"";
  636. FREPA(name)
  637. {
  638. Char8 c=name[i];
  639. if(c=='"')prefix+="\\\"";else
  640. if(c>=' ')prefix+=c;
  641. }
  642. prefix+="\"\r\n";
  643. prefix+="Content-Type: form-data\r\n";
  644. prefix+=S+"Content-Length: "+value.length()+"\r\n"; // data length
  645. prefix+="\r\n";
  646. prefix+=value;
  647. prefix+="\r\n";
  648. }
  649. }
  650. prefix+=S+"--"+boundary+"\r\n";
  651. prefix+="Content-Disposition: form-data; name=\"file\"; filename=\"file\"\r\n";
  652. prefix+="Content-Type: application/octet-stream\r\n";
  653. prefix+="Content-Transfer-Encoding: binary\r\n";
  654. prefix+=S+"Content-Length: "+toSend()+"\r\n";
  655. prefix+="\r\n";
  656. // data is here
  657. suffix+="\r\n";
  658. suffix+=S+"--"+boundary+"--\r\n";
  659. // now when 'prefix' and 'suffix' are ready, setup the main header
  660. command+=S+"Content-Type: multipart/form-data; boundary="+boundary+"\r\n";
  661. command+=S+"Content-Length: "+(prefix.length()+toSend()+suffix.length())+"\r\n";
  662. command+="Cache-Control: no-cache\r\n";
  663. }else
  664. if(has_post_params) // just POST params
  665. {
  666. Bool added=false;
  667. FREPA(params)
  668. {
  669. C HTTPParam &param=params[i]; if(param.type==HTTP_POST)
  670. {
  671. if(added)prefix+='&';
  672. AppendParam(prefix, param);
  673. added=true;
  674. }
  675. }
  676. command+="Content-type: application/x-www-form-urlencoded\r\n";
  677. command+="Content-Transfer-Encoding: binary\r\n";
  678. command+=S+"Content-Length: "+prefix.length()+"\r\n";
  679. }
  680. command+="\r\n";
  681. command+=prefix;
  682. #if !WEB
  683. _pre_send=command.length();
  684. _message.setNum(command.length()+suffix.length());
  685. CopyFast(_message.data() , command(), command.length());
  686. CopyFast(_message.data()+command.length(), suffix (), suffix .length());
  687. if(!_thread.create(DownloadFunc, this, 0, paused, "EE.Download"))error(); // thread as last
  688. #else
  689. // if we're running a game from "esenthel.com" then it cannot access files from "www.esenthel.com" and vice versa
  690. // therefore we detect where the game is from, and if the domain matches, but www is different, then we adjust it
  691. if(_url_full[0]!='/') // ignore following code if we're just accessing the resource from website root like "/site/files/xx"
  692. if(Bool url_has_http=StartsPath(_url_full, "http://")) // we can do this only if we're accessing using full path (to avoid cases when downloading "www.esenthel.com" where it's actually a folder name on the server)
  693. {
  694. if(url_has_http)_url_full=SkipStartPath(_url_full, "http://"); // eat HTTP
  695. Str8 url_domain =GetStart(_url_full);
  696. Str8 document_url_domain =GetStart(SkipStartPath(JavaScriptRunS("document.URL"), "http://"));
  697. Bool document_url_domain_has_www=Starts(document_url_domain, "www.");
  698. Bool url_domain_has_www=Starts( url_domain, "www.");
  699. if(document_url_domain_has_www!=url_domain_has_www) // difference in www
  700. {
  701. if(document_url_domain_has_www)document_url_domain=SkipStart(document_url_domain, "www.");
  702. if( url_domain_has_www) url_domain=SkipStart( url_domain, "www.");
  703. if(url_domain==document_url_domain) // if the domain is the same, case insensitive
  704. {
  705. if(document_url_domain_has_www)_url_full=S+"www."+_url_full; // the document has www, so we need to add www prefix
  706. else _url_full=SkipStart(_url_full, "www."); // the document has no www, so we need to remove www prefix
  707. }
  708. }
  709. if(url_has_http)_url_full=S+"http://"+_url_full; // restore HTTP
  710. }
  711. JSDownload &js_download=JSDownloads.New();
  712. js_download.download=this;
  713. _js_download=&js_download;
  714. EM_ASM
  715. ({
  716. var request=Pointer_stringify($0); var url=Pointer_stringify($1); var range_bytes=Pointer_stringify($2);
  717. var post_params=Pointer_stringify($3); var user=$4; var onprogress=$5; var onload=$6; var onerror=$7;
  718. var xhr=new XMLHttpRequest();
  719. xhr.open(request, url, true);
  720. xhr.responseType='arraybuffer';
  721. if(range_bytes!='')xhr.setRequestHeader('Range', range_bytes);
  722. // progress
  723. xhr.onprogress=function http_onprogress(e)
  724. {
  725. if(user!=null)
  726. {
  727. var date=xhr.getResponseHeader('Last-Modified'); date=((date!=null) ? new Date(date).getTime()/1000 : 0);
  728. Runtime.dynCall('viiiii', onprogress, [user, xhr.status, e.loaded, e.total, date]);
  729. }
  730. };
  731. // done
  732. xhr.onload=function http_onload(e)
  733. {
  734. if(user!=null)
  735. {
  736. var content_length=xhr.getResponseHeader('Content-Length'); if(content_length===null)content_length=-1; // needed for HEAD requests
  737. var content_range =xhr.getResponseHeader('Content-Range'); var cr=-1; if(content_range!=null){var i=content_range.indexOf('/'); if(i>=0)cr=parseInt(content_range.substr(i+1));}
  738. var date=xhr.getResponseHeader('Last-Modified'); date=((date!=null) ? new Date(date).getTime()/1000 : 0);
  739. var byteArray=new Uint8Array(xhr.response);
  740. var buffer=_malloc(byteArray.length);
  741. HEAPU8.set(byteArray, buffer);
  742. Runtime.dynCall('viiiiiii', onload, [user, xhr.status, buffer, byteArray.length, content_length, cr, date]);
  743. _free(buffer);
  744. user=null;
  745. }
  746. };
  747. // error
  748. xhr.onerror=function http_onerror(e)
  749. {
  750. if(user!=null)
  751. {
  752. Runtime.dynCall('vii', onerror, [user, xhr.status]);
  753. user=null;
  754. }
  755. };
  756. // limit the number of redirections
  757. try{if(xhr.channel instanceof Ci.nsIHttpChannel)xhr.channel.redirectionLimit=0;}catch(ex){}
  758. // send
  759. if(request!="POST")xhr.send(null);else
  760. {
  761. xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  762. xhr.setRequestHeader("Content-length", post_params.length);
  763. xhr.setRequestHeader("Connection", "close");
  764. xhr.send(post_params);
  765. }
  766. }, request(), _url_full(), bytes(), prefix(), _js_download, OnProgress, OnDone, OnError);
  767. #endif
  768. return T;
  769. }
  770. Download& Download::pause ( ) {_thread.pause ( ); return T;}
  771. Download& Download::resume( ) {_thread.resume( ); return T;}
  772. Download& Download::stop ( ) {_thread.stop ( ); return T;}
  773. Download& Download::wait (Int milliseconds) {_thread.wait (milliseconds); return T;}
  774. void Download::finish( ) {_state=((code()>=200 && code()<300) ? DWNL_DONE : DWNL_ERROR);}
  775. Bool Download::error ( ) // !! this is not thread-safe !! this can be called only on the download thread or if the thread is not running
  776. {
  777. stop();
  778. delPartial();
  779. _state=DWNL_ERROR; // set this as last
  780. return false;
  781. }
  782. Bool Download::authFailed()C {return FlagTest(_flags, AUTH_FAILED);}
  783. /******************************************************************************/
  784. }
  785. /******************************************************************************/