| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827 |
- /******************************************************************************/
- #include "stdafx.h"
- namespace EE{
- /******************************************************************************
- For 'Download' 'Memb' is good for storing data,
- because speed of accessing data is not important,
- however files can be big and Memb only allocates new chunks of data,
- while Memc requires reallocation and copying.
- /******************************************************************************/
- #define BUF_SIZE (64*1024) // 64 KB
- /******************************************************************************/
- // DOWNLOAD
- /******************************************************************************/
- static void EncodeChar(Str8 &text, Char8 c)
- {
- text+='%';
- text+=Digits16[Byte(c)>>4];
- text+=Digits16[Byte(c)&15];
- }
- static Bool AppendUrlPath(Str8 &text, C Str8 &path)
- {
- Bool has_params=false;
- text.reserve(text.length()+path.length());
- FREPA(path)
- {
- Char8 c=path[i];
- 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
- if(c=='\\') // use Unix style paths
- {
- text+='/';
- }else
- {
- if(c=='?')has_params=true;
- text+=c;
- }
- }
- return has_params;
- }
- static void AppendParam(Str8 &text, C TextParam ¶m)
- {
- C Str8 &name=UTF8(param.name);
- FREPA(name) // set name
- {
- Char8 c=name[i];
- 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
- else text+=c;
- }
- text+='=';
- C Str8 &value=UTF8(param.value);
- FREPA(value) // set value
- {
- Char8 c=value[i];
- 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
- else text+=c;
- }
- }
- Str8 HTTPParam::Encode(C MemPtr<HTTPParam> ¶ms)
- {
- Str8 s; FREPA(params)
- {
- if(i)s+='&';
- AppendParam(s, params[i]);
- }
- return s;
- }
- static Str8 GetHeaders(C Str8 &url, CChar8 *request)
- {
- CChar8 *name =_SkipStartPath(_SkipStartPath(url, "http://"), "https://");
- Str8 headers =S+request+" /"+GetStartNot(name)+" HTTP/1.1\r\nHost: "+_GetStart(name)+"\r\n";
- headers+="Connection: close\r\n"; // connection will be closed after receiving all data
- #if WINDOWS
- headers+="User-Agent: Esenthel Windows\r\n";
- #elif MAC
- headers+="User-Agent: Esenthel Mac\r\n";
- #elif LINUX
- headers+="User-Agent: Esenthel Linux\r\n";
- #elif ANDROID
- headers+="User-Agent: Esenthel Android\r\n";
- #elif IOS
- headers+=(D.smallSize() ? "User-Agent: Esenthel iPhone\r\n" : "User-Agent: Esenthel iPad\r\n");
- #endif
- return headers;
- }
- #if WEB
- struct JSDownload
- {
- Download *download;
- };
- Memx<JSDownload> JSDownloads;
- static SyncLock JSDownloadsLock;
- static void OnProgress(Ptr user, Int code, Int done, Int total, UInt timestamp)
- {
- if(JSDownloads.elms()) // check in case 'JSDownloads' destructor was already called
- {
- JSDownload &js_download=*(JSDownload*)user;
- SyncLocker lock(JSDownloadsLock);
- if(Download *download=js_download.download)
- {
- download->_code=code;
- if(timestamp)download->_modif_time.from1970s(timestamp);
- if(download->_size) // expecting size
- {
- download->_done=done;
- download->_size=total;
- }
- download->_state=DWNL_DOWNLOAD;
- }
- }
- }
- static void OnDone(Ptr user, Int code, CPtr data, Int data_size, Int content_length, Int content_range, UInt timestamp)
- {
- if(JSDownloads.elms()) // check in case 'JSDownloads' destructor was already called
- {
- JSDownload &js_download=*(JSDownload*)user;
- {
- SyncLocker lock(JSDownloadsLock);
- if(Download *download=js_download.download)
- {
- download->_code=code;
- if(timestamp)download->_modif_time.from1970s(timestamp);
- if(content_length<0)content_length=data_size;
- if(download->_size) // expecting size
- {
- download->_size=content_length;
- download->_done=data_size;
- Copy(download->_data=Alloc(data_size), data, data_size);
- }else
- {
- download->_total_size=content_length;
- }
- if(download->_total_size<0)download->_total_size=((content_range>=0) ? content_range : download->_size);
- download->_js_download=null; // unlink
- download->finish(); // !! set at the end !!
- }
- }
- JSDownloads.removeData(&js_download);
- }
- }
- static void OnError(Ptr user, Int code)
- {
- if(JSDownloads.elms()) // check in case 'JSDownloads' destructor was already called
- {
- JSDownload &js_download=*(JSDownload*)user;
- {
- SyncLocker lock(JSDownloadsLock);
- if(Download *download=js_download.download)
- {
- download->_code=code;
- download->_js_download=null; // unlink
- download->_state=DWNL_ERROR; // !! set at the end !!
- }
- }
- JSDownloads.removeData(&js_download);
- }
- }
- #endif
- /******************************************************************************/
- void Download::parse(Byte *data, Int size)
- {
- if(data)for(; size>0; )switch(_parse)
- {
- case PARSE_DATA_SIZE:
- {
- for(; size>0; )
- {
- Byte c=*data++; size--;
- if(c=='\n')
- {
- _parse=(_expected_size ? PARSE_DATA : PARSE_END);
- break;
- }
- Int i=CharInt(c);
- if(InRange(i, 16))_expected_size=_expected_size*16+i;else continue;
- }
- }break;
- case PARSE_DATA:
- {
- Int left =Min(size, _expected_size);
- Int start=_memb.addNum(left);
- REP(left)_memb[start+i]=data[i];
- size -=left;
- data +=left;
- _done +=left;
- _expected_size-=left;
- if(!_expected_size)_parse=PARSE_SKIP_LINE;
- }break;
- case PARSE_SKIP_LINE:
- {
- for(; size>0; )
- {
- Byte c=*data++; size--;
- if(c=='\n')
- {
- _parse=PARSE_DATA_SIZE;
- break;
- }
- }
- }break;
- default: return;
- }
- }
- /******************************************************************************/
- #if !WEB
- #if SUPPORT_MBED_TLS
- static int DownloadSendFunc(Ptr ctx, C Byte *buf, size_t len)
- {
- 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;
- d._total_sent+=r;
- return r;
- }
- static int DownloadReceiveFunc(Ptr ctx, Byte *buf, size_t len)
- {
- 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;
- d._total_rcvd+=r;
- return r;
- }
- static int DownloadReceiveFuncTimeout(Ptr ctx, Byte *buf, size_t len, uint32_t timeout)
- {
- 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
- return DownloadReceiveFunc(ctx, buf, len);
- }
- static Bool Again(Int r, Download &d)
- {
- // 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
- switch(r)
- {
- case MBEDTLS_ERR_SSL_WANT_READ :
- case MBEDTLS_ERR_SSL_TIMEOUT : if(!d._thread.wantStop()){d._socket.wait (DOWNLOAD_WAIT_TIME); return true;} break;
- case MBEDTLS_ERR_SSL_WANT_WRITE: if(!d._thread.wantStop()){d._socket.flush(DOWNLOAD_WAIT_TIME); return true;} break;
- }
- return false;
- }
- #endif
- Int Download::send(CPtr data, Int size)
- {
- #if SUPPORT_MBED_TLS
- if(_socket._secure)
- {
- again:
- Int r=mbedtls_ssl_write(_socket._secure, (Byte*)data, size); if(Again(r, T))goto again;
- return r;
- }
- #endif
- Int r=_socket.Socket::send(data, size); if(r>0)_total_sent+=r; return r;
- }
- Int Download::receive(Ptr data, Int size)
- {
- #if SUPPORT_MBED_TLS
- if(_socket._secure)
- {
- again:
- Int r=mbedtls_ssl_read(_socket._secure, (Byte*)data, size); if(Again(r, T))goto again;
- return r;
- }
- #endif
- Int r=_socket.Socket::receive(data, size); if(r>0)_total_rcvd+=r; return r;
- }
- static Bool DownloadFunc(Thread &thread) {return ((Download*)thread.user)->func();}
- Bool Download::func()
- {
- Byte data[BUF_SIZE];
- switch(_state)
- {
- case DWNL_CONNECTING:
- {
- // initialize connection
- if(!_socket.is())
- {
- SockAddr addr;
- if(!hasAddrsHeader())
- {
- _flags|=HAS_ADDRS_HEADER;
- CChar8 *url=_url_full(); Int port;
- if(StartsPath(url, "https://"))
- {
- url+=8; port=443; // 443 = default HTTPS port
- if(!_socket.secure(url))return error();
- #if SUPPORT_MBED_TLS
- mbedtls_ssl_set_bio(_socket._secure, this, DownloadSendFunc, DownloadReceiveFunc, DownloadReceiveFuncTimeout);
- #endif
- }else
- if(StartsPath(url, "http://"))
- {
- url+=7; port=80; // 80 = default HTTP port
- }else
- return error();
- Char8 temp[MAX_LONG_PATH], *url_start=_GetStart(url, temp);
- if(Char8 *url_port=TextPos(url_start, ':')) // check for port override, example URL "http://www.example.com:8080/path/"
- {
- port=TextInt(url_port+1); // ":8080" +1 to skip ':'
- *url_port='\0'; // have to remove port specifier because 'GetHostAddresses' will fail
- }
- Memt<SockAddr> addresses;
- GetHostAddresses(addresses, url_start, port);
- if(!addresses.elms())return error();
- addresses.reverseOrder(); // recommended order for processing is from start to end, however since we're going to use 'pop' then reverse order
- 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
- }else
- {
- if(!_addrs.elms())return error();
- addr=_addrs.pop();
- }
- if( !_socket.createTcp(addr))return error();
- switch(_socket.connect (addr, DOWNLOAD_WAIT_TIME))
- {
- case Socket::IN_PROGRESS: break;
- case Socket::CONNECTED : FlagDisable(_flags, HAS_ADDRS_HEADER); _state=DWNL_AUTH; goto auth;
- default : _socket.del(); break;
- }
- }else
- {
- if(_socket.any(DOWNLOAD_WAIT_TIME)){FlagDisable(_flags, HAS_ADDRS_HEADER); _state=DWNL_AUTH; goto auth;}
- if(_socket.connectFailed())_socket.del();
- }
- }break;
- case DWNL_AUTH: auth:
- {
- switch(_socket.handshake())
- {
- case SecureSocket::OK : ok: _state=DWNL_SENDING; goto sending;
- 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
- 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
- case SecureSocket::BAD_CERT : _flags|=AUTH_FAILED; if(_flags&AUTH_IGNORE)goto ok; goto error;
- default : error: return error(); // error
- }
- }break;
- case DWNL_SENDING: sending:
- {
- // send message
- Int left=_pre_send-_pos_send; if(left>0) // header
- {
- send_message:;
- if(_socket.flush(DOWNLOAD_WAIT_TIME))
- {
- Int sent=send(_message.data()+_pos_send, left); if(sent<=0)return error();
- _pos_send+=sent;
- }
- }else
- {
- Long file_left=_to_send-_sent; if(file_left>0) // file data
- {
- if(_socket.flush(DOWNLOAD_WAIT_TIME))
- {
- Int buf_left=Min(BUF_SIZE, file_left); if(!_post_file->get(data, buf_left))return error();
- Int sent=send(data, buf_left); if(sent<=0)return error();
- _sent+=sent;
- if(!_post_file->skip(sent-buf_left))return error(); // go back to unsent data
- }
- }else
- {
- left=_message.elms()-_pos_send; if(left>0)goto send_message; // footer
- _state=DWNL_DOWNLOAD; goto downloading;
- }
- }
- }break;
- case DWNL_DOWNLOAD: downloading:
- {
- if(_socket.wait(DOWNLOAD_WAIT_TIME))
- {
- if(!hasAddrsHeader()) // download header
- {
- Byte *rest;
- Int rcvd=receive(data, BUF_SIZE); if(rcvd<=0)return error();
- _header.reserve(_header.length()+rcvd);
- FREP(rcvd)
- {
- _header+=Char8(data[i]);
- 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
- {
- _flags|=HAS_ADDRS_HEADER;
- i++;
- rest =data+i;
- rcvd-=i;
- break;
- }
- }
- if(hasAddrsHeader())
- {
- // example header = "HTTP/1.1 200 OK"
- if(!Starts(_header, "HTTP/1.1", false, true))return error();
- if(CChar8 *t=TextPos(_header, "Location:")) // check if it's a redirect (check this as first before all other data)
- {
- Int line =TextPosI(t, "\r\n");
- if( line>=0)ConstCast(t)[line]=0;
- CChar8 *url=_SkipWhiteChars(t+9); // operate on 'url' before clearing the '_header'
- if(!Is(url))return error(); // no path specified
- if(StartsPath(url, "http://") || StartsPath(url, "https://")) // if this is a full path then just use it
- {
- AppendUrlPath(_url_full.clear(), url);
- }else
- { // relative path
- if(url[0]=='/') // start of the domain
- {
- Int pos =TextPosIN(_url_full, '/', 2); // find the 3rd slash in the url "http://www.esenthel.com/index.php"
- if( pos>=0)_url_full.clip(pos); // remove the slash and everything after it, leaving "http://www.esenthel.com"
- AppendUrlPath(_url_full, url);
- }else // relative to current location
- {
- FREPA(_url_full)if(_url_full[i]=='?'){_url_full.clip(i ); break;} // first remove any parameters that were specified
- REPA(_url_full)if(_url_full[i]=='/'){_url_full.clip(i+1); break;} // remove base name, but keep the tail slash
- AppendUrlPath(_url_full, url);
- //_url_full =NormalizePath(_url_full); // this is not needed because the server will handle this properly
- }
- }
- FlagDisable(_flags, HAS_ADDRS_HEADER);
- _header.clear();
- _socket.del(); // unsecure and delete the socket because we will need to reconnect to a different address
- _state=DWNL_CONNECTING;
- _addrs.clear();
- _pos_send=0;
- Str8 bytes,
- command=GetHeaders(_url_full, (_size==0) ? "HEAD" : "GET");
- if(_size>0 )bytes =S+"bytes="+_offset+'-'+(_offset+_size-1);else
- if(_size && _offset)bytes =S+"bytes="+_offset+'-';
- if(bytes.is() )command+=S+"Range: "+bytes+"\r\n";
- command+="\r\n";
- _pre_send=command.length();
- _message.setNum(command.length());
- CopyFast(_message.data(), command(), command.length());
- break;
- }
- _code=TextInt(_header()+8+1); // 8=Length("HTTP/1.1")
- // !! 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 !!
- Long content_length=-1; if(CChar8 *t=TextPos(_header, "Content-Length:"))content_length=TextLong(t+Length("Content-Length:"));
- if(_size)_size=content_length;else _total_size=content_length;
- if(_total_size<0)if(CChar8 *t=TextPos(TextPos(_header, "Content-Range:" ), '/'))_total_size=TextLong(t+1);else _total_size=_size;
- if(CChar8 *t=TextPos(_header, "Last-Modified:" ))
- {
- t+=Length("Last-Modified:"); // Thu, 24 Mar 2011 18:35:38 GMT
- if(t=_SkipChar(TextPos(t, ',')))
- {
- CalcValue val; if(t=_SkipWhiteChars(TextValue(t, val)))if(val.type)
- {
- _modif_time.day=val.asInt();
- if(Starts(t, "Jan"))_modif_time.month= 1;else
- if(Starts(t, "Feb"))_modif_time.month= 2;else
- if(Starts(t, "Mar"))_modif_time.month= 3;else
- if(Starts(t, "Apr"))_modif_time.month= 4;else
- if(Starts(t, "May"))_modif_time.month= 5;else
- if(Starts(t, "Jun"))_modif_time.month= 6;else
- if(Starts(t, "Jul"))_modif_time.month= 7;else
- if(Starts(t, "Aug"))_modif_time.month= 8;else
- if(Starts(t, "Sep"))_modif_time.month= 9;else
- if(Starts(t, "Oct"))_modif_time.month=10;else
- if(Starts(t, "Nov"))_modif_time.month=11;else
- if(Starts(t, "Dec"))_modif_time.month=12;
- if(_modif_time.month)if(t=TextValue(TextPos(t, ' '), val))if(val.type)
- {
- _modif_time.year=val.asInt();
- if(t=TextValue(t, val))if(val.type)
- {
- _modif_time.hour=val.asInt();
- if(*t++==':')if(t=TextValue(t, val))if(val.type)
- {
- _modif_time.minute=val.asInt();
- if(*t++==':')
- {
- TextValue(t, val);
- if(val.type)_modif_time.second=val.asInt();
- }
- }
- }
- }
- }
- }
- }
- // check if it's chunked
- if(CChar8 *t=TextPos(_header, "Transfer-Encoding:"))
- {
- Int line=TextPosI(t, "\r\n");
- if( line>=0)((Char8*)t)[line]=0;
- if(Contains(t, "chunked"))_flags|=CHUNKED;
- }
- // process remaining data
- if(_size>=0) // known size
- {
- _data=Alloc(_size);
- if(rcvd){CopyFast(_data, rest, rcvd); _done+=rcvd;}
- }else // unknown size
- if(chunked()) // chunked
- {
- if(rcvd)parse(rest, rcvd);
- }else // remaining data as long as received
- {
- _memb.addNum(rcvd); REPAO(_memb)=rest[i];
- _done+=rcvd;
- }
- }
- }else
- {
- if(_size>=0) // known size
- {
- Long left=_size-_done;
- if( left>0)
- if(_socket.wait(DOWNLOAD_WAIT_TIME))
- {
- left=receive((Byte*)_data+_done, Min(INT_MAX, left));
- if(left>0)_done+=left;else return error();
- }
- if(_done>=_size)finish(); // set the state at final stage (to avoid multi-threading issues)
- }else
- if(chunked()) // chunked
- {
- if(_parse==PARSE_END) // finished
- {
- _data=Alloc(_memb.elms()); _memb.copyTo((Byte*)_data);
- _size=_total_size=_memb.elms();
- _memb.del();
- finish(); // set the state at final stage (to avoid multi-threading issues)
- }else
- if(_socket.wait(DOWNLOAD_WAIT_TIME))
- {
- Int size=receive(data, BUF_SIZE);
- if( size>0)parse(data, size);else return error();
- }
- }else
- {
- if(_socket.wait(DOWNLOAD_WAIT_TIME))
- {
- Int rcvd=receive(data, BUF_SIZE);
- if( rcvd>0)
- {
- FREP(rcvd)_memb.add(data[i]);
- _done+=rcvd;
- }else
- {
- _data=Alloc(_memb.elms()); _memb.copyTo((Byte*)_data);
- _size=_total_size=_memb.elms();
- _memb.del();
- finish(); // set the state at final stage (to avoid multi-threading issues)
- }
- }
- }
- }
- }
- }break;
- default: delPartial(); return false;
- }
- return true;
- }
- #endif
- /******************************************************************************/
- void Download::zero()
- {
- _flags=_parse=0;
- _state=DWNL_NONE;
- _code=0;
- _expected_size=_pre_send=_pos_send=0;
- _offset=_done=_size=_total_size=_sent=_to_send=_total_sent=_total_rcvd=0;
- _data=null;
- _post_file=null;
- _modif_time.zero();
- #if WEB
- _js_download=null;
- #endif
- }
- Download::Download( ) : _memb(64*1024) {zero(); }
- Download::Download(C Str &url, C MemPtr<HTTPParam> ¶ms, 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);}
- void Download::delPartial() // this deletes only members which can't be accessed on the main thread
- {
- _socket .del ();
- _memb .del ();
- _addrs .clear();
- _message .clear();
- _header .clear();
- _url_full.clear();
- }
- Download& Download::del(Int milliseconds)
- {
- #if WEB
- if(_js_download)
- {
- SyncLocker lock(JSDownloadsLock);
- if(JSDownloads.elms()) // check in case 'JSDownloads' destructor was already called
- 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)
- }
- #endif
- stop();
- _thread.del(milliseconds);
- delPartial();
- _url.clear();
- Free(_data);
- zero(); return T;
- }
- Download& Download::create(C Str &url, C MemPtr<HTTPParam> ¶ms, File *post, Long max_post_size, Long offset, Long size, Bool paused, Bool ignore_auth_result)
- {
- #if WEB
- post=null; // not yet supported
- #endif
- del();
- _flags =(ignore_auth_result ? AUTH_IGNORE : 0);
- _url =url;
- _offset =offset;
- _size =size;
- _total_size=-1; // unknown total size
- _to_send =(post ? ((max_post_size<0) ? post->left() : max_post_size) : 0);
- _post_file =post;
- _state =DWNL_CONNECTING; // this must be set immediately, so that we mark this 'Download' as used
- // set url full
- Bool url_has_params=AppendUrlPath(_url_full, UTF8(url)), has_post_params=false;
- FREPA(params) // add parameters
- {
- C HTTPParam ¶m=params[i]; switch(param.type)
- {
- case HTTP_POST: has_post_params=true; break;
- case HTTP_GET :
- {
- _url_full+=(url_has_params ? '&' : '?'); // first param must be started with '?', others with '&'
- url_has_params=true;
- AppendParam(_url_full, param);
- }break;
- }
- }
- // set message
- Str8 prefix, suffix, boundary, bytes,
- request=((_post_file || has_post_params) ? "POST" : (_size==0) ? "HEAD" : "GET"),
- command=GetHeaders(_url_full, request);
- if(_size>0 )bytes =S+"bytes="+_offset+'-'+(_offset+_size-1);else
- if(_size && _offset)bytes =S+"bytes="+_offset+'-';
- if(bytes.is() )command+=S+"Range: "+bytes+"\r\n";
- FREPA(params) // header params
- {
- C HTTPParam ¶m=params[i]; if(param.type==HTTP_HEADER)command+=UTF8(param.name)+": "+UTF8(param.value)+"\r\n";
- }
- if(_post_file)
- {
- // sub header
- boundary="d5ubjyl0q4rwb8j4grz5vaxgnj2l1lpuh4ku"; boundary+=Random(); // 'boundary' must not be present in the file data !! otherwise the upload will fail
- FREPA(params) // params
- {
- C HTTPParam ¶m=params[i]; if(param.type==HTTP_POST)
- {
- C Str8 &name=UTF8(param.name), &value=UTF8(param.value);
- prefix+=S+"--"+boundary+"\r\n";
- prefix+="Content-Disposition: form-data; name=\"";
- FREPA(name)
- {
- Char8 c=name[i];
- if(c=='"')prefix+="\\\"";else
- if(c>=' ')prefix+=c;
- }
- prefix+="\"\r\n";
- prefix+="Content-Type: form-data\r\n";
- prefix+=S+"Content-Length: "+value.length()+"\r\n"; // data length
- prefix+="\r\n";
- prefix+=value;
- prefix+="\r\n";
- }
- }
- prefix+=S+"--"+boundary+"\r\n";
- prefix+="Content-Disposition: form-data; name=\"file\"; filename=\"file\"\r\n";
- prefix+="Content-Type: application/octet-stream\r\n";
- prefix+="Content-Transfer-Encoding: binary\r\n";
- prefix+=S+"Content-Length: "+toSend()+"\r\n";
- prefix+="\r\n";
- // data is here
- suffix+="\r\n";
- suffix+=S+"--"+boundary+"--\r\n";
- // now when 'prefix' and 'suffix' are ready, setup the main header
- command+=S+"Content-Type: multipart/form-data; boundary="+boundary+"\r\n";
- command+=S+"Content-Length: "+(prefix.length()+toSend()+suffix.length())+"\r\n";
- command+="Cache-Control: no-cache\r\n";
- }else
- if(has_post_params) // just POST params
- {
- Bool added=false;
- FREPA(params)
- {
- C HTTPParam ¶m=params[i]; if(param.type==HTTP_POST)
- {
- if(added)prefix+='&';
- AppendParam(prefix, param);
- added=true;
- }
- }
- command+="Content-type: application/x-www-form-urlencoded\r\n";
- command+="Content-Transfer-Encoding: binary\r\n";
- command+=S+"Content-Length: "+prefix.length()+"\r\n";
- }
- command+="\r\n";
- command+=prefix;
- #if !WEB
- _pre_send=command.length();
- _message.setNum(command.length()+suffix.length());
- CopyFast(_message.data() , command(), command.length());
- CopyFast(_message.data()+command.length(), suffix (), suffix .length());
- if(!_thread.create(DownloadFunc, this, 0, paused, "EE.Download"))error(); // thread as last
- #else
- // if we're running a game from "esenthel.com" then it cannot access files from "www.esenthel.com" and vice versa
- // therefore we detect where the game is from, and if the domain matches, but www is different, then we adjust it
- if(_url_full[0]!='/') // ignore following code if we're just accessing the resource from website root like "/site/files/xx"
- 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)
- {
- if(url_has_http)_url_full=SkipStartPath(_url_full, "http://"); // eat HTTP
- Str8 url_domain =GetStart(_url_full);
- Str8 document_url_domain =GetStart(SkipStartPath(JavaScriptRunS("document.URL"), "http://"));
- Bool document_url_domain_has_www=Starts(document_url_domain, "www.");
- Bool url_domain_has_www=Starts( url_domain, "www.");
- if(document_url_domain_has_www!=url_domain_has_www) // difference in www
- {
- if(document_url_domain_has_www)document_url_domain=SkipStart(document_url_domain, "www.");
- if( url_domain_has_www) url_domain=SkipStart( url_domain, "www.");
- if(url_domain==document_url_domain) // if the domain is the same, case insensitive
- {
- if(document_url_domain_has_www)_url_full=S+"www."+_url_full; // the document has www, so we need to add www prefix
- else _url_full=SkipStart(_url_full, "www."); // the document has no www, so we need to remove www prefix
- }
- }
- if(url_has_http)_url_full=S+"http://"+_url_full; // restore HTTP
- }
- JSDownload &js_download=JSDownloads.New();
- js_download.download=this;
- _js_download=&js_download;
- EM_ASM
- ({
- var request=Pointer_stringify($0); var url=Pointer_stringify($1); var range_bytes=Pointer_stringify($2);
- var post_params=Pointer_stringify($3); var user=$4; var onprogress=$5; var onload=$6; var onerror=$7;
- var xhr=new XMLHttpRequest();
- xhr.open(request, url, true);
- xhr.responseType='arraybuffer';
- if(range_bytes!='')xhr.setRequestHeader('Range', range_bytes);
- // progress
- xhr.onprogress=function http_onprogress(e)
- {
- if(user!=null)
- {
- var date=xhr.getResponseHeader('Last-Modified'); date=((date!=null) ? new Date(date).getTime()/1000 : 0);
- Runtime.dynCall('viiiii', onprogress, [user, xhr.status, e.loaded, e.total, date]);
- }
- };
- // done
- xhr.onload=function http_onload(e)
- {
- if(user!=null)
- {
- var content_length=xhr.getResponseHeader('Content-Length'); if(content_length===null)content_length=-1; // needed for HEAD requests
- 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));}
- var date=xhr.getResponseHeader('Last-Modified'); date=((date!=null) ? new Date(date).getTime()/1000 : 0);
- var byteArray=new Uint8Array(xhr.response);
- var buffer=_malloc(byteArray.length);
- HEAPU8.set(byteArray, buffer);
- Runtime.dynCall('viiiiiii', onload, [user, xhr.status, buffer, byteArray.length, content_length, cr, date]);
- _free(buffer);
- user=null;
- }
- };
-
- // error
- xhr.onerror=function http_onerror(e)
- {
- if(user!=null)
- {
- Runtime.dynCall('vii', onerror, [user, xhr.status]);
- user=null;
- }
- };
- // limit the number of redirections
- try{if(xhr.channel instanceof Ci.nsIHttpChannel)xhr.channel.redirectionLimit=0;}catch(ex){}
-
- // send
- if(request!="POST")xhr.send(null);else
- {
- xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
- xhr.setRequestHeader("Content-length", post_params.length);
- xhr.setRequestHeader("Connection", "close");
- xhr.send(post_params);
- }
- }, request(), _url_full(), bytes(), prefix(), _js_download, OnProgress, OnDone, OnError);
- #endif
- return T;
- }
- Download& Download::pause ( ) {_thread.pause ( ); return T;}
- Download& Download::resume( ) {_thread.resume( ); return T;}
- Download& Download::stop ( ) {_thread.stop ( ); return T;}
- Download& Download::wait (Int milliseconds) {_thread.wait (milliseconds); return T;}
- void Download::finish( ) {_state=((code()>=200 && code()<300) ? DWNL_DONE : DWNL_ERROR);}
- Bool Download::error ( ) // !! this is not thread-safe !! this can be called only on the download thread or if the thread is not running
- {
- stop();
- delPartial();
- _state=DWNL_ERROR; // set this as last
- return false;
- }
- Bool Download::authFailed()C {return FlagTest(_flags, AUTH_FAILED);}
- /******************************************************************************/
- }
- /******************************************************************************/
|