diff --git a/src/megabasterd/ChunkDownloader.java b/src/megabasterd/ChunkDownloader.java index 4a246efc4..9979f7a2e 100644 --- a/src/megabasterd/ChunkDownloader.java +++ b/src/megabasterd/ChunkDownloader.java @@ -127,10 +127,10 @@ public class ChunkDownloader implements Runnable, SecureNotifiable { chunk = new Chunk(_download.nextChunkId(), _download.getFile_size(), worker_url); URL url = new URL(chunk.getUrl()); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setConnectTimeout(MainPanel.CONNECTION_TIMEOUT); - conn.setRequestProperty("User-Agent", MegaAPI.USER_AGENT); - conn.setRequestProperty("Connection", "close"); + + KissHttpURLConnection kissconn = new KissHttpURLConnection(url); + + kissconn.doGET(); error = false; @@ -138,9 +138,9 @@ public class ChunkDownloader implements Runnable, SecureNotifiable { if(!_exit && !_download.isStopped()) { - is = new ThrottledInputStream(conn.getInputStream(), _download.getMain_panel().getStream_supervisor()); + is = new ThrottledInputStream(kissconn.getInputStream(), _download.getMain_panel().getStream_supervisor()); - http_status = conn.getResponseCode(); + http_status = kissconn.getStatus_code(); if ( http_status != HttpURLConnection.HTTP_OK ) { @@ -236,7 +236,8 @@ public class ChunkDownloader implements Runnable, SecureNotifiable { getLogger(ChunkDownloader.class.getName()).log(Level.SEVERE, null, ex); } finally { - conn.disconnect(); + + kissconn.close(); } } diff --git a/src/megabasterd/ChunkDownloaderMono.java b/src/megabasterd/ChunkDownloaderMono.java index 8c7ae5896..581772970 100644 --- a/src/megabasterd/ChunkDownloaderMono.java +++ b/src/megabasterd/ChunkDownloaderMono.java @@ -5,6 +5,7 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.logging.Level; +import java.util.logging.Logger; import static java.util.logging.Logger.getLogger; import static megabasterd.MainPanel.THROTTLE_SLICE_SIZE; import static megabasterd.MiscTools.getWaitTimeExpBackOff; @@ -31,7 +32,7 @@ public class ChunkDownloaderMono extends ChunkDownloader { System.out.println("Worker ["+getId()+"]: let's do some work!"); - HttpURLConnection conn=null; + KissHttpURLConnection kissconn = null; try { @@ -50,17 +51,19 @@ public class ChunkDownloaderMono extends ChunkDownloader { worker_url=getDownload().getDownloadUrlForWorker(); } - chunk = new Chunk(getDownload().nextChunkId(), getDownload().getFile_size(), worker_url); + chunk = new Chunk(getDownload().nextChunkId(), getDownload().getFile_size(), null); if(url == null || error) { url = new URL(worker_url+"/"+chunk.getOffset()); - conn = (HttpURLConnection) url.openConnection(); - conn.setConnectTimeout(MainPanel.CONNECTION_TIMEOUT); - conn.setRequestProperty("User-Agent", MegaAPI.USER_AGENT); - conn.setRequestProperty("Connection", "close"); - is = new ThrottledInputStream(conn.getInputStream(), getDownload().getMain_panel().getStream_supervisor()); - http_status = conn.getResponseCode(); + + kissconn = new KissHttpURLConnection(url); + + kissconn.doGET(); + + is = new ThrottledInputStream(kissconn.getInputStream(), getDownload().getMain_panel().getStream_supervisor()); + + http_status = kissconn.getStatus_code(); } error = false; @@ -164,11 +167,15 @@ public class ChunkDownloaderMono extends ChunkDownloader { getDownload().emergencyStopDownloader(ex.getMessage()); } finally { - - if(conn!=null) { - - conn.disconnect(); + + if(kissconn != null) { + try { + kissconn.close(); + } catch (IOException ex) { + Logger.getLogger(ChunkDownloaderMono.class.getName()).log(Level.SEVERE, null, ex); + } } + } getDownload().stopThisSlot(this); diff --git a/src/megabasterd/ChunkUploader.java b/src/megabasterd/ChunkUploader.java index 1f6a4bd41..8b673b6c9 100644 --- a/src/megabasterd/ChunkUploader.java +++ b/src/megabasterd/ChunkUploader.java @@ -144,13 +144,10 @@ public class ChunkUploader implements Runnable, SecureNotifiable { }while(!_exit && !_upload.isStopped() && chunk.getOutputStream().size() 0 || conn.getContentLengthLong() == -1) && _upload.getCompletion_handle() == null) { + String content_length = kissconn.getResponseHeader("Content-Length"); + + if((content_length == null || Long.parseLong(content_length) > 0) && _upload.getCompletion_handle() == null) { - String content_encoding = conn.getContentEncoding(); + String content_encoding = kissconn.getResponseHeader("Content-Encoding"); - InputStream is=(content_encoding!=null && content_encoding.equals("gzip"))?new GZIPInputStream(conn.getInputStream()):conn.getInputStream(); + InputStream is=(content_encoding!=null && content_encoding.equals("gzip"))?new GZIPInputStream(kissconn.getInputStream()):kissconn.getInputStream(); ByteArrayOutputStream byte_res = new ByteArrayOutputStream(); @@ -295,7 +294,7 @@ public class ChunkUploader implements Runnable, SecureNotifiable { getLogger(ChunkUploader.class.getName()).log(Level.SEVERE, null, ex); } finally { - conn.disconnect(); + kissconn.close(); } } diff --git a/src/megabasterd/ChunkUploaderMono.java b/src/megabasterd/ChunkUploaderMono.java index 892114904..c7efbdc73 100644 --- a/src/megabasterd/ChunkUploaderMono.java +++ b/src/megabasterd/ChunkUploaderMono.java @@ -16,6 +16,7 @@ import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.logging.Level; +import java.util.logging.Logger; import static java.util.logging.Logger.getLogger; import java.util.zip.GZIPInputStream; import javax.crypto.CipherInputStream; @@ -43,7 +44,7 @@ public class ChunkUploaderMono extends ChunkUploader { byte[] buffer = new byte[MainPanel.THROTTLE_SLICE_SIZE]; boolean error = false; OutputStream out=null; - HttpURLConnection conn=null; + KissHttpURLConnection kissconn = null; try { @@ -55,7 +56,7 @@ public class ChunkUploaderMono extends ChunkUploader { while(!isExit() && !getUpload().isStopped()) { - chunk = new Chunk(getUpload().nextChunkId(), getUpload().getFile_size(), worker_url); + chunk = new Chunk(getUpload().nextChunkId(), getUpload().getFile_size(), null); f.seek(chunk.getOffset()); @@ -72,14 +73,13 @@ public class ChunkUploaderMono extends ChunkUploader { if(url == null || error) { url = new URL(worker_url+"/"+chunk.getOffset()); - conn = (HttpURLConnection) url.openConnection(); - conn.setConnectTimeout(MainPanel.CONNECTION_TIMEOUT); - conn.setDoOutput(true); - conn.setRequestMethod("POST"); - conn.setRequestProperty("User-Agent", MegaAPI.USER_AGENT); - conn.setRequestProperty("Connection", "close"); - conn.setFixedLengthStreamingMode(getUpload().getFile_size()-chunk.getOffset()); - out = new ThrottledOutputStream(conn.getOutputStream(), getUpload().getMain_panel().getStream_supervisor()); + + kissconn = new KissHttpURLConnection(url); + + kissconn.doPOST(getUpload().getFile_size()-chunk.getOffset()); + + out = new ThrottledOutputStream(kissconn.getOutputStream(), getUpload().getMain_panel().getStream_supervisor()); + } tot_bytes_up=0; @@ -179,23 +179,25 @@ public class ChunkUploaderMono extends ChunkUploader { } - if(!error && chunk.getOffset() + tot_bytes_up == getUpload().getFile_size() && conn != null) { + if(!error && chunk.getOffset() + tot_bytes_up == getUpload().getFile_size() && kissconn != null) { - http_status = conn.getResponseCode(); + http_status = kissconn.getStatus_code(); + + String content_length = kissconn.getResponseHeader("Content-Length"); if (http_status != HttpURLConnection.HTTP_OK ) { throw new IOException("UPLOAD FAILED! (HTTP STATUS: "+ http_status+")"); - } else if(!(conn.getContentLengthLong() > 0 || conn.getContentLengthLong() == -1)) { + } else if(!(content_length == null || Long.parseLong(content_length) > 0)) { throw new IOException("UPLOAD FAILED! (Empty completion handle!)"); } else { - String content_encoding = conn.getContentEncoding(); + String content_encoding = kissconn.getResponseHeader("Content-Encoding"); - InputStream is=(content_encoding!=null && content_encoding.equals("gzip"))?new GZIPInputStream(conn.getInputStream()):conn.getInputStream(); + InputStream is=(content_encoding!=null && content_encoding.equals("gzip"))?new GZIPInputStream(kissconn.getInputStream()):kissconn.getInputStream(); ByteArrayOutputStream byte_res = new ByteArrayOutputStream(); @@ -234,9 +236,13 @@ public class ChunkUploaderMono extends ChunkUploader { } finally { - if(conn!=null) { + if(kissconn!=null) { - conn.disconnect(); + try { + kissconn.close(); + } catch (IOException ex) { + Logger.getLogger(ChunkUploaderMono.class.getName()).log(Level.SEVERE, null, ex); + } } } diff --git a/src/megabasterd/KissHttpURLConnection.java b/src/megabasterd/KissHttpURLConnection.java new file mode 100644 index 000000000..6512e11ec --- /dev/null +++ b/src/megabasterd/KissHttpURLConnection.java @@ -0,0 +1,443 @@ +package megabasterd; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.CookieManager; +import java.net.Socket; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +/** + * + * @author tonikelope + * + * EXPERIMENTAL!! + */ +public class KissHttpURLConnection { + + private URL _url; + private int _status_code; + private final Map> _response_headers; + private final Map> _request_headers; + private Socket _socket; + private final CookieManager _cookie_manager; + private boolean _cookies_put; + + public KissHttpURLConnection(URL url) { + + _url = url; + _response_headers = new HashMap<>(); + _request_headers = new HashMap<>(); + _cookie_manager = new CookieManager(); + _cookies_put=false; + _socket = null; + _status_code = -1; + } + + public CookieManager getCookie_manager() { + return _cookie_manager; + } + + public InputStream getInputStream() throws IOException { + + try { + + if(_status_code == -1) { + + _parseStatusCode(); + } + + if(_response_headers.isEmpty()) { + + _parseResponseHeaders(); + } + + if(!_cookies_put) { + + _cookie_manager.put(_url.toURI(), _response_headers); + _cookies_put = true; + } + + } catch (URISyntaxException ex) { + Logger.getLogger(KissHttpURLConnection.class.getName()).log(Level.SEVERE, null, ex); + } + + return _socket.getInputStream(); + } + + public OutputStream getOutputStream() throws IOException { + + return _socket.getOutputStream(); + } + + public Map> getRequest_headers() { + + return _request_headers; + } + + public Map> getResponse_headers() { + + return _response_headers; + } + + public String getRequestHeader(String key) { + + return getRequestHeader(key, 0); + } + + public String getRequestHeader(String key, int pos) { + + return _request_headers.containsKey(_capitalizeHeaderKey(key))?_request_headers.get(_capitalizeHeaderKey(key)).get(pos):null; + } + + public String getResponseHeader(String key) { + + return getResponseHeader(key, 0); + } + + public String getResponseHeader(String key, int pos) { + + return _response_headers.containsKey(_capitalizeHeaderKey(key))?_response_headers.get(_capitalizeHeaderKey(key)).get(pos):null; + } + + public void setRequestHeader(String key, String val) { + + key = _capitalizeHeaderKey(key); + + if(!_request_headers.containsKey(key)) { + + List list; + + _request_headers.put(key, list = new ArrayList<>()); + + list.add(val); + + } else { + + _request_headers.get(key).add(val); + } + } + + public void removeRequestHeader(String key) { + + _request_headers.remove(_capitalizeHeaderKey(key)); + + } + + public void removeRequestHeader(String key, String value) { + + key = _capitalizeHeaderKey(key); + + _request_headers.get(key).remove(value); + + if(_request_headers.get(key).isEmpty()) { + + _request_headers.remove(key); + } + + } + + public URL getUrl() { + return _url; + } + + public void setUrl(URL url) { + + _url = url; + + try { + close(); + } catch (IOException ex) { + Logger.getLogger(KissHttpURLConnection.class.getName()).log(Level.SEVERE, null, ex); + } + } + + public int getStatus_code() { + + if(_status_code == -1) { + + try { + _parseStatusCode(); + } catch (IOException ex) { + Logger.getLogger(KissHttpURLConnection.class.getName()).log(Level.SEVERE, null, ex); + } + } + + return _status_code; + } + + public void close() throws IOException { + + if(_socket != null && !_socket.isClosed()) { + + _socket.close(); + } + + _socket = null; + } + + private void _parseStatusCode() throws IOException { + + int b; + + ByteArrayOutputStream byte_res = new ByteArrayOutputStream(); + + do{ + + b = _socket.getInputStream().read(); + + if(b != -1) { + byte_res.write(b); + } + + }while(b!=-1 && b != 0x0A); + + String temp = new String(byte_res.toByteArray()).trim(); + + Pattern header_pattern = Pattern.compile("HTTP*/*[0-9]+\\.[0-9]+ +([0-9]+)"); + + Matcher matcher = header_pattern.matcher(temp); + + if(matcher.find()) { + + _status_code = Integer.parseInt(matcher.group(1)); + + } else { + + throw new IOException("BAD HTTP STATUS CODE!"); + } + + } + + private void _parseResponseHeaders() { + + try { + + String temp; + + Pattern header_pattern = Pattern.compile("([^:]+):(.*)"); + + do { + + int b; + + ByteArrayOutputStream byte_res = new ByteArrayOutputStream(); + + do{ + + b = _socket.getInputStream().read(); + + byte_res.write(b); + + }while(b != -1 && b != 0x0A); + + temp = new String(byte_res.toByteArray()).trim(); + + if(temp.length() > 0) { + + Matcher matcher = header_pattern.matcher(temp); + + if(matcher.find()) { + + String key = _capitalizeHeaderKey(matcher.group(1).trim()); + + if(!_response_headers.containsKey(key)) { + + List list; + + _response_headers.put(key, (list = new ArrayList<>())); + + list.add(matcher.group(2).trim()); + + } else { + + List list = _response_headers.get(key); + + list.add(matcher.group(2).trim()); + } + } + } + + }while(temp.length() > 0); + + } catch (IOException ex) { + Logger.getLogger(KissHttpURLConnection.class.getName()).log(Level.SEVERE, null, ex); + } + + } + + public void doGET() { + + try { + _initSocket(); + + _status_code = -1; + + if(!_response_headers.isEmpty() && !_cookies_put) { + + _cookie_manager.put(_url.toURI(), _response_headers); + _cookies_put = true; + } + + _response_headers.clear(); + + ByteArrayOutputStream byte_req = new ByteArrayOutputStream(); + + byte_req.write(("GET " + (_url.getFile().isEmpty()?"/":_url.getFile()) + " HTTP/1.1\r\n").getBytes()); + + byte_req.write(("Host: "+_url.getHost()+"\r\n").getBytes()); + + byte_req.write(("Connection: close\r\n").getBytes()); + + for(Map.Entry pair : _request_headers.entrySet()) { + + for(Object s:(List)pair.getValue()) { + + byte_req.write((pair.getKey()+":"+(String)s+"\r\n").getBytes()); + } + } + + for(List list : _cookie_manager.get(_url.toURI(), _request_headers).values()) { + + for(Object s:list) { + + byte_req.write(("Cookie: "+(String)s + "\r\n").getBytes()); + } + } + + byte_req.write("\r\n".getBytes()); + + _socket.getOutputStream().write(byte_req.toByteArray()); + + _cookie_manager.put(_url.toURI(), _response_headers); + + _cookies_put = true; + + } catch (IOException | URISyntaxException ex) { + + Logger.getLogger(KissHttpURLConnection.class.getName()).log(Level.SEVERE, null, ex); + + } + } + + + public void doPOST(long length) { + + try { + + _initSocket(); + + _status_code = -1; + + if(!_response_headers.isEmpty() && !_cookies_put) { + + _cookie_manager.put(_url.toURI(),_response_headers); + + _cookies_put = true; + } + + _response_headers.clear(); + + ByteArrayOutputStream byte_req = new ByteArrayOutputStream(); + + byte_req.write(("POST " + (_url.getFile().isEmpty()?"/":_url.getFile()) + " HTTP/1.1\r\n").getBytes()); + + byte_req.write(("Host: "+_url.getHost()+"\r\n").getBytes()); + + byte_req.write(("Connection: close\r\n").getBytes()); + + byte_req.write(("Content-Length: " + length + "\r\n").getBytes()); + + for(Map.Entry pair : _request_headers.entrySet()) { + + for(Object s:(List)pair.getValue()) { + + byte_req.write((pair.getKey()+":"+(String)s+"\r\n").getBytes()); + } + } + + for(List list : _cookie_manager.get(_url.toURI(), _request_headers).values()) { + + for(Object s:list) { + + byte_req.write(("Cookie: "+(String)s + "\r\n").getBytes()); + } + } + + byte_req.write("\r\n".getBytes()); + + _socket.getOutputStream().write(byte_req.toByteArray()); + + _cookies_put = false; + + } catch (IOException | URISyntaxException ex) { + + Logger.getLogger(KissHttpURLConnection.class.getName()).log(Level.SEVERE, null, ex); + + } + } + + private void _initSocket() { + + if(_socket == null){ + + if(_url.getProtocol().equals("http")) { + + try { + + _socket = new Socket(_url.getHost(), _url.getPort()!=-1?_url.getPort():_url.getDefaultPort()); + + } catch (UnknownHostException ex) { + Logger.getLogger(KissHttpURLConnection.class.getName()).log(Level.SEVERE, null, ex); + } catch (IOException ex) { + Logger.getLogger(KissHttpURLConnection.class.getName()).log(Level.SEVERE, null, ex); + } + + } else if(_url.getProtocol().equals("https")) { + + try { + + SSLSocketFactory factory = (SSLSocketFactory)SSLSocketFactory.getDefault(); + + _socket = (SSLSocket)factory.createSocket(_url.getHost(), _url.getPort()!=-1?_url.getPort():_url.getDefaultPort()); + + ((SSLSocket)_socket).setUseClientMode(true); + + ((SSLSocket)_socket).startHandshake(); + + } catch (IOException ex) { + Logger.getLogger(KissHttpURLConnection.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + } + + private String _capitalizeHeaderKey(String value) { + + StringBuilder builder = new StringBuilder(value); + + for(int i=0; i0?cl:"-1",16); + + if(chunk_length > 0) { + + for(long j=0; j data = (HashMap)pair.getValue(); @@ -341,7 +342,7 @@ public final class SettingsDialog extends javax.swing.JDialog { swingReflectionInvoke("setVisible", unlock_accounts_button, false); - for (HashMap.Entry pair : _main_panel.getMega_accounts().entrySet()) { + for (Map.Entry pair : _main_panel.getMega_accounts().entrySet()) { HashMap data = (HashMap)pair.getValue(); @@ -1115,7 +1116,7 @@ public final class SettingsDialog extends javax.swing.JDialog { insertSettingValueInDB("mega_master_pass_hash", _main_panel.getMega_master_pass_hash()); - for (HashMap.Entry pair : _main_panel.getMega_accounts().entrySet()) { + for (Map.Entry pair : _main_panel.getMega_accounts().entrySet()) { HashMap data = (HashMap)pair.getValue(); @@ -1212,7 +1213,7 @@ public final class SettingsDialog extends javax.swing.JDialog { swingReflectionInvoke("setEnabled", delete_all_accounts_button, true); - for (HashMap.Entry pair : _main_panel.getMega_accounts().entrySet()) { + for (Map.Entry pair : _main_panel.getMega_accounts().entrySet()) { HashMap data = (HashMap)pair.getValue(); @@ -1276,7 +1277,7 @@ public final class SettingsDialog extends javax.swing.JDialog { mega_accounts_table.setModel(new_model); - for (HashMap.Entry pair : _main_panel.getMega_accounts().entrySet()) { + for (Map.Entry pair : _main_panel.getMega_accounts().entrySet()) { try { DBTools.deleteMegaAccount((String) pair.getKey());