├── LICENSE.txt ├── README.textile ├── pom.xml └── src ├── main └── java │ └── org │ └── kamranzafar │ └── jddl │ ├── Authentication.java │ ├── DirectDownloader.java │ ├── DownloadAdaptor.java │ ├── DownloadListener.java │ ├── DownloadTask.java │ ├── HttpConnector.java │ └── util │ └── Base64.java └── test └── java └── org └── kamranzafar └── jddl ├── AndroidExample.java ├── SimpleTest.java └── SwingTest.java /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. Overview 2 | 3 | jddl (Java Direct Downloader) is a simple and lightweight java library, which makes it very easy and trivial to direct-download multiple files over HTTP and to track the download progress. Now supports SSL and basic authentication. 4 | 5 | Note: Support for FTP downloads will be added in future releases 6 | 7 | h1. Usage 8 | 9 | jddl is a multi-threaded API capable of parallel-downloading multiple files and uses a custom event handler to update the download status. Below is a basic example. 10 | 11 |
                                                                           
12 | 
13 |  // Create a DirectDownloader instance
14 |  DirectDownloader dd = new DirectDownloader();
15 | 
16 |  String file = "http://someserver/somefile.zip"
17 |  String out = "somefile.zip"
18 | 
19 |  // Add files to be downloaded
20 |  dd.download( new DownloadTask( new URL( file ), new FileOutputStream( out ), new DownloadListener() {
21 |    String fname;
22 |  
23 |    public void onUpdate(int bytes, int totalDownloaded) {
24 |       // update progress bar etc.
25 |    }
26 | 
27 |    public void onStart(String fname, int size) {
28 |       this.fname = fname;
29 |       System.out.println( "Downloading " + fname + " of size " + size );
30 |    }
31 | 
32 |    public void onComplete() {
33 |       System.out.println( fname + " downloaded" );
34 |    }
35 | 
36 |    public void onCancel() {
37 |       System.out.println( fname + " cancelled" );
38 |    }
39 |  } ) );
40 | 
41 |  // Start downloading
42 |  Thread t = new Thread( dd );
43 |  t.start();
44 |  t.join();
45 | 
46 | 
47 | 48 | h1. Examples 49 | 50 | Below are some comprehensive examples of using jddl. 51 | 52 | * "Console example":https://github.com/kamranzafar/jddl/blob/master/src/test/java/org/kamranzafar/jddl/SimpleTest.java 53 | * "Swing example":https://github.com/kamranzafar/jddl/blob/master/src/test/java/org/kamranzafar/jddl/SwingTest.java 54 | * "Android example":https://github.com/kamranzafar/jddl/blob/master/src/test/java/org/kamranzafar/jddl/AndroidExample.java 55 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | org.kamranzafar.jddl 5 | jddl 6 | 0.7.1 7 | Java http file download library 8 | 9 | 10 | 11 | kamran 12 | Kamran Zafar 13 | xeus.man@gmail.com 14 | Kamran Zafar 15 | 16 | developer 17 | 18 | 19 | 20 | 21 | 22 | 23 | Apache Software License 2.0 24 | LICENSE.txt 25 | 26 | 27 | 28 | 29 | ${basedir}/src/main/java 30 | ${basedir}/src/test/java 31 | 32 | 33 | maven-compiler-plugin 34 | 35 | 1.5 36 | 1.5 37 | 38 | 39 | 40 | org.apache.maven.plugins 41 | maven-surefire-plugin 42 | 2.10 43 | 44 | true 45 | 46 | 47 | 48 | 49 | 50 | 51 | junit 52 | junit 53 | 4.10 54 | test 55 | 56 | 57 | com.google.android 58 | android 59 | 2.3.3 60 | test 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/main/java/org/kamranzafar/jddl/Authentication.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012 Kamran Zafar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.kamranzafar.jddl; 19 | 20 | public class Authentication { 21 | public static enum AuthType { 22 | BASIC 23 | } 24 | 25 | private AuthType authType = AuthType.BASIC; 26 | private String username; 27 | private String password; 28 | 29 | public Authentication() { 30 | } 31 | 32 | public Authentication(String username, String password) { 33 | super(); 34 | this.username = username; 35 | this.password = password; 36 | } 37 | 38 | public AuthType getAuthType() { 39 | return authType; 40 | } 41 | 42 | public void setAuthType(AuthType authType) { 43 | this.authType = authType; 44 | } 45 | 46 | public String getUsername() { 47 | return username; 48 | } 49 | 50 | public void setUsername(String username) { 51 | this.username = username; 52 | } 53 | 54 | public String getPassword() { 55 | return password; 56 | } 57 | 58 | public void setPassword(String password) { 59 | this.password = password; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/kamranzafar/jddl/DirectDownloader.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012 Kamran Zafar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.kamranzafar.jddl; 19 | 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.io.OutputStream; 23 | import java.net.HttpURLConnection; 24 | import java.net.Proxy; 25 | import java.security.KeyManagementException; 26 | import java.security.NoSuchAlgorithmException; 27 | import java.util.List; 28 | import java.util.concurrent.BlockingQueue; 29 | import java.util.concurrent.LinkedBlockingQueue; 30 | import java.util.logging.Logger; 31 | 32 | import org.kamranzafar.jddl.util.Base64; 33 | 34 | /** 35 | * @author kamran 36 | * 37 | */ 38 | public class DirectDownloader extends HttpConnector implements Runnable { 39 | private int poolSize = 3; 40 | private int bufferSize = 2048; 41 | 42 | private DirectDownloadThread[] dts; 43 | private Proxy proxy; 44 | private final BlockingQueue tasks = new LinkedBlockingQueue(); 45 | 46 | private static Logger logger = Logger.getLogger(DirectDownloader.class.getName()); 47 | 48 | public DirectDownloader() { 49 | } 50 | 51 | public DirectDownloader(int poolSize) { 52 | this.poolSize = poolSize; 53 | } 54 | 55 | public DirectDownloader(Proxy proxy) { 56 | this.proxy = proxy; 57 | } 58 | 59 | public DirectDownloader(Proxy proxy, int poolSize) { 60 | this.poolSize = poolSize; 61 | this.proxy = proxy; 62 | } 63 | 64 | protected class DirectDownloadThread extends Thread { 65 | private static final String CD_FNAME = "fname="; 66 | private static final String CONTENT_DISPOSITION = "Content-Disposition"; 67 | 68 | private boolean cancel = false; 69 | private boolean stop = false; 70 | 71 | private final BlockingQueue tasks; 72 | 73 | public DirectDownloadThread(BlockingQueue tasks) { 74 | this.tasks = tasks; 75 | } 76 | 77 | protected void download(DownloadTask dt) throws IOException, InterruptedException, KeyManagementException, 78 | NoSuchAlgorithmException { 79 | HttpURLConnection conn = (HttpURLConnection) getConnection(dt.getUrl(), proxy); 80 | 81 | if (dt.getAuthentication() != null) { 82 | Authentication auth = dt.getAuthentication(); 83 | String authString = auth.getUsername() + ":" + auth.getPassword(); 84 | 85 | conn.setRequestProperty("Authorization", "Basic " + Base64.encodeBytes(authString.getBytes())); 86 | } 87 | 88 | conn.setReadTimeout(dt.getTimeout()); 89 | conn.setDoOutput(true); 90 | conn.connect(); 91 | 92 | int fsize = conn.getContentLength(); 93 | String fname; 94 | 95 | String cd = conn.getHeaderField(CONTENT_DISPOSITION); 96 | 97 | if (cd != null) { 98 | fname = cd.substring(cd.indexOf(CD_FNAME) + 1, cd.length() - 1); 99 | } else { 100 | String url = dt.getUrl().toString(); 101 | fname = url.substring(url.lastIndexOf('/') + 1); 102 | } 103 | 104 | InputStream is = conn.getInputStream(); 105 | 106 | OutputStream os = dt.getOutputStream(); 107 | List listeners = dt.getListeners(); 108 | 109 | byte[] buff = new byte[bufferSize]; 110 | int res; 111 | 112 | for (DownloadListener listener : listeners) { 113 | listener.onStart(fname, fsize); 114 | } 115 | 116 | int total = 0; 117 | while ((res = is.read(buff)) != -1) { 118 | os.write(buff, 0, res); 119 | total += res; 120 | for (DownloadListener listener : listeners) { 121 | listener.onUpdate(res, total); 122 | } 123 | 124 | synchronized (dt) { 125 | // cancel download 126 | if (cancel || dt.isCancelled()) { 127 | close(is, os); 128 | for (DownloadListener listener : listeners) { 129 | listener.onCancel(); 130 | } 131 | 132 | throw new RuntimeException("Cancelled download"); 133 | } 134 | 135 | // stop thread 136 | if (stop) { 137 | close(is, os); 138 | for (DownloadListener listener : listeners) { 139 | listener.onCancel(); 140 | } 141 | 142 | throw new InterruptedException("Shutdown"); 143 | } 144 | 145 | // pause thread 146 | while (dt.isPaused()) { 147 | try { 148 | wait(); 149 | } catch (Exception e) { 150 | } 151 | } 152 | } 153 | } 154 | 155 | for (DownloadListener listener : listeners) { 156 | listener.onComplete(); 157 | } 158 | 159 | close(is, os); 160 | } 161 | 162 | private void close(InputStream is, OutputStream os) { 163 | try { 164 | is.close(); 165 | os.close(); 166 | } catch (IOException e) { 167 | } 168 | } 169 | 170 | @Override 171 | public void run() { 172 | while (true) { 173 | try { 174 | download(tasks.take()); 175 | } catch (InterruptedException e) { 176 | logger.info("Stopping download thread"); 177 | break; 178 | } catch (Exception e) { 179 | e.printStackTrace(); 180 | } 181 | } 182 | } 183 | 184 | public void cancel() { 185 | cancel = true; 186 | } 187 | 188 | public void shutdown() { 189 | stop = true; 190 | } 191 | } 192 | 193 | public void download(DownloadTask dt) { 194 | tasks.add(dt); 195 | } 196 | 197 | public void run() { 198 | logger.info("Initializing downloader..."); 199 | 200 | dts = new DirectDownloadThread[poolSize]; 201 | 202 | for (int i = 0; i < dts.length; i++) { 203 | dts[i] = new DirectDownloadThread(tasks); 204 | dts[i].start(); 205 | } 206 | 207 | logger.info("Downloader started, waiting for tasks."); 208 | } 209 | 210 | public void shutdown() { 211 | for (int i = 0; i < dts.length; i++) { 212 | if (dts[i] != null) { 213 | dts[i].shutdown(); 214 | } 215 | } 216 | } 217 | 218 | public void cancelAll() { 219 | for (int i = 0; i < dts.length; i++) { 220 | if (dts[i] != null) { 221 | dts[i].cancel(); 222 | } 223 | } 224 | } 225 | 226 | public int getPoolSize() { 227 | return poolSize; 228 | } 229 | 230 | public void setPoolSize(int poolSize) { 231 | this.poolSize = poolSize; 232 | } 233 | 234 | public int getBufferSize() { 235 | return bufferSize; 236 | } 237 | 238 | public void setBufferSize(int bufferSize) { 239 | this.bufferSize = bufferSize; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/main/java/org/kamranzafar/jddl/DownloadAdaptor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012 Kamran Zafar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | package org.kamranzafar.jddl; 18 | 19 | /** 20 | * @author Kamran 21 | * 22 | */ 23 | public class DownloadAdaptor implements DownloadListener { 24 | 25 | public void onStart(String fname, int fsize) { 26 | } 27 | 28 | public void onUpdate(int bytes, int totalDownloaded) { 29 | } 30 | 31 | public void onComplete() { 32 | } 33 | 34 | public void onCancel() { 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/kamranzafar/jddl/DownloadListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012 Kamran Zafar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.kamranzafar.jddl; 19 | 20 | /** 21 | * @author kamran 22 | * 23 | */ 24 | public interface DownloadListener { 25 | public void onStart(String fname, int fsize); 26 | 27 | public void onUpdate(int bytes, int totalDownloaded); 28 | 29 | public void onComplete(); 30 | 31 | public void onCancel(); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/kamranzafar/jddl/DownloadTask.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012 Kamran Zafar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.kamranzafar.jddl; 19 | 20 | import java.io.OutputStream; 21 | import java.net.URL; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | /** 26 | * @author kamran 27 | * 28 | */ 29 | public class DownloadTask { 30 | private URL url; 31 | private OutputStream outputStream; 32 | private final List listeners = new ArrayList(); 33 | 34 | private boolean paused = false; 35 | private boolean cancelled = false; 36 | private int timeout = 15000; 37 | 38 | private Authentication authentication; 39 | 40 | public DownloadTask(URL url, OutputStream outputStream) { 41 | this.url = url; 42 | this.outputStream = outputStream; 43 | } 44 | 45 | public DownloadTask(URL url, OutputStream outputStream, DownloadListener listener) { 46 | this.url = url; 47 | this.outputStream = outputStream; 48 | listeners.add(listener); 49 | } 50 | 51 | public URL getUrl() { 52 | return url; 53 | } 54 | 55 | public DownloadTask setUrl(URL url) { 56 | this.url = url; 57 | return this; 58 | } 59 | 60 | public OutputStream getOutputStream() { 61 | return outputStream; 62 | } 63 | 64 | public DownloadTask setOutputStream(OutputStream outputStream) { 65 | this.outputStream = outputStream; 66 | return this; 67 | } 68 | 69 | public List getListeners() { 70 | return listeners; 71 | } 72 | 73 | public DownloadTask addListener(DownloadListener listener) { 74 | listeners.add(listener); 75 | return this; 76 | } 77 | 78 | public DownloadTask removeListener(DownloadListener listener) { 79 | listeners.remove(listener); 80 | return this; 81 | } 82 | 83 | public DownloadTask removeAllListener() { 84 | listeners.clear(); 85 | return this; 86 | } 87 | 88 | public boolean isPaused() { 89 | return paused; 90 | } 91 | 92 | public DownloadTask setPaused(boolean paused) { 93 | this.paused = paused; 94 | return this; 95 | } 96 | 97 | public boolean isCancelled() { 98 | return cancelled; 99 | } 100 | 101 | public DownloadTask setCancelled(boolean cancelled) { 102 | this.cancelled = cancelled; 103 | return this; 104 | } 105 | 106 | public int getTimeout() { 107 | return timeout; 108 | } 109 | 110 | public DownloadTask setTimeout(int timeout) { 111 | this.timeout = timeout; 112 | return this; 113 | } 114 | 115 | public Authentication getAuthentication() { 116 | return authentication; 117 | } 118 | 119 | public DownloadTask setAuthentication(Authentication authentication) { 120 | this.authentication = authentication; 121 | return this; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/org/kamranzafar/jddl/HttpConnector.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012 Kamran Zafar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.kamranzafar.jddl; 19 | 20 | import java.io.BufferedReader; 21 | import java.io.BufferedWriter; 22 | import java.io.IOException; 23 | import java.io.InputStreamReader; 24 | import java.io.OutputStreamWriter; 25 | import java.net.HttpURLConnection; 26 | import java.net.Proxy; 27 | import java.net.URL; 28 | import java.net.URLConnection; 29 | import java.security.KeyManagementException; 30 | import java.security.NoSuchAlgorithmException; 31 | import java.security.SecureRandom; 32 | import java.security.cert.CertificateException; 33 | import java.security.cert.X509Certificate; 34 | import java.util.HashMap; 35 | import java.util.Iterator; 36 | import java.util.Map; 37 | 38 | import javax.net.ssl.HostnameVerifier; 39 | import javax.net.ssl.HttpsURLConnection; 40 | import javax.net.ssl.KeyManager; 41 | import javax.net.ssl.SSLContext; 42 | import javax.net.ssl.SSLSession; 43 | import javax.net.ssl.TrustManager; 44 | import javax.net.ssl.X509TrustManager; 45 | 46 | /** 47 | * @author Kamran 48 | * 49 | */ 50 | public class HttpConnector { 51 | 52 | protected int BUFFER_SIZE = 2048; 53 | protected int DEFAULT_STREAM_BUFFER_SIZE = 3072; 54 | protected int DEFAULT_CONNECT_TIMEOUT = 13000; 55 | 56 | private int connectionTimeout = DEFAULT_CONNECT_TIMEOUT; 57 | 58 | private final Map headers = new HashMap(); 59 | private String requestMethod = "GET"; 60 | 61 | public HttpConnector() { 62 | HttpURLConnection.setFollowRedirects(true); 63 | } 64 | 65 | /** 66 | * @param url 67 | * @return 68 | * @throws IOException 69 | * @throws NoSuchAlgorithmException 70 | * @throws KeyManagementException 71 | */ 72 | protected URLConnection getConnection(URL url) throws IOException, KeyManagementException, NoSuchAlgorithmException { 73 | if ("http".equalsIgnoreCase(url.getProtocol()) || "ftp".equalsIgnoreCase(url.getProtocol())) { 74 | return getConnection(url, null); 75 | } else if ("https".equalsIgnoreCase(url.getProtocol())) { 76 | return getSecureConnection(url); 77 | } 78 | 79 | return null; 80 | } 81 | 82 | /** 83 | * @param url 84 | * @param proxy 85 | * @return 86 | * @throws IOException 87 | * @throws NoSuchAlgorithmException 88 | * @throws KeyManagementException 89 | */ 90 | protected URLConnection getConnection(URL url, Proxy proxy) throws IOException, KeyManagementException, 91 | NoSuchAlgorithmException { 92 | if ("http".equalsIgnoreCase(url.getProtocol()) || "ftp".equalsIgnoreCase(url.getProtocol())) { 93 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy == null ? Proxy.NO_PROXY : proxy); 94 | conn.setRequestMethod(requestMethod); 95 | setHeaders(conn); 96 | return conn; 97 | } else if ("https".equalsIgnoreCase(url.getProtocol())) { 98 | return getSecureConnection(url, proxy); 99 | } 100 | 101 | return null; 102 | } 103 | 104 | /** 105 | * @param url 106 | * @return 107 | * @throws IOException 108 | * @throws KeyManagementException 109 | * @throws NoSuchAlgorithmException 110 | */ 111 | protected HttpsURLConnection getSecureConnection(URL url) throws IOException, KeyManagementException, 112 | NoSuchAlgorithmException { 113 | return getSecureConnection(url, null); 114 | } 115 | 116 | /** 117 | * @param url 118 | * @param proxy 119 | * @return 120 | * @throws IOException 121 | * @throws NoSuchAlgorithmException 122 | * @throws KeyManagementException 123 | */ 124 | protected HttpsURLConnection getSecureConnection(URL url, Proxy proxy) throws IOException, 125 | NoSuchAlgorithmException, KeyManagementException { 126 | 127 | SSLContext context = SSLContext.getInstance("TLS"); 128 | context.init(new KeyManager[0], new TrustManager[] { new DefaultTrustManager() }, new SecureRandom()); 129 | 130 | HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(proxy == null ? Proxy.NO_PROXY : proxy); 131 | conn.setRequestMethod(requestMethod); 132 | setHeaders(conn); 133 | 134 | conn.setSSLSocketFactory(context.getSocketFactory()); 135 | conn.setHostnameVerifier(new HostnameVerifier() { 136 | public boolean verify(String hostname, SSLSession session) { 137 | return true; 138 | } 139 | }); 140 | 141 | conn.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT); 142 | return conn; 143 | } 144 | 145 | /** 146 | * @param conn 147 | * @param data 148 | * @throws IOException 149 | */ 150 | protected void doOutput(URLConnection conn, String data) throws IOException { 151 | BufferedWriter wr = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()), 152 | DEFAULT_STREAM_BUFFER_SIZE); 153 | 154 | wr.write(data); 155 | wr.flush(); 156 | wr.close(); 157 | } 158 | 159 | /** 160 | * @param conn 161 | * @return 162 | * @throws IOException 163 | */ 164 | protected StringBuffer doInput(URLConnection conn) throws IOException { 165 | BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()), DEFAULT_STREAM_BUFFER_SIZE); 166 | 167 | StringBuffer buff = new StringBuffer(); 168 | 169 | char[] bb = new char[BUFFER_SIZE]; 170 | int nob; 171 | 172 | while ((nob = rd.read(bb)) != -1) { 173 | buff.append(new String(bb, 0, nob)); 174 | } 175 | 176 | rd.close(); 177 | 178 | return buff; 179 | } 180 | 181 | protected final static class DefaultTrustManager implements X509TrustManager { 182 | 183 | public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 184 | } 185 | 186 | public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 187 | } 188 | 189 | public X509Certificate[] getAcceptedIssuers() { 190 | return null; 191 | } 192 | } 193 | 194 | private void setHeaders(URLConnection uc) { 195 | Iterator itr = headers.keySet().iterator(); 196 | while (itr.hasNext()) { 197 | String key = itr.next(); 198 | uc.addRequestProperty(key, headers.get(key)); 199 | } 200 | } 201 | 202 | public int getConnectionTimeout() { 203 | return connectionTimeout; 204 | } 205 | 206 | public void setConnectionTimeout(int connectionTimeout) { 207 | this.connectionTimeout = connectionTimeout; 208 | } 209 | 210 | public void addHeader(String key, String value) { 211 | headers.put(key, value); 212 | } 213 | 214 | public Map getHeaders() { 215 | return headers; 216 | } 217 | 218 | public String getRequestMethod() { 219 | return requestMethod; 220 | } 221 | 222 | public void setRequestMethod(String requestMethod) { 223 | this.requestMethod = requestMethod; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/main/java/org/kamranzafar/jddl/util/Base64.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012 Kamran Zafar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.kamranzafar.jddl.util; 19 | 20 | /** 21 | *

22 | * Encodes and decodes to and from Base64 notation. 23 | *

24 | *

25 | * Homepage: http://iharder.net/base64. 26 | *

27 | * 28 | *

29 | * Example: 30 | *

31 | * 32 | * String encoded = Base64.encode( myByteArray );
33 | * byte[] myByteArray = Base64.decode( encoded ); 34 | * 35 | *

36 | * The options parameter, which appears in a few places, is used to 37 | * pass several pieces of information to the encoder. In the "higher level" 38 | * methods such as encodeBytes( bytes, options ) the options parameter can be 39 | * used to indicate such things as first gzipping the bytes before encoding 40 | * them, not inserting linefeeds, and encoding using the URL-safe and Ordered 41 | * dialects. 42 | *

43 | * 44 | *

45 | * Note, according to RFC3548, Section 2.1, 47 | * implementations should not add line feeds unless explicitly told to do so. 48 | * I've got Base64 set to this behavior now, although earlier versions broke 49 | * lines by default. 50 | *

51 | * 52 | *

53 | * The constants defined in Base64 can be OR-ed together to combine options, so 54 | * you might make a call like this: 55 | *

56 | * 57 | * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES ); 58 | *

59 | * to compress the data before encoding it and then making the output have 60 | * newline characters. 61 | *

62 | *

63 | * Also... 64 | *

65 | * String encoded = Base64.encodeBytes( crazyString.getBytes() ); 66 | * 67 | * 68 | * 69 | *

70 | * Change Log: 71 | *

72 | *
    73 | *
  • v2.3.7 - Fixed subtle bug when base 64 input stream contained the value 74 | * 01111111, which is an invalid base 64 character but should not throw an 75 | * ArrayIndexOutOfBoundsException either. Led to discovery of mishandling (or 76 | * potential for better handling) of other bad input characters. You should now 77 | * get an IOException if you try decoding something that has bad characters in 78 | * it.
  • 79 | *
  • v2.3.6 - Fixed bug when breaking lines and the final byte of the encoded 80 | * string ended in the last column; the buffer was not properly shrunk and 81 | * contained an extra (null) byte that made it into the string.
  • 82 | *
  • v2.3.5 - Fixed bug in {@link #encodeFromFile} where estimated buffer size 83 | * was wrong for files of size 31, 34, and 37 bytes.
  • 84 | *
  • v2.3.4 - Fixed bug when working with gzipped streams whereby flushing the 85 | * Base64.OutputStream closed the Base64 encoding (by padding with equals signs) 86 | * too soon. Also added an option to suppress the automatic decoding of gzipped 87 | * streams. Also added experimental support for specifying a class loader when 88 | * using the 89 | * {@link #decodeToObject(java.lang.String, int, java.lang.ClassLoader)} method. 90 | *
  • 91 | *
  • v2.3.3 - Changed default char encoding to US-ASCII which reduces the 92 | * internal Java footprint with its CharEncoders and so forth. Fixed some 93 | * javadocs that were inconsistent. Removed imports and specified things like 94 | * java.io.IOException explicitly inline.
  • 95 | *
  • v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how 96 | * big the final encoded data will be so that the code doesn't have to create 97 | * two output arrays: an oversized initial one and then a final, exact-sized 98 | * one. Big win when using the {@link #encodeBytesToBytes(byte[])} family of 99 | * methods (and not using the gzip options which uses a different mechanism with 100 | * streams and stuff).
  • 101 | *
  • v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and 102 | * some similar helper methods to be more efficient with memory by not returning 103 | * a String but just a byte array.
  • 104 | *
  • v2.3 - This is not a drop-in replacement! This is two 105 | * years of comments and bug fixes queued up and finally executed. Thanks to 106 | * everyone who sent me stuff, and I'm sorry I wasn't able to distribute your 107 | * fixes to everyone else. Much bad coding was cleaned up including throwing 108 | * exceptions where necessary instead of returning null values or something 109 | * similar. Here are some changes that may affect you: 110 | *
      111 | *
    • Does not break lines, by default. This is to keep in compliance 112 | * with RFC3548.
    • 113 | *
    • Throws exceptions instead of returning null values. Because some 114 | * operations (especially those that may permit the GZIP option) use IO streams, 115 | * there is a possiblity of an java.io.IOException being thrown. After some 116 | * discussion and thought, I've changed the behavior of the methods to throw 117 | * java.io.IOExceptions rather than return null if ever there's an error. I 118 | * think this is more appropriate, though it will require some changes to your 119 | * code. Sorry, it should have been done this way to begin with.
    • 120 | *
    • Removed all references to System.out, System.err, and the like. 121 | * Shame on me. All I can say is sorry they were ever there.
    • 122 | *
    • Throws NullPointerExceptions and IllegalArgumentExceptions as 123 | * needed such as when passed arrays are null or offsets are invalid.
    • 124 | *
    • Cleaned up as much javadoc as I could to avoid any javadoc warnings. This 125 | * was especially annoying before for people who were thorough in their own 126 | * projects and then had gobs of javadoc warnings on this file.
    • 127 | *
    128 | *
  • v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug when 129 | * using very small files (~< 40 bytes).
  • 130 | *
  • v2.2 - Added some helper methods for encoding/decoding directly from one 131 | * file to the next. Also added a main() method to support command line 132 | * encoding/decoding from one file to the next. Also added these Base64 133 | * dialects: 134 | *
      135 | *
    1. The default is RFC3548 format.
    2. 136 | *
    3. Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates 137 | * URL and file name friendly format as described in Section 4 of RFC3548. 138 | * http://www.faqs.org/rfcs/rfc3548.html
    4. 139 | *
    5. Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates 140 | * URL and file name friendly format that preserves lexical ordering as 141 | * described in http://www.faqs.org/qa/rfcc-1940.html
    6. 142 | *
    143 | * Special thanks to Jim Kellerman at http://www.powerset.com/ for contributing 145 | * the new Base64 dialects.
  • 146 | * 147 | *
  • v2.1 - Cleaned up javadoc comments and unused variables and methods. 148 | * Added some convenience methods for reading and writing to and from files.
  • 149 | *
  • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on 150 | * systems with other encodings (like EBCDIC).
  • 151 | *
  • v2.0.1 - Fixed an error when decoding a single byte, that is, when the 152 | * encoded data was a single byte.
  • 153 | *
  • v2.0 - I got rid of methods that used booleans to set options. Now 154 | * everything is more consolidated and cleaner. The code now detects when data 155 | * that's being decoded is gzip-compressed and will decompress it automatically. 156 | * Generally things are cleaner. You'll probably have to change some method 157 | * calls that you were making to support the new options format (ints 158 | * that you "OR" together).
  • 159 | *
  • v1.5.1 - Fixed bug when decompressing and decoding to a byte[] using 160 | * decode( String s, boolean gzipCompressed ). Added the ability to 161 | * "suspend" encoding in the Output Stream so you can turn on and off the 162 | * encoding if you need to embed base64 data in an otherwise "normal" stream 163 | * (like an XML file).
  • 164 | *
  • v1.5 - Output stream pases on flush() command but doesn't do anything 165 | * itself. This helps when using GZIP streams. Added the ability to 166 | * GZip-compress objects before encoding them.
  • 167 | *
  • v1.4 - Added helper methods to read/write files.
  • 168 | *
  • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
  • 169 | *
  • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input 170 | * stream where last buffer being read, if not completely full, was not 171 | * returned.
  • 172 | *
  • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the 173 | * wrong time.
  • 174 | *
  • v1.3.3 - Fixed I/O streams which were totally messed up.
  • 175 | *
176 | * 177 | *

178 | * I am placing this code in the Public Domain. Do with it as you will. This 179 | * software comes with no guarantees or warranties but with plenty of 180 | * well-wishing instead! Please visit http://iharder.net/base64 periodically 182 | * to check for updates or to contribute improvements. 183 | *

184 | * 185 | * @author Robert Harder 186 | * @author rob@iharder.net 187 | * @version 2.3.7 188 | */ 189 | public class Base64 { 190 | 191 | /* ******** P U B L I C F I E L D S ******** */ 192 | 193 | /** No options specified. Value is zero. */ 194 | public final static int NO_OPTIONS = 0; 195 | 196 | /** Specify encoding in first bit. Value is one. */ 197 | public final static int ENCODE = 1; 198 | 199 | /** Specify decoding in first bit. Value is zero. */ 200 | public final static int DECODE = 0; 201 | 202 | /** Specify that data should be gzip-compressed in second bit. Value is two. */ 203 | public final static int GZIP = 2; 204 | 205 | /** 206 | * Specify that gzipped data should not be automatically gunzipped. 207 | */ 208 | public final static int DONT_GUNZIP = 4; 209 | 210 | /** Do break lines when encoding. Value is 8. */ 211 | public final static int DO_BREAK_LINES = 8; 212 | 213 | /** 214 | * Encode using Base64-like encoding that is URL- and Filename-safe as 215 | * described in Section 4 of RFC3548: http://www.faqs.org/rfcs/rfc3548.html. It is important to note that 218 | * data encoded this way is not officially valid Base64, or at the 219 | * very least should not be called Base64 without also specifying that is 220 | * was encoded using the URL- and Filename-safe dialect. 221 | */ 222 | public final static int URL_SAFE = 16; 223 | 224 | /** 225 | * Encode using the special "ordered" dialect of Base64 described here: http://www.faqs.org/qa/rfcc- 227 | * 1940.html. 228 | */ 229 | public final static int ORDERED = 32; 230 | 231 | /* ******** P R I V A T E F I E L D S ******** */ 232 | 233 | /** Maximum line length (76) of Base64 output. */ 234 | private final static int MAX_LINE_LENGTH = 76; 235 | 236 | /** The equals sign (=) as a byte. */ 237 | private final static byte EQUALS_SIGN = (byte) '='; 238 | 239 | /** The new line character (\n) as a byte. */ 240 | private final static byte NEW_LINE = (byte) '\n'; 241 | 242 | /** Preferred encoding. */ 243 | private final static String PREFERRED_ENCODING = "US-ASCII"; 244 | 245 | private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in 246 | // encoding 247 | private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in 248 | // encoding 249 | 250 | /* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ 251 | 252 | /** The 64 valid Base64 values. */ 253 | /* 254 | * Host platform me be something funny like EBCDIC, so we hardcode these 255 | * values. 256 | */ 257 | private final static byte[] _STANDARD_ALPHABET = { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', 258 | (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', 259 | (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', 260 | (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', 261 | (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', 262 | (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', 263 | (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', 264 | (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/' }; 265 | 266 | /** 267 | * Translates a Base64 value to either its 6-bit reconstruction value or a 268 | * negative number indicating some other meaning. 269 | **/ 270 | private final static byte[] _STANDARD_DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 271 | // 0 272 | // - 273 | // 8 274 | -5, -5, // Whitespace: Tab and Linefeed 275 | -9, -9, // Decimal 11 - 12 276 | -5, // Whitespace: Carriage Return 277 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 278 | // 26 279 | -9, -9, -9, -9, -9, // Decimal 27 - 31 280 | -5, // Whitespace: Space 281 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 282 | 62, // Plus sign at decimal 43 283 | -9, -9, -9, // Decimal 44 - 46 284 | 63, // Slash at decimal 47 285 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine 286 | -9, -9, -9, // Decimal 58 - 60 287 | -1, // Equals sign at decimal 61 288 | -9, -9, -9, // Decimal 62 - 64 289 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 290 | // 'N' 291 | 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' 292 | // through 'Z' 293 | -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 294 | 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' 295 | // through 'm' 296 | 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' 297 | // through 'z' 298 | -9, -9, -9, -9, -9 // Decimal 123 - 127 299 | , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 300 | // 139 301 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 302 | // 152 303 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 304 | // 165 305 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 306 | // 178 307 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 308 | // 191 309 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 310 | // 204 311 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 312 | // 217 313 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 314 | // 230 315 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 316 | // 243 317 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 318 | }; 319 | 320 | /* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ 321 | 322 | /** 323 | * Used in the URL- and Filename-safe dialect described in Section 4 of 324 | * RFC3548: http://www.faqs.org 326 | * /rfcs/rfc3548.html. Notice that the last two bytes become "hyphen" 327 | * and "underscore" instead of "plus" and "slash." 328 | */ 329 | private final static byte[] _URL_SAFE_ALPHABET = { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', 330 | (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', 331 | (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', 332 | (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', 333 | (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', 334 | (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', 335 | (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', 336 | (byte) '7', (byte) '8', (byte) '9', (byte) '-', (byte) '_' }; 337 | 338 | /** 339 | * Used in decoding URL- and Filename-safe dialects of Base64. 340 | */ 341 | private final static byte[] _URL_SAFE_DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 342 | // 0 343 | // - 344 | // 8 345 | -5, -5, // Whitespace: Tab and Linefeed 346 | -9, -9, // Decimal 11 - 12 347 | -5, // Whitespace: Carriage Return 348 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 349 | // 26 350 | -9, -9, -9, -9, -9, // Decimal 27 - 31 351 | -5, // Whitespace: Space 352 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 353 | -9, // Plus sign at decimal 43 354 | -9, // Decimal 44 355 | 62, // Minus sign at decimal 45 356 | -9, // Decimal 46 357 | -9, // Slash at decimal 47 358 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine 359 | -9, -9, -9, // Decimal 58 - 60 360 | -1, // Equals sign at decimal 61 361 | -9, -9, -9, // Decimal 62 - 64 362 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 363 | // 'N' 364 | 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' 365 | // through 'Z' 366 | -9, -9, -9, -9, // Decimal 91 - 94 367 | 63, // Underscore at decimal 95 368 | -9, // Decimal 96 369 | 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' 370 | // through 'm' 371 | 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' 372 | // through 'z' 373 | -9, -9, -9, -9, -9 // Decimal 123 - 127 374 | , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 375 | // 139 376 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 377 | // 152 378 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 379 | // 165 380 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 381 | // 178 382 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 383 | // 191 384 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 385 | // 204 386 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 387 | // 217 388 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 389 | // 230 390 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 391 | // 243 392 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 393 | }; 394 | 395 | /* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ 396 | 397 | /** 398 | * I don't get the point of this technique, but someone requested it, and it 399 | * is described here: http:// 401 | * www.faqs.org/qa/rfcc-1940.html. 402 | */ 403 | private final static byte[] _ORDERED_ALPHABET = { (byte) '-', (byte) '0', (byte) '1', (byte) '2', (byte) '3', 404 | (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A', (byte) 'B', (byte) 'C', 405 | (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', 406 | (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', 407 | (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) '_', (byte) 'a', (byte) 'b', (byte) 'c', 408 | (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', 409 | (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', 410 | (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z' }; 411 | 412 | /** 413 | * Used in decoding the "ordered" dialect of Base64. 414 | */ 415 | private final static byte[] _ORDERED_DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 416 | // 0 417 | // - 418 | // 8 419 | -5, -5, // Whitespace: Tab and Linefeed 420 | -9, -9, // Decimal 11 - 12 421 | -5, // Whitespace: Carriage Return 422 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 423 | // 26 424 | -9, -9, -9, -9, -9, // Decimal 27 - 31 425 | -5, // Whitespace: Space 426 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 427 | -9, // Plus sign at decimal 43 428 | -9, // Decimal 44 429 | 0, // Minus sign at decimal 45 430 | -9, // Decimal 46 431 | -9, // Slash at decimal 47 432 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // Numbers zero through nine 433 | -9, -9, -9, // Decimal 58 - 60 434 | -1, // Equals sign at decimal 61 435 | -9, -9, -9, // Decimal 62 - 64 436 | 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, // Letters 'A' 437 | // through 'M' 438 | 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, // Letters 'N' 439 | // through 'Z' 440 | -9, -9, -9, -9, // Decimal 91 - 94 441 | 37, // Underscore at decimal 95 442 | -9, // Decimal 96 443 | 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, // Letters 'a' 444 | // through 'm' 445 | 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // Letters 'n' 446 | // through 'z' 447 | -9, -9, -9, -9, -9 // Decimal 123 - 127 448 | , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 449 | // - 139 450 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 451 | // 152 452 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 453 | // 165 454 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 455 | // 178 456 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 457 | // 191 458 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 459 | // 204 460 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 461 | // 217 462 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 463 | // 230 464 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 465 | // 243 466 | -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 467 | }; 468 | 469 | /* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ 470 | 471 | /** 472 | * Returns one of the _SOMETHING_ALPHABET byte arrays depending on the 473 | * options specified. It's possible, though silly, to specify ORDERED 474 | * and URLSAFE in which case one of them will be picked, though there 475 | * is no guarantee as to which one will be picked. 476 | */ 477 | private final static byte[] getAlphabet(int options) { 478 | if ((options & URL_SAFE) == URL_SAFE) { 479 | return _URL_SAFE_ALPHABET; 480 | } else if ((options & ORDERED) == ORDERED) { 481 | return _ORDERED_ALPHABET; 482 | } else { 483 | return _STANDARD_ALPHABET; 484 | } 485 | } // end getAlphabet 486 | 487 | /** 488 | * Returns one of the _SOMETHING_DECODABET byte arrays depending on the 489 | * options specified. It's possible, though silly, to specify ORDERED and 490 | * URL_SAFE in which case one of them will be picked, though there is no 491 | * guarantee as to which one will be picked. 492 | */ 493 | private final static byte[] getDecodabet(int options) { 494 | if ((options & URL_SAFE) == URL_SAFE) { 495 | return _URL_SAFE_DECODABET; 496 | } else if ((options & ORDERED) == ORDERED) { 497 | return _ORDERED_DECODABET; 498 | } else { 499 | return _STANDARD_DECODABET; 500 | } 501 | } // end getAlphabet 502 | 503 | /** Defeats instantiation. */ 504 | private Base64() { 505 | } 506 | 507 | /* ******** E N C O D I N G M E T H O D S ******** */ 508 | 509 | /** 510 | * Encodes up to the first three bytes of array threeBytes and 511 | * returns a four-byte array in Base64 notation. The actual number of 512 | * significant bytes in your array is given by numSigBytes. The 513 | * array threeBytes needs only be as big as 514 | * numSigBytes. Code can reuse a byte array by passing a 515 | * four-byte array as b4. 516 | * 517 | * @param b4 518 | * A reusable byte array to reduce array instantiation 519 | * @param threeBytes 520 | * the array to convert 521 | * @param numSigBytes 522 | * the number of significant bytes in your array 523 | * @return four byte array in Base64 notation. 524 | * @since 1.5.1 525 | */ 526 | private static byte[] encode3to4(byte[] b4, byte[] threeBytes, int numSigBytes, int options) { 527 | encode3to4(threeBytes, 0, numSigBytes, b4, 0, options); 528 | return b4; 529 | } // end encode3to4 530 | 531 | /** 532 | *

533 | * Encodes up to three bytes of the array source and writes the 534 | * resulting four Base64 bytes to destination. The source and 535 | * destination arrays can be manipulated anywhere along their length by 536 | * specifying srcOffset and destOffset. This method 537 | * does not check to make sure your arrays are large enough to accomodate 538 | * srcOffset + 3 for the source array or 539 | * destOffset + 4 for the destination array. The 540 | * actual number of significant bytes in your array is given by 541 | * numSigBytes. 542 | *

543 | *

544 | * This is the lowest level of the encoding methods with all possible 545 | * parameters. 546 | *

547 | * 548 | * @param source 549 | * the array to convert 550 | * @param srcOffset 551 | * the index where conversion begins 552 | * @param numSigBytes 553 | * the number of significant bytes in your array 554 | * @param destination 555 | * the array to hold the conversion 556 | * @param destOffset 557 | * the index where output will be put 558 | * @return the destination array 559 | * @since 1.3 560 | */ 561 | private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes, byte[] destination, int destOffset, 562 | int options) { 563 | 564 | byte[] ALPHABET = getAlphabet(options); 565 | 566 | // 1 2 3 567 | // 01234567890123456789012345678901 Bit position 568 | // --------000000001111111122222222 Array position from threeBytes 569 | // --------| || || || | Six bit groups to index ALPHABET 570 | // >>18 >>12 >> 6 >> 0 Right shift necessary 571 | // 0x3f 0x3f 0x3f Additional AND 572 | 573 | // Create buffer with zero-padding if there are only one or two 574 | // significant bytes passed in the array. 575 | // We have to shift left 24 in order to flush out the 1's that appear 576 | // when Java treats a value as negative that is cast from a byte to an 577 | // int. 578 | int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) 579 | | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) 580 | | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); 581 | 582 | switch (numSigBytes) { 583 | case 3: 584 | destination[destOffset] = ALPHABET[(inBuff >>> 18)]; 585 | destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; 586 | destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; 587 | destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; 588 | return destination; 589 | 590 | case 2: 591 | destination[destOffset] = ALPHABET[(inBuff >>> 18)]; 592 | destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; 593 | destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; 594 | destination[destOffset + 3] = EQUALS_SIGN; 595 | return destination; 596 | 597 | case 1: 598 | destination[destOffset] = ALPHABET[(inBuff >>> 18)]; 599 | destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; 600 | destination[destOffset + 2] = EQUALS_SIGN; 601 | destination[destOffset + 3] = EQUALS_SIGN; 602 | return destination; 603 | 604 | default: 605 | return destination; 606 | } // end switch 607 | } // end encode3to4 608 | 609 | /** 610 | * Performs Base64 encoding on the raw ByteBuffer, writing it 611 | * to the encoded ByteBuffer. This is an experimental feature. 612 | * Currently it does not pass along any options (such as 613 | * {@link #DO_BREAK_LINES} or {@link #GZIP}. 614 | * 615 | * @param raw 616 | * input buffer 617 | * @param encoded 618 | * output buffer 619 | * @since 2.3 620 | */ 621 | public static void encode(java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded) { 622 | byte[] raw3 = new byte[3]; 623 | byte[] enc4 = new byte[4]; 624 | 625 | while (raw.hasRemaining()) { 626 | int rem = Math.min(3, raw.remaining()); 627 | raw.get(raw3, 0, rem); 628 | Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS); 629 | encoded.put(enc4); 630 | } // end input remaining 631 | } 632 | 633 | /** 634 | * Performs Base64 encoding on the raw ByteBuffer, writing it 635 | * to the encoded CharBuffer. This is an experimental feature. 636 | * Currently it does not pass along any options (such as 637 | * {@link #DO_BREAK_LINES} or {@link #GZIP}. 638 | * 639 | * @param raw 640 | * input buffer 641 | * @param encoded 642 | * output buffer 643 | * @since 2.3 644 | */ 645 | public static void encode(java.nio.ByteBuffer raw, java.nio.CharBuffer encoded) { 646 | byte[] raw3 = new byte[3]; 647 | byte[] enc4 = new byte[4]; 648 | 649 | while (raw.hasRemaining()) { 650 | int rem = Math.min(3, raw.remaining()); 651 | raw.get(raw3, 0, rem); 652 | Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS); 653 | for (int i = 0; i < 4; i++) { 654 | encoded.put((char) (enc4[i] & 0xFF)); 655 | } 656 | } // end input remaining 657 | } 658 | 659 | /** 660 | * Serializes an object and returns the Base64-encoded version of that 661 | * serialized object. 662 | * 663 | *

664 | * As of v 2.3, if the object cannot be serialized or there is another 665 | * error, the method will throw an java.io.IOException. This is new to 666 | * v2.3! In earlier versions, it just returned a null value, but in 667 | * retrospect that's a pretty poor way to handle it. 668 | *

669 | * 670 | * The object is not GZip-compressed before being encoded. 671 | * 672 | * @param serializableObject 673 | * The object to encode 674 | * @return The Base64-encoded object 675 | * @throws java.io.IOException 676 | * if there is an error 677 | * @throws NullPointerException 678 | * if serializedObject is null 679 | * @since 1.4 680 | */ 681 | public static String encodeObject(java.io.Serializable serializableObject) throws java.io.IOException { 682 | return encodeObject(serializableObject, NO_OPTIONS); 683 | } // end encodeObject 684 | 685 | /** 686 | * Serializes an object and returns the Base64-encoded version of that 687 | * serialized object. 688 | * 689 | *

690 | * As of v 2.3, if the object cannot be serialized or there is another 691 | * error, the method will throw an java.io.IOException. This is new to 692 | * v2.3! In earlier versions, it just returned a null value, but in 693 | * retrospect that's a pretty poor way to handle it. 694 | *

695 | * 696 | * The object is not GZip-compressed before being encoded. 697 | *

698 | * Example options: 699 | * 700 | *

 701 | 	 *   GZIP: gzip-compresses object before encoding it.
 702 | 	 *   DO_BREAK_LINES: break lines at 76 characters
 703 | 	 * 
704 | *

705 | * Example: encodeObject( myObj, Base64.GZIP ) or 706 | *

707 | * Example: 708 | * encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES ) 709 | * 710 | * @param serializableObject 711 | * The object to encode 712 | * @param options 713 | * Specified options 714 | * @return The Base64-encoded object 715 | * @see Base64#GZIP 716 | * @see Base64#DO_BREAK_LINES 717 | * @throws java.io.IOException 718 | * if there is an error 719 | * @since 2.0 720 | */ 721 | public static String encodeObject(java.io.Serializable serializableObject, int options) throws java.io.IOException { 722 | 723 | if (serializableObject == null) { 724 | throw new NullPointerException("Cannot serialize a null object."); 725 | } // end if: null 726 | 727 | // Streams 728 | java.io.ByteArrayOutputStream baos = null; 729 | java.io.OutputStream b64os = null; 730 | java.util.zip.GZIPOutputStream gzos = null; 731 | java.io.ObjectOutputStream oos = null; 732 | 733 | try { 734 | // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream 735 | baos = new java.io.ByteArrayOutputStream(); 736 | b64os = new Base64.OutputStream(baos, ENCODE | options); 737 | if ((options & GZIP) != 0) { 738 | // Gzip 739 | gzos = new java.util.zip.GZIPOutputStream(b64os); 740 | oos = new java.io.ObjectOutputStream(gzos); 741 | } else { 742 | // Not gzipped 743 | oos = new java.io.ObjectOutputStream(b64os); 744 | } 745 | oos.writeObject(serializableObject); 746 | } // end try 747 | catch (java.io.IOException e) { 748 | // Catch it and then throw it immediately so that 749 | // the finally{} block is called for cleanup. 750 | throw e; 751 | } // end catch 752 | finally { 753 | try { 754 | oos.close(); 755 | } catch (Exception e) { 756 | } 757 | try { 758 | gzos.close(); 759 | } catch (Exception e) { 760 | } 761 | try { 762 | b64os.close(); 763 | } catch (Exception e) { 764 | } 765 | try { 766 | baos.close(); 767 | } catch (Exception e) { 768 | } 769 | } // end finally 770 | 771 | // Return value according to relevant encoding. 772 | try { 773 | return new String(baos.toByteArray(), PREFERRED_ENCODING); 774 | } // end try 775 | catch (java.io.UnsupportedEncodingException uue) { 776 | // Fall back to some Java default 777 | return new String(baos.toByteArray()); 778 | } // end catch 779 | 780 | } // end encode 781 | 782 | /** 783 | * Encodes a byte array into Base64 notation. Does not GZip-compress data. 784 | * 785 | * @param source 786 | * The data to convert 787 | * @return The data in Base64-encoded form 788 | * @throws NullPointerException 789 | * if source array is null 790 | * @since 1.4 791 | */ 792 | public static String encodeBytes(byte[] source) { 793 | // Since we're not going to have the GZIP encoding turned on, 794 | // we're not going to have an java.io.IOException thrown, so 795 | // we should not force the user to have to catch it. 796 | String encoded = null; 797 | try { 798 | encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); 799 | } catch (java.io.IOException ex) { 800 | assert false : ex.getMessage(); 801 | } // end catch 802 | assert encoded != null; 803 | return encoded; 804 | } // end encodeBytes 805 | 806 | /** 807 | * Encodes a byte array into Base64 notation. 808 | *

809 | * Example options: 810 | * 811 | *

 812 | 	 *   GZIP: gzip-compresses object before encoding it.
 813 | 	 *   DO_BREAK_LINES: break lines at 76 characters
 814 | 	 *     Note: Technically, this makes your encoding non-compliant.
 815 | 	 * 
816 | *

817 | * Example: encodeBytes( myData, Base64.GZIP ) or 818 | *

819 | * Example: 820 | * encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) 821 | * 822 | * 823 | *

824 | * As of v 2.3, if there is an error with the GZIP stream, the method will 825 | * throw an java.io.IOException. This is new to v2.3! In earlier 826 | * versions, it just returned a null value, but in retrospect that's a 827 | * pretty poor way to handle it. 828 | *

829 | * 830 | * 831 | * @param source 832 | * The data to convert 833 | * @param options 834 | * Specified options 835 | * @return The Base64-encoded data as a String 836 | * @see Base64#GZIP 837 | * @see Base64#DO_BREAK_LINES 838 | * @throws java.io.IOException 839 | * if there is an error 840 | * @throws NullPointerException 841 | * if source array is null 842 | * @since 2.0 843 | */ 844 | public static String encodeBytes(byte[] source, int options) throws java.io.IOException { 845 | return encodeBytes(source, 0, source.length, options); 846 | } // end encodeBytes 847 | 848 | /** 849 | * Encodes a byte array into Base64 notation. Does not GZip-compress data. 850 | * 851 | *

852 | * As of v 2.3, if there is an error, the method will throw an 853 | * java.io.IOException. This is new to v2.3! In earlier versions, it 854 | * just returned a null value, but in retrospect that's a pretty poor way to 855 | * handle it. 856 | *

857 | * 858 | * 859 | * @param source 860 | * The data to convert 861 | * @param off 862 | * Offset in array where conversion should begin 863 | * @param len 864 | * Length of data to convert 865 | * @return The Base64-encoded data as a String 866 | * @throws NullPointerException 867 | * if source array is null 868 | * @throws IllegalArgumentException 869 | * if source array, offset, or length are invalid 870 | * @since 1.4 871 | */ 872 | public static String encodeBytes(byte[] source, int off, int len) { 873 | // Since we're not going to have the GZIP encoding turned on, 874 | // we're not going to have an java.io.IOException thrown, so 875 | // we should not force the user to have to catch it. 876 | String encoded = null; 877 | try { 878 | encoded = encodeBytes(source, off, len, NO_OPTIONS); 879 | } catch (java.io.IOException ex) { 880 | assert false : ex.getMessage(); 881 | } // end catch 882 | assert encoded != null; 883 | return encoded; 884 | } // end encodeBytes 885 | 886 | /** 887 | * Encodes a byte array into Base64 notation. 888 | *

889 | * Example options: 890 | * 891 | *

 892 | 	 *   GZIP: gzip-compresses object before encoding it.
 893 | 	 *   DO_BREAK_LINES: break lines at 76 characters
 894 | 	 *     Note: Technically, this makes your encoding non-compliant.
 895 | 	 * 
896 | *

897 | * Example: encodeBytes( myData, Base64.GZIP ) or 898 | *

899 | * Example: 900 | * encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) 901 | * 902 | * 903 | *

904 | * As of v 2.3, if there is an error with the GZIP stream, the method will 905 | * throw an java.io.IOException. This is new to v2.3! In earlier 906 | * versions, it just returned a null value, but in retrospect that's a 907 | * pretty poor way to handle it. 908 | *

909 | * 910 | * 911 | * @param source 912 | * The data to convert 913 | * @param off 914 | * Offset in array where conversion should begin 915 | * @param len 916 | * Length of data to convert 917 | * @param options 918 | * Specified options 919 | * @return The Base64-encoded data as a String 920 | * @see Base64#GZIP 921 | * @see Base64#DO_BREAK_LINES 922 | * @throws java.io.IOException 923 | * if there is an error 924 | * @throws NullPointerException 925 | * if source array is null 926 | * @throws IllegalArgumentException 927 | * if source array, offset, or length are invalid 928 | * @since 2.0 929 | */ 930 | public static String encodeBytes(byte[] source, int off, int len, int options) throws java.io.IOException { 931 | byte[] encoded = encodeBytesToBytes(source, off, len, options); 932 | 933 | // Return value according to relevant encoding. 934 | try { 935 | return new String(encoded, PREFERRED_ENCODING); 936 | } // end try 937 | catch (java.io.UnsupportedEncodingException uue) { 938 | return new String(encoded); 939 | } // end catch 940 | 941 | } // end encodeBytes 942 | 943 | /** 944 | * Similar to {@link #encodeBytes(byte[])} but returns a byte array instead 945 | * of instantiating a String. This is more efficient if you're working with 946 | * I/O streams and have large data sets to encode. 947 | * 948 | * 949 | * @param source 950 | * The data to convert 951 | * @return The Base64-encoded data as a byte[] (of ASCII characters) 952 | * @throws NullPointerException 953 | * if source array is null 954 | * @since 2.3.1 955 | */ 956 | public static byte[] encodeBytesToBytes(byte[] source) { 957 | byte[] encoded = null; 958 | try { 959 | encoded = encodeBytesToBytes(source, 0, source.length, Base64.NO_OPTIONS); 960 | } catch (java.io.IOException ex) { 961 | assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); 962 | } 963 | return encoded; 964 | } 965 | 966 | /** 967 | * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns a byte 968 | * array instead of instantiating a String. This is more efficient if you're 969 | * working with I/O streams and have large data sets to encode. 970 | * 971 | * 972 | * @param source 973 | * The data to convert 974 | * @param off 975 | * Offset in array where conversion should begin 976 | * @param len 977 | * Length of data to convert 978 | * @param options 979 | * Specified options 980 | * @return The Base64-encoded data as a String 981 | * @see Base64#GZIP 982 | * @see Base64#DO_BREAK_LINES 983 | * @throws java.io.IOException 984 | * if there is an error 985 | * @throws NullPointerException 986 | * if source array is null 987 | * @throws IllegalArgumentException 988 | * if source array, offset, or length are invalid 989 | * @since 2.3.1 990 | */ 991 | public static byte[] encodeBytesToBytes(byte[] source, int off, int len, int options) throws java.io.IOException { 992 | 993 | if (source == null) { 994 | throw new NullPointerException("Cannot serialize a null array."); 995 | } // end if: null 996 | 997 | if (off < 0) { 998 | throw new IllegalArgumentException("Cannot have negative offset: " + off); 999 | } // end if: off < 0 1000 | 1001 | if (len < 0) { 1002 | throw new IllegalArgumentException("Cannot have length offset: " + len); 1003 | } // end if: len < 0 1004 | 1005 | if (off + len > source.length) { 1006 | throw new IllegalArgumentException(String.format( 1007 | "Cannot have offset of %d and length of %d with array of length %d", off, len, source.length)); 1008 | } // end if: off < 0 1009 | 1010 | // Compress? 1011 | if ((options & GZIP) != 0) { 1012 | java.io.ByteArrayOutputStream baos = null; 1013 | java.util.zip.GZIPOutputStream gzos = null; 1014 | Base64.OutputStream b64os = null; 1015 | 1016 | try { 1017 | // GZip -> Base64 -> ByteArray 1018 | baos = new java.io.ByteArrayOutputStream(); 1019 | b64os = new Base64.OutputStream(baos, ENCODE | options); 1020 | gzos = new java.util.zip.GZIPOutputStream(b64os); 1021 | 1022 | gzos.write(source, off, len); 1023 | gzos.close(); 1024 | } // end try 1025 | catch (java.io.IOException e) { 1026 | // Catch it and then throw it immediately so that 1027 | // the finally{} block is called for cleanup. 1028 | throw e; 1029 | } // end catch 1030 | finally { 1031 | try { 1032 | gzos.close(); 1033 | } catch (Exception e) { 1034 | } 1035 | try { 1036 | b64os.close(); 1037 | } catch (Exception e) { 1038 | } 1039 | try { 1040 | baos.close(); 1041 | } catch (Exception e) { 1042 | } 1043 | } // end finally 1044 | 1045 | return baos.toByteArray(); 1046 | } // end if: compress 1047 | 1048 | // Else, don't compress. Better not to use streams at all then. 1049 | else { 1050 | boolean breakLines = (options & DO_BREAK_LINES) != 0; 1051 | 1052 | // int len43 = len * 4 / 3; 1053 | // byte[] outBuff = new byte[ ( len43 ) // Main 4:3 1054 | // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding 1055 | // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines 1056 | // Try to determine more precisely how big the array needs to be. 1057 | // If we get it right, we don't have to do an array copy, and 1058 | // we save a bunch of memory. 1059 | int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0); // Bytes 1060 | // needed 1061 | // for 1062 | // actual 1063 | // encoding 1064 | if (breakLines) { 1065 | encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline 1066 | // characters 1067 | } 1068 | byte[] outBuff = new byte[encLen]; 1069 | 1070 | int d = 0; 1071 | int e = 0; 1072 | int len2 = len - 2; 1073 | int lineLength = 0; 1074 | for (; d < len2; d += 3, e += 4) { 1075 | encode3to4(source, d + off, 3, outBuff, e, options); 1076 | 1077 | lineLength += 4; 1078 | if (breakLines && lineLength >= MAX_LINE_LENGTH) { 1079 | outBuff[e + 4] = NEW_LINE; 1080 | e++; 1081 | lineLength = 0; 1082 | } // end if: end of line 1083 | } // en dfor: each piece of array 1084 | 1085 | if (d < len) { 1086 | encode3to4(source, d + off, len - d, outBuff, e, options); 1087 | e += 4; 1088 | } // end if: some padding needed 1089 | 1090 | // Only resize array if we didn't guess it right. 1091 | if (e <= outBuff.length - 1) { 1092 | // If breaking lines and the last byte falls right at 1093 | // the line length (76 bytes per line), there will be 1094 | // one extra byte, and the array will need to be resized. 1095 | // Not too bad of an estimate on array size, I'd say. 1096 | byte[] finalOut = new byte[e]; 1097 | System.arraycopy(outBuff, 0, finalOut, 0, e); 1098 | // System.err.println("Having to resize array from " + 1099 | // outBuff.length + " to " + e ); 1100 | return finalOut; 1101 | } else { 1102 | // System.err.println("No need to resize array."); 1103 | return outBuff; 1104 | } 1105 | 1106 | } // end else: don't compress 1107 | 1108 | } // end encodeBytesToBytes 1109 | 1110 | /* ******** D E C O D I N G M E T H O D S ******** */ 1111 | 1112 | /** 1113 | * Decodes four bytes from array source and writes the resulting 1114 | * bytes (up to three of them) to destination. The source and 1115 | * destination arrays can be manipulated anywhere along their length by 1116 | * specifying srcOffset and destOffset. This method 1117 | * does not check to make sure your arrays are large enough to accomodate 1118 | * srcOffset + 4 for the source array or 1119 | * destOffset + 3 for the destination array. This 1120 | * method returns the actual number of bytes that were converted from the 1121 | * Base64 encoding. 1122 | *

1123 | * This is the lowest level of the decoding methods with all possible 1124 | * parameters. 1125 | *

1126 | * 1127 | * 1128 | * @param source 1129 | * the array to convert 1130 | * @param srcOffset 1131 | * the index where conversion begins 1132 | * @param destination 1133 | * the array to hold the conversion 1134 | * @param destOffset 1135 | * the index where output will be put 1136 | * @param options 1137 | * alphabet type is pulled from this (standard, url-safe, 1138 | * ordered) 1139 | * @return the number of decoded bytes converted 1140 | * @throws NullPointerException 1141 | * if source or destination arrays are null 1142 | * @throws IllegalArgumentException 1143 | * if srcOffset or destOffset are invalid or there is not enough 1144 | * room in the array. 1145 | * @since 1.3 1146 | */ 1147 | private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset, int options) { 1148 | 1149 | // Lots of error checking and exception throwing 1150 | if (source == null) { 1151 | throw new NullPointerException("Source array was null."); 1152 | } // end if 1153 | if (destination == null) { 1154 | throw new NullPointerException("Destination array was null."); 1155 | } // end if 1156 | if (srcOffset < 0 || srcOffset + 3 >= source.length) { 1157 | throw new IllegalArgumentException(String.format( 1158 | "Source array with length %d cannot have offset of %d and still process four bytes.", 1159 | source.length, srcOffset)); 1160 | } // end if 1161 | if (destOffset < 0 || destOffset + 2 >= destination.length) { 1162 | throw new IllegalArgumentException(String.format( 1163 | "Destination array with length %d cannot have offset of %d and still store three bytes.", 1164 | destination.length, destOffset)); 1165 | } // end if 1166 | 1167 | byte[] DECODABET = getDecodabet(options); 1168 | 1169 | // Example: Dk== 1170 | if (source[srcOffset + 2] == EQUALS_SIGN) { 1171 | // Two ways to do the same thing. Don't know which way I like best. 1172 | // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 1173 | // ) 1174 | // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); 1175 | int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) 1176 | | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12); 1177 | 1178 | destination[destOffset] = (byte) (outBuff >>> 16); 1179 | return 1; 1180 | } 1181 | 1182 | // Example: DkL= 1183 | else if (source[srcOffset + 3] == EQUALS_SIGN) { 1184 | // Two ways to do the same thing. Don't know which way I like best. 1185 | // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 1186 | // ) 1187 | // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) 1188 | // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); 1189 | int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) 1190 | | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) 1191 | | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6); 1192 | 1193 | destination[destOffset] = (byte) (outBuff >>> 16); 1194 | destination[destOffset + 1] = (byte) (outBuff >>> 8); 1195 | return 2; 1196 | } 1197 | 1198 | // Example: DkLE 1199 | else { 1200 | // Two ways to do the same thing. Don't know which way I like best. 1201 | // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 1202 | // ) 1203 | // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) 1204 | // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) 1205 | // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); 1206 | int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) 1207 | | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) 1208 | | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) | ((DECODABET[source[srcOffset + 3]] & 0xFF)); 1209 | 1210 | destination[destOffset] = (byte) (outBuff >> 16); 1211 | destination[destOffset + 1] = (byte) (outBuff >> 8); 1212 | destination[destOffset + 2] = (byte) (outBuff); 1213 | 1214 | return 3; 1215 | } 1216 | } // end decodeToBytes 1217 | 1218 | /** 1219 | * Low-level access to decoding ASCII characters in the form of a byte 1220 | * array. Ignores GUNZIP option, if it's set. This is not 1221 | * generally a recommended method, although it is used internally as part of 1222 | * the decoding process. Special case: if len = 0, an empty array is 1223 | * returned. Still, if you need more speed and reduced memory footprint (and 1224 | * aren't gzipping), consider this method. 1225 | * 1226 | * @param source 1227 | * The Base64 encoded data 1228 | * @return decoded data 1229 | * @since 2.3.1 1230 | */ 1231 | public static byte[] decode(byte[] source) throws java.io.IOException { 1232 | byte[] decoded = null; 1233 | // try { 1234 | decoded = decode(source, 0, source.length, Base64.NO_OPTIONS); 1235 | // } catch( java.io.IOException ex ) { 1236 | // assert false : 1237 | // "IOExceptions only come from GZipping, which is turned off: " + 1238 | // ex.getMessage(); 1239 | // } 1240 | return decoded; 1241 | } 1242 | 1243 | /** 1244 | * Low-level access to decoding ASCII characters in the form of a byte 1245 | * array. Ignores GUNZIP option, if it's set. This is not 1246 | * generally a recommended method, although it is used internally as part of 1247 | * the decoding process. Special case: if len = 0, an empty array is 1248 | * returned. Still, if you need more speed and reduced memory footprint (and 1249 | * aren't gzipping), consider this method. 1250 | * 1251 | * @param source 1252 | * The Base64 encoded data 1253 | * @param off 1254 | * The offset of where to begin decoding 1255 | * @param len 1256 | * The length of characters to decode 1257 | * @param options 1258 | * Can specify options such as alphabet type to use 1259 | * @return decoded data 1260 | * @throws java.io.IOException 1261 | * If bogus characters exist in source data 1262 | * @since 1.3 1263 | */ 1264 | public static byte[] decode(byte[] source, int off, int len, int options) throws java.io.IOException { 1265 | 1266 | // Lots of error checking and exception throwing 1267 | if (source == null) { 1268 | throw new NullPointerException("Cannot decode null source array."); 1269 | } // end if 1270 | if (off < 0 || off + len > source.length) { 1271 | throw new IllegalArgumentException(String.format( 1272 | "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, 1273 | len)); 1274 | } // end if 1275 | 1276 | if (len == 0) { 1277 | return new byte[0]; 1278 | } else if (len < 4) { 1279 | throw new IllegalArgumentException( 1280 | "Base64-encoded string must have at least four characters, but length specified was " + len); 1281 | } // end if 1282 | 1283 | byte[] DECODABET = getDecodabet(options); 1284 | 1285 | int len34 = len * 3 / 4; // Estimate on array size 1286 | byte[] outBuff = new byte[len34]; // Upper limit on size of output 1287 | int outBuffPosn = 0; // Keep track of where we're writing 1288 | 1289 | byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating 1290 | // white space 1291 | int b4Posn = 0; // Keep track of four byte input buffer 1292 | int i = 0; // Source array counter 1293 | byte sbiDecode = 0; // Special value from DECODABET 1294 | 1295 | for (i = off; i < off + len; i++) { // Loop through source 1296 | 1297 | sbiDecode = DECODABET[source[i] & 0xFF]; 1298 | 1299 | // White space, Equals sign, or legit Base64 character 1300 | // Note the values such as -5 and -9 in the 1301 | // DECODABETs at the top of the file. 1302 | if (sbiDecode >= WHITE_SPACE_ENC) { 1303 | if (sbiDecode >= EQUALS_SIGN_ENC) { 1304 | b4[b4Posn++] = source[i]; // Save non-whitespace 1305 | if (b4Posn > 3) { // Time to decode? 1306 | outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, options); 1307 | b4Posn = 0; 1308 | 1309 | // If that was the equals sign, break out of 'for' loop 1310 | if (source[i] == EQUALS_SIGN) { 1311 | break; 1312 | } // end if: equals sign 1313 | } // end if: quartet built 1314 | } // end if: equals sign or better 1315 | } // end if: white space, equals sign or better 1316 | else { 1317 | // There's a bad input character in the Base64 stream. 1318 | throw new java.io.IOException(String.format( 1319 | "Bad Base64 input character decimal %d in array position %d", source[i] & 0xFF, i)); 1320 | } // end else: 1321 | } // each input character 1322 | 1323 | byte[] out = new byte[outBuffPosn]; 1324 | System.arraycopy(outBuff, 0, out, 0, outBuffPosn); 1325 | return out; 1326 | } // end decode 1327 | 1328 | /** 1329 | * Decodes data from Base64 notation, automatically detecting 1330 | * gzip-compressed data and decompressing it. 1331 | * 1332 | * @param s 1333 | * the string to decode 1334 | * @return the decoded data 1335 | * @throws java.io.IOException 1336 | * If there is a problem 1337 | * @since 1.4 1338 | */ 1339 | public static byte[] decode(String s) throws java.io.IOException { 1340 | return decode(s, NO_OPTIONS); 1341 | } 1342 | 1343 | /** 1344 | * Decodes data from Base64 notation, automatically detecting 1345 | * gzip-compressed data and decompressing it. 1346 | * 1347 | * @param s 1348 | * the string to decode 1349 | * @param options 1350 | * encode options such as URL_SAFE 1351 | * @return the decoded data 1352 | * @throws java.io.IOException 1353 | * if there is an error 1354 | * @throws NullPointerException 1355 | * if s is null 1356 | * @since 1.4 1357 | */ 1358 | public static byte[] decode(String s, int options) throws java.io.IOException { 1359 | 1360 | if (s == null) { 1361 | throw new NullPointerException("Input string was null."); 1362 | } // end if 1363 | 1364 | byte[] bytes; 1365 | try { 1366 | bytes = s.getBytes(PREFERRED_ENCODING); 1367 | } // end try 1368 | catch (java.io.UnsupportedEncodingException uee) { 1369 | bytes = s.getBytes(); 1370 | } // end catch 1371 | // 1372 | 1373 | // Decode 1374 | bytes = decode(bytes, 0, bytes.length, options); 1375 | 1376 | // Check to see if it's gzip-compressed 1377 | // GZIP Magic Two-Byte Number: 0x8b1f (35615) 1378 | boolean dontGunzip = (options & DONT_GUNZIP) != 0; 1379 | if ((bytes != null) && (bytes.length >= 4) && (!dontGunzip)) { 1380 | 1381 | int head = (bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); 1382 | if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) { 1383 | java.io.ByteArrayInputStream bais = null; 1384 | java.util.zip.GZIPInputStream gzis = null; 1385 | java.io.ByteArrayOutputStream baos = null; 1386 | byte[] buffer = new byte[2048]; 1387 | int length = 0; 1388 | 1389 | try { 1390 | baos = new java.io.ByteArrayOutputStream(); 1391 | bais = new java.io.ByteArrayInputStream(bytes); 1392 | gzis = new java.util.zip.GZIPInputStream(bais); 1393 | 1394 | while ((length = gzis.read(buffer)) >= 0) { 1395 | baos.write(buffer, 0, length); 1396 | } // end while: reading input 1397 | 1398 | // No error? Get new bytes. 1399 | bytes = baos.toByteArray(); 1400 | 1401 | } // end try 1402 | catch (java.io.IOException e) { 1403 | e.printStackTrace(); 1404 | // Just return originally-decoded bytes 1405 | } // end catch 1406 | finally { 1407 | try { 1408 | baos.close(); 1409 | } catch (Exception e) { 1410 | } 1411 | try { 1412 | gzis.close(); 1413 | } catch (Exception e) { 1414 | } 1415 | try { 1416 | bais.close(); 1417 | } catch (Exception e) { 1418 | } 1419 | } // end finally 1420 | 1421 | } // end if: gzipped 1422 | } // end if: bytes.length >= 2 1423 | 1424 | return bytes; 1425 | } // end decode 1426 | 1427 | /** 1428 | * Attempts to decode Base64 data and deserialize a Java Object within. 1429 | * Returns null if there was an error. 1430 | * 1431 | * @param encodedObject 1432 | * The Base64 data to decode 1433 | * @return The decoded and deserialized object 1434 | * @throws NullPointerException 1435 | * if encodedObject is null 1436 | * @throws java.io.IOException 1437 | * if there is a general error 1438 | * @throws ClassNotFoundException 1439 | * if the decoded object is of a class that cannot be found by 1440 | * the JVM 1441 | * @since 1.5 1442 | */ 1443 | public static Object decodeToObject(String encodedObject) throws java.io.IOException, 1444 | java.lang.ClassNotFoundException { 1445 | return decodeToObject(encodedObject, NO_OPTIONS, null); 1446 | } 1447 | 1448 | /** 1449 | * Attempts to decode Base64 data and deserialize a Java Object within. 1450 | * Returns null if there was an error. If loader is not 1451 | * null, it will be the class loader used when deserializing. 1452 | * 1453 | * @param encodedObject 1454 | * The Base64 data to decode 1455 | * @param options 1456 | * Various parameters related to decoding 1457 | * @param loader 1458 | * Optional class loader to use in deserializing classes. 1459 | * @return The decoded and deserialized object 1460 | * @throws NullPointerException 1461 | * if encodedObject is null 1462 | * @throws java.io.IOException 1463 | * if there is a general error 1464 | * @throws ClassNotFoundException 1465 | * if the decoded object is of a class that cannot be found by 1466 | * the JVM 1467 | * @since 2.3.4 1468 | */ 1469 | public static Object decodeToObject(String encodedObject, int options, final ClassLoader loader) 1470 | throws java.io.IOException, java.lang.ClassNotFoundException { 1471 | 1472 | // Decode and gunzip if necessary 1473 | byte[] objBytes = decode(encodedObject, options); 1474 | 1475 | java.io.ByteArrayInputStream bais = null; 1476 | java.io.ObjectInputStream ois = null; 1477 | Object obj = null; 1478 | 1479 | try { 1480 | bais = new java.io.ByteArrayInputStream(objBytes); 1481 | 1482 | // If no custom class loader is provided, use Java's builtin OIS. 1483 | if (loader == null) { 1484 | ois = new java.io.ObjectInputStream(bais); 1485 | } // end if: no loader provided 1486 | 1487 | // Else make a customized object input stream that uses 1488 | // the provided class loader. 1489 | else { 1490 | ois = new java.io.ObjectInputStream(bais) { 1491 | @Override 1492 | public Class resolveClass(java.io.ObjectStreamClass streamClass) throws java.io.IOException, 1493 | ClassNotFoundException { 1494 | Class c = Class.forName(streamClass.getName(), false, loader); 1495 | if (c == null) { 1496 | return super.resolveClass(streamClass); 1497 | } else { 1498 | return c; // Class loader knows of this class. 1499 | } // end else: not null 1500 | } // end resolveClass 1501 | }; // end ois 1502 | } // end else: no custom class loader 1503 | 1504 | obj = ois.readObject(); 1505 | } // end try 1506 | catch (java.io.IOException e) { 1507 | throw e; // Catch and throw in order to execute finally{} 1508 | } // end catch 1509 | catch (java.lang.ClassNotFoundException e) { 1510 | throw e; // Catch and throw in order to execute finally{} 1511 | } // end catch 1512 | finally { 1513 | try { 1514 | bais.close(); 1515 | } catch (Exception e) { 1516 | } 1517 | try { 1518 | ois.close(); 1519 | } catch (Exception e) { 1520 | } 1521 | } // end finally 1522 | 1523 | return obj; 1524 | } // end decodeObject 1525 | 1526 | /** 1527 | * Convenience method for encoding data to a file. 1528 | * 1529 | *

1530 | * As of v 2.3, if there is a error, the method will throw an 1531 | * java.io.IOException. This is new to v2.3! In earlier versions, it 1532 | * just returned false, but in retrospect that's a pretty poor way to handle 1533 | * it. 1534 | *

1535 | * 1536 | * @param dataToEncode 1537 | * byte array of data to encode in base64 form 1538 | * @param filename 1539 | * Filename for saving encoded data 1540 | * @throws java.io.IOException 1541 | * if there is an error 1542 | * @throws NullPointerException 1543 | * if dataToEncode is null 1544 | * @since 2.1 1545 | */ 1546 | public static void encodeToFile(byte[] dataToEncode, String filename) throws java.io.IOException { 1547 | 1548 | if (dataToEncode == null) { 1549 | throw new NullPointerException("Data to encode was null."); 1550 | } // end iff 1551 | 1552 | Base64.OutputStream bos = null; 1553 | try { 1554 | bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.ENCODE); 1555 | bos.write(dataToEncode); 1556 | } // end try 1557 | catch (java.io.IOException e) { 1558 | throw e; // Catch and throw to execute finally{} block 1559 | } // end catch: java.io.IOException 1560 | finally { 1561 | try { 1562 | bos.close(); 1563 | } catch (Exception e) { 1564 | } 1565 | } // end finally 1566 | 1567 | } // end encodeToFile 1568 | 1569 | /** 1570 | * Convenience method for decoding data to a file. 1571 | * 1572 | *

1573 | * As of v 2.3, if there is a error, the method will throw an 1574 | * java.io.IOException. This is new to v2.3! In earlier versions, it 1575 | * just returned false, but in retrospect that's a pretty poor way to handle 1576 | * it. 1577 | *

1578 | * 1579 | * @param dataToDecode 1580 | * Base64-encoded data as a string 1581 | * @param filename 1582 | * Filename for saving decoded data 1583 | * @throws java.io.IOException 1584 | * if there is an error 1585 | * @since 2.1 1586 | */ 1587 | public static void decodeToFile(String dataToDecode, String filename) throws java.io.IOException { 1588 | 1589 | Base64.OutputStream bos = null; 1590 | try { 1591 | bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.DECODE); 1592 | bos.write(dataToDecode.getBytes(PREFERRED_ENCODING)); 1593 | } // end try 1594 | catch (java.io.IOException e) { 1595 | throw e; // Catch and throw to execute finally{} block 1596 | } // end catch: java.io.IOException 1597 | finally { 1598 | try { 1599 | bos.close(); 1600 | } catch (Exception e) { 1601 | } 1602 | } // end finally 1603 | 1604 | } // end decodeToFile 1605 | 1606 | /** 1607 | * Convenience method for reading a base64-encoded file and decoding it. 1608 | * 1609 | *

1610 | * As of v 2.3, if there is a error, the method will throw an 1611 | * java.io.IOException. This is new to v2.3! In earlier versions, it 1612 | * just returned false, but in retrospect that's a pretty poor way to handle 1613 | * it. 1614 | *

1615 | * 1616 | * @param filename 1617 | * Filename for reading encoded data 1618 | * @return decoded byte array 1619 | * @throws java.io.IOException 1620 | * if there is an error 1621 | * @since 2.1 1622 | */ 1623 | public static byte[] decodeFromFile(String filename) throws java.io.IOException { 1624 | 1625 | byte[] decodedData = null; 1626 | Base64.InputStream bis = null; 1627 | try { 1628 | // Set up some useful variables 1629 | java.io.File file = new java.io.File(filename); 1630 | byte[] buffer = null; 1631 | int length = 0; 1632 | int numBytes = 0; 1633 | 1634 | // Check for size of file 1635 | if (file.length() > Integer.MAX_VALUE) { 1636 | throw new java.io.IOException("File is too big for this convenience method (" + file.length() 1637 | + " bytes)."); 1638 | } // end if: file too big for int index 1639 | buffer = new byte[(int) file.length()]; 1640 | 1641 | // Open a stream 1642 | bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)), 1643 | Base64.DECODE); 1644 | 1645 | // Read until done 1646 | while ((numBytes = bis.read(buffer, length, 4096)) >= 0) { 1647 | length += numBytes; 1648 | } // end while 1649 | 1650 | // Save in a variable to return 1651 | decodedData = new byte[length]; 1652 | System.arraycopy(buffer, 0, decodedData, 0, length); 1653 | 1654 | } // end try 1655 | catch (java.io.IOException e) { 1656 | throw e; // Catch and release to execute finally{} 1657 | } // end catch: java.io.IOException 1658 | finally { 1659 | try { 1660 | bis.close(); 1661 | } catch (Exception e) { 1662 | } 1663 | } // end finally 1664 | 1665 | return decodedData; 1666 | } // end decodeFromFile 1667 | 1668 | /** 1669 | * Convenience method for reading a binary file and base64-encoding it. 1670 | * 1671 | *

1672 | * As of v 2.3, if there is a error, the method will throw an 1673 | * java.io.IOException. This is new to v2.3! In earlier versions, it 1674 | * just returned false, but in retrospect that's a pretty poor way to handle 1675 | * it. 1676 | *

1677 | * 1678 | * @param filename 1679 | * Filename for reading binary data 1680 | * @return base64-encoded string 1681 | * @throws java.io.IOException 1682 | * if there is an error 1683 | * @since 2.1 1684 | */ 1685 | public static String encodeFromFile(String filename) throws java.io.IOException { 1686 | 1687 | String encodedData = null; 1688 | Base64.InputStream bis = null; 1689 | try { 1690 | // Set up some useful variables 1691 | java.io.File file = new java.io.File(filename); 1692 | byte[] buffer = new byte[Math.max((int) (file.length() * 1.4 + 1), 40)]; // Need 1693 | // max() 1694 | // for 1695 | // math 1696 | // on 1697 | // small 1698 | // files 1699 | // (v2.2.1); 1700 | // Need 1701 | // +1 1702 | // for 1703 | // a 1704 | // few 1705 | // corner 1706 | // cases 1707 | // (v2.3.5) 1708 | int length = 0; 1709 | int numBytes = 0; 1710 | 1711 | // Open a stream 1712 | bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)), 1713 | Base64.ENCODE); 1714 | 1715 | // Read until done 1716 | while ((numBytes = bis.read(buffer, length, 4096)) >= 0) { 1717 | length += numBytes; 1718 | } // end while 1719 | 1720 | // Save in a variable to return 1721 | encodedData = new String(buffer, 0, length, Base64.PREFERRED_ENCODING); 1722 | 1723 | } // end try 1724 | catch (java.io.IOException e) { 1725 | throw e; // Catch and release to execute finally{} 1726 | } // end catch: java.io.IOException 1727 | finally { 1728 | try { 1729 | bis.close(); 1730 | } catch (Exception e) { 1731 | } 1732 | } // end finally 1733 | 1734 | return encodedData; 1735 | } // end encodeFromFile 1736 | 1737 | /** 1738 | * Reads infile and encodes it to outfile. 1739 | * 1740 | * @param infile 1741 | * Input file 1742 | * @param outfile 1743 | * Output file 1744 | * @throws java.io.IOException 1745 | * if there is an error 1746 | * @since 2.2 1747 | */ 1748 | public static void encodeFileToFile(String infile, String outfile) throws java.io.IOException { 1749 | 1750 | String encoded = Base64.encodeFromFile(infile); 1751 | java.io.OutputStream out = null; 1752 | try { 1753 | out = new java.io.BufferedOutputStream(new java.io.FileOutputStream(outfile)); 1754 | out.write(encoded.getBytes("US-ASCII")); // Strict, 7-bit 1755 | // output. 1756 | } // end try 1757 | catch (java.io.IOException e) { 1758 | throw e; // Catch and release to execute finally{} 1759 | } // end catch 1760 | finally { 1761 | try { 1762 | out.close(); 1763 | } catch (Exception ex) { 1764 | } 1765 | } // end finally 1766 | } // end encodeFileToFile 1767 | 1768 | /** 1769 | * Reads infile and decodes it to outfile. 1770 | * 1771 | * @param infile 1772 | * Input file 1773 | * @param outfile 1774 | * Output file 1775 | * @throws java.io.IOException 1776 | * if there is an error 1777 | * @since 2.2 1778 | */ 1779 | public static void decodeFileToFile(String infile, String outfile) throws java.io.IOException { 1780 | 1781 | byte[] decoded = Base64.decodeFromFile(infile); 1782 | java.io.OutputStream out = null; 1783 | try { 1784 | out = new java.io.BufferedOutputStream(new java.io.FileOutputStream(outfile)); 1785 | out.write(decoded); 1786 | } // end try 1787 | catch (java.io.IOException e) { 1788 | throw e; // Catch and release to execute finally{} 1789 | } // end catch 1790 | finally { 1791 | try { 1792 | out.close(); 1793 | } catch (Exception ex) { 1794 | } 1795 | } // end finally 1796 | } // end decodeFileToFile 1797 | 1798 | /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ 1799 | 1800 | /** 1801 | * A {@link Base64.InputStream} will read data from another 1802 | * java.io.InputStream, given in the constructor, and encode/decode 1803 | * to/from Base64 notation on the fly. 1804 | * 1805 | * @see Base64 1806 | * @since 1.3 1807 | */ 1808 | public static class InputStream extends java.io.FilterInputStream { 1809 | 1810 | private final boolean encode; // Encoding or decoding 1811 | private int position; // Current position in the buffer 1812 | private final byte[] buffer; // Small buffer holding converted data 1813 | private final int bufferLength; // Length of buffer (3 or 4) 1814 | private int numSigBytes; // Number of meaningful bytes in the buffer 1815 | private int lineLength; 1816 | private final boolean breakLines; // Break lines at less than 80 1817 | // characters 1818 | private final int options; // Record options used to create the stream. 1819 | private final byte[] decodabet; // Local copies to avoid extra method 1820 | // calls 1821 | 1822 | /** 1823 | * Constructs a {@link Base64.InputStream} in DECODE mode. 1824 | * 1825 | * @param in 1826 | * the java.io.InputStream from which to read data. 1827 | * @since 1.3 1828 | */ 1829 | public InputStream(java.io.InputStream in) { 1830 | this(in, DECODE); 1831 | } // end constructor 1832 | 1833 | /** 1834 | * Constructs a {@link Base64.InputStream} in either ENCODE or DECODE 1835 | * mode. 1836 | *

1837 | * Valid options: 1838 | * 1839 | *

1840 | 		 *   ENCODE or DECODE: Encode or Decode as data is read.
1841 | 		 *   DO_BREAK_LINES: break lines at 76 characters
1842 | 		 *     (only meaningful when encoding)
1843 | 		 * 
1844 | *

1845 | * Example: new Base64.InputStream( in, Base64.DECODE ) 1846 | * 1847 | * 1848 | * @param in 1849 | * the java.io.InputStream from which to read data. 1850 | * @param options 1851 | * Specified options 1852 | * @see Base64#ENCODE 1853 | * @see Base64#DECODE 1854 | * @see Base64#DO_BREAK_LINES 1855 | * @since 2.0 1856 | */ 1857 | public InputStream(java.io.InputStream in, int options) { 1858 | 1859 | super(in); 1860 | this.options = options; // Record for later 1861 | this.breakLines = (options & DO_BREAK_LINES) > 0; 1862 | this.encode = (options & ENCODE) > 0; 1863 | this.bufferLength = encode ? 4 : 3; 1864 | this.buffer = new byte[bufferLength]; 1865 | this.position = -1; 1866 | this.lineLength = 0; 1867 | this.decodabet = getDecodabet(options); 1868 | } // end constructor 1869 | 1870 | /** 1871 | * Reads enough of the input stream to convert to/from Base64 and 1872 | * returns the next byte. 1873 | * 1874 | * @return next byte 1875 | * @since 1.3 1876 | */ 1877 | @Override 1878 | public int read() throws java.io.IOException { 1879 | 1880 | // Do we need to get data? 1881 | if (position < 0) { 1882 | if (encode) { 1883 | byte[] b3 = new byte[3]; 1884 | int numBinaryBytes = 0; 1885 | for (int i = 0; i < 3; i++) { 1886 | int b = in.read(); 1887 | 1888 | // If end of stream, b is -1. 1889 | if (b >= 0) { 1890 | b3[i] = (byte) b; 1891 | numBinaryBytes++; 1892 | } else { 1893 | break; // out of for loop 1894 | } // end else: end of stream 1895 | 1896 | } // end for: each needed input byte 1897 | 1898 | if (numBinaryBytes > 0) { 1899 | encode3to4(b3, 0, numBinaryBytes, buffer, 0, options); 1900 | position = 0; 1901 | numSigBytes = 4; 1902 | } // end if: got data 1903 | else { 1904 | return -1; // Must be end of stream 1905 | } // end else 1906 | } // end if: encoding 1907 | 1908 | // Else decoding 1909 | else { 1910 | byte[] b4 = new byte[4]; 1911 | int i = 0; 1912 | for (i = 0; i < 4; i++) { 1913 | // Read four "meaningful" bytes: 1914 | int b = 0; 1915 | do { 1916 | b = in.read(); 1917 | } while (b >= 0 && decodabet[b & 0x7f] <= WHITE_SPACE_ENC); 1918 | 1919 | if (b < 0) { 1920 | break; // Reads a -1 if end of stream 1921 | } // end if: end of stream 1922 | 1923 | b4[i] = (byte) b; 1924 | } // end for: each needed input byte 1925 | 1926 | if (i == 4) { 1927 | numSigBytes = decode4to3(b4, 0, buffer, 0, options); 1928 | position = 0; 1929 | } // end if: got four characters 1930 | else if (i == 0) { 1931 | return -1; 1932 | } // end else if: also padded correctly 1933 | else { 1934 | // Must have broken out from above. 1935 | throw new java.io.IOException("Improperly padded Base64 input."); 1936 | } // end 1937 | 1938 | } // end else: decode 1939 | } // end else: get data 1940 | 1941 | // Got data? 1942 | if (position >= 0) { 1943 | // End of relevant data? 1944 | if ( /* !encode && */position >= numSigBytes) { 1945 | return -1; 1946 | } // end if: got data 1947 | 1948 | if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) { 1949 | lineLength = 0; 1950 | return '\n'; 1951 | } // end if 1952 | else { 1953 | lineLength++; // This isn't important when decoding 1954 | // but throwing an extra "if" seems 1955 | // just as wasteful. 1956 | 1957 | int b = buffer[position++]; 1958 | 1959 | if (position >= bufferLength) { 1960 | position = -1; 1961 | } // end if: end 1962 | 1963 | return b & 0xFF; // This is how you "cast" a byte that's 1964 | // intended to be unsigned. 1965 | } // end else 1966 | } // end if: position >= 0 1967 | 1968 | // Else error 1969 | else { 1970 | throw new java.io.IOException("Error in Base64 code reading stream."); 1971 | } // end else 1972 | } // end read 1973 | 1974 | /** 1975 | * Calls {@link #read()} repeatedly until the end of stream is reached 1976 | * or len bytes are read. Returns number of bytes read into 1977 | * array or -1 if end of stream is encountered. 1978 | * 1979 | * @param dest 1980 | * array to hold values 1981 | * @param off 1982 | * offset for array 1983 | * @param len 1984 | * max number of bytes to read into array 1985 | * @return bytes read into array or -1 if end of stream is encountered. 1986 | * @since 1.3 1987 | */ 1988 | @Override 1989 | public int read(byte[] dest, int off, int len) throws java.io.IOException { 1990 | int i; 1991 | int b; 1992 | for (i = 0; i < len; i++) { 1993 | b = read(); 1994 | 1995 | if (b >= 0) { 1996 | dest[off + i] = (byte) b; 1997 | } else if (i == 0) { 1998 | return -1; 1999 | } else { 2000 | break; // Out of 'for' loop 2001 | } // Out of 'for' loop 2002 | } // end for: each byte read 2003 | return i; 2004 | } // end read 2005 | 2006 | } // end inner class InputStream 2007 | 2008 | /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ 2009 | 2010 | /** 2011 | * A {@link Base64.OutputStream} will write data to another 2012 | * java.io.OutputStream, given in the constructor, and 2013 | * encode/decode to/from Base64 notation on the fly. 2014 | * 2015 | * @see Base64 2016 | * @since 1.3 2017 | */ 2018 | public static class OutputStream extends java.io.FilterOutputStream { 2019 | 2020 | private final boolean encode; 2021 | private int position; 2022 | private byte[] buffer; 2023 | private final int bufferLength; 2024 | private int lineLength; 2025 | private final boolean breakLines; 2026 | private final byte[] b4; // Scratch used in a few places 2027 | private boolean suspendEncoding; 2028 | private final int options; // Record for later 2029 | private final byte[] decodabet; // Local copies to avoid extra method 2030 | // calls 2031 | 2032 | /** 2033 | * Constructs a {@link Base64.OutputStream} in ENCODE mode. 2034 | * 2035 | * @param out 2036 | * the java.io.OutputStream to which data will be 2037 | * written. 2038 | * @since 1.3 2039 | */ 2040 | public OutputStream(java.io.OutputStream out) { 2041 | this(out, ENCODE); 2042 | } // end constructor 2043 | 2044 | /** 2045 | * Constructs a {@link Base64.OutputStream} in either ENCODE or DECODE 2046 | * mode. 2047 | *

2048 | * Valid options: 2049 | * 2050 | *

2051 | 		 *   ENCODE or DECODE: Encode or Decode as data is read.
2052 | 		 *   DO_BREAK_LINES: don't break lines at 76 characters
2053 | 		 *     (only meaningful when encoding)
2054 | 		 * 
2055 | *

2056 | * Example: new Base64.OutputStream( out, Base64.ENCODE ) 2057 | * 2058 | * @param out 2059 | * the java.io.OutputStream to which data will be 2060 | * written. 2061 | * @param options 2062 | * Specified options. 2063 | * @see Base64#ENCODE 2064 | * @see Base64#DECODE 2065 | * @see Base64#DO_BREAK_LINES 2066 | * @since 1.3 2067 | */ 2068 | public OutputStream(java.io.OutputStream out, int options) { 2069 | super(out); 2070 | this.breakLines = (options & DO_BREAK_LINES) != 0; 2071 | this.encode = (options & ENCODE) != 0; 2072 | this.bufferLength = encode ? 3 : 4; 2073 | this.buffer = new byte[bufferLength]; 2074 | this.position = 0; 2075 | this.lineLength = 0; 2076 | this.suspendEncoding = false; 2077 | this.b4 = new byte[4]; 2078 | this.options = options; 2079 | this.decodabet = getDecodabet(options); 2080 | } // end constructor 2081 | 2082 | /** 2083 | * Writes the byte to the output stream after converting to/from Base64 2084 | * notation. When encoding, bytes are buffered three at a time before 2085 | * the output stream actually gets a write() call. When decoding, bytes 2086 | * are buffered four at a time. 2087 | * 2088 | * @param theByte 2089 | * the byte to write 2090 | * @since 1.3 2091 | */ 2092 | @Override 2093 | public void write(int theByte) throws java.io.IOException { 2094 | // Encoding suspended? 2095 | if (suspendEncoding) { 2096 | this.out.write(theByte); 2097 | return; 2098 | } // end if: supsended 2099 | 2100 | // Encode? 2101 | if (encode) { 2102 | buffer[position++] = (byte) theByte; 2103 | if (position >= bufferLength) { // Enough to encode. 2104 | 2105 | this.out.write(encode3to4(b4, buffer, bufferLength, options)); 2106 | 2107 | lineLength += 4; 2108 | if (breakLines && lineLength >= MAX_LINE_LENGTH) { 2109 | this.out.write(NEW_LINE); 2110 | lineLength = 0; 2111 | } // end if: end of line 2112 | 2113 | position = 0; 2114 | } // end if: enough to output 2115 | } // end if: encoding 2116 | 2117 | // Else, Decoding 2118 | else { 2119 | // Meaningful Base64 character? 2120 | if (decodabet[theByte & 0x7f] > WHITE_SPACE_ENC) { 2121 | buffer[position++] = (byte) theByte; 2122 | if (position >= bufferLength) { // Enough to output. 2123 | 2124 | int len = Base64.decode4to3(buffer, 0, b4, 0, options); 2125 | out.write(b4, 0, len); 2126 | position = 0; 2127 | } // end if: enough to output 2128 | } // end if: meaningful base64 character 2129 | else if (decodabet[theByte & 0x7f] != WHITE_SPACE_ENC) { 2130 | throw new java.io.IOException("Invalid character in Base64 data."); 2131 | } // end else: not white space either 2132 | } // end else: decoding 2133 | } // end write 2134 | 2135 | /** 2136 | * Calls {@link #write(int)} repeatedly until len bytes are 2137 | * written. 2138 | * 2139 | * @param theBytes 2140 | * array from which to read bytes 2141 | * @param off 2142 | * offset for array 2143 | * @param len 2144 | * max number of bytes to read into array 2145 | * @since 1.3 2146 | */ 2147 | @Override 2148 | public void write(byte[] theBytes, int off, int len) throws java.io.IOException { 2149 | // Encoding suspended? 2150 | if (suspendEncoding) { 2151 | this.out.write(theBytes, off, len); 2152 | return; 2153 | } // end if: supsended 2154 | 2155 | for (int i = 0; i < len; i++) { 2156 | write(theBytes[off + i]); 2157 | } // end for: each byte written 2158 | 2159 | } // end write 2160 | 2161 | /** 2162 | * Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer 2163 | * without closing the stream. 2164 | * 2165 | * @throws java.io.IOException 2166 | * if there's an error. 2167 | */ 2168 | public void flushBase64() throws java.io.IOException { 2169 | if (position > 0) { 2170 | if (encode) { 2171 | out.write(encode3to4(b4, buffer, position, options)); 2172 | position = 0; 2173 | } // end if: encoding 2174 | else { 2175 | throw new java.io.IOException("Base64 input not properly padded."); 2176 | } // end else: decoding 2177 | } // end if: buffer partially full 2178 | 2179 | } // end flush 2180 | 2181 | /** 2182 | * Flushes and closes (I think, in the superclass) the stream. 2183 | * 2184 | * @since 1.3 2185 | */ 2186 | @Override 2187 | public void close() throws java.io.IOException { 2188 | // 1. Ensure that pending characters are written 2189 | flushBase64(); 2190 | 2191 | // 2. Actually close the stream 2192 | // Base class both flushes and closes. 2193 | super.close(); 2194 | 2195 | buffer = null; 2196 | out = null; 2197 | } // end close 2198 | 2199 | /** 2200 | * Suspends encoding of the stream. May be helpful if you need to embed 2201 | * a piece of base64-encoded data in a stream. 2202 | * 2203 | * @throws java.io.IOException 2204 | * if there's an error flushing 2205 | * @since 1.5.1 2206 | */ 2207 | public void suspendEncoding() throws java.io.IOException { 2208 | flushBase64(); 2209 | this.suspendEncoding = true; 2210 | } // end suspendEncoding 2211 | 2212 | /** 2213 | * Resumes encoding of the stream. May be helpful if you need to embed a 2214 | * piece of base64-encoded data in a stream. 2215 | * 2216 | * @since 1.5.1 2217 | */ 2218 | public void resumeEncoding() { 2219 | this.suspendEncoding = false; 2220 | } // end resumeEncoding 2221 | 2222 | } // end inner class OutputStream 2223 | 2224 | } // end class Base64 2225 | -------------------------------------------------------------------------------- /src/test/java/org/kamranzafar/jddl/AndroidExample.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012 Kamran Zafar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.kamranzafar.jddl; 19 | 20 | import java.io.File; 21 | import java.io.FileOutputStream; 22 | import java.net.URL; 23 | 24 | import android.app.Activity; 25 | import android.app.Dialog; 26 | import android.app.ProgressDialog; 27 | import android.content.DialogInterface; 28 | import android.os.Bundle; 29 | import android.os.Environment; 30 | import android.os.Handler; 31 | import android.os.Message; 32 | 33 | /** 34 | * @author Kamran 35 | * 36 | * This is just an template/example that shows how jddl can be used in 37 | * Android apps, the following code should be updated as per 38 | * requirements. 39 | * 40 | */ 41 | public class AndroidExample extends Activity { 42 | public static final int DOWNLOAD_PROGRESS_DIALOG_ID = 0; 43 | 44 | private DirectDownloader dd = new DirectDownloader(); 45 | 46 | private ProgressDialog mProgressDialog; 47 | 48 | File downloadDir = new File(Environment.getExternalStorageDirectory() 49 | + "/Android/data/org.kamranzafar.android.test/files"); 50 | 51 | private final Handler callback = new Handler() { 52 | @Override 53 | public void handleMessage(Message msg) { 54 | // TODO: update UI etc. 55 | }; 56 | }; 57 | 58 | @Override 59 | public void onCreate(Bundle savedInstanceState) { 60 | super.onCreate(savedInstanceState); 61 | 62 | // TODO: initializations etc. 63 | // this should be updated as per requirements 64 | 65 | downloadDir.mkdirs(); 66 | 67 | // start downloading... this can be called from button clicks etc. 68 | new DownloadFileAsync().download("http://www.python.org/ftp/python/3.2.2/python-3.2.2.msi"); 69 | } 70 | 71 | @Override 72 | protected Dialog onCreateDialog(int id) { 73 | switch (id) { 74 | case DOWNLOAD_PROGRESS_DIALOG_ID: 75 | mProgressDialog = new ProgressDialog(this); 76 | mProgressDialog.setMessage("Downloading..."); 77 | mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); 78 | mProgressDialog.setCancelable(true); 79 | mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { 80 | public void onCancel(DialogInterface dialog) { 81 | dd.cancelAll(); 82 | } 83 | }); 84 | mProgressDialog.show(); 85 | 86 | return mProgressDialog; 87 | default: 88 | return null; 89 | } 90 | } 91 | 92 | /** 93 | * shutdown jddl 94 | */ 95 | @Override 96 | protected void onPause() { 97 | dd.shutdown(); 98 | dd = null; 99 | 100 | super.onPause(); 101 | } 102 | 103 | /** 104 | * start jddl 105 | */ 106 | @Override 107 | protected void onResume() { 108 | super.onResume(); 109 | 110 | dd = new DirectDownloader(); 111 | new Thread(dd).start(); 112 | } 113 | 114 | // This is an example download listner, that can be used to update UI and to 115 | // track download progress 116 | class DownloadFileAsync implements DownloadListener { 117 | private int fsize = -1; 118 | private String fname; 119 | 120 | private File downloadFile; 121 | 122 | public String download(String url) { 123 | fname = url.substring(url.lastIndexOf('/') + 1); 124 | downloadFile = new File(downloadDir, fname); 125 | try { 126 | DownloadTask dt = new DownloadTask(new URL(url), new FileOutputStream(downloadFile), this); 127 | dd.download(dt); 128 | } catch (Exception e) { 129 | e.printStackTrace(); 130 | } finally { 131 | // show progress dialog 132 | showDialog(DOWNLOAD_PROGRESS_DIALOG_ID); 133 | } 134 | return null; 135 | } 136 | 137 | public void onComplete() { 138 | // dismiss progress dialog 139 | if (mProgressDialog.isShowing()) { 140 | mProgressDialog.dismiss(); 141 | } 142 | 143 | // an example call back to update UI etc. 144 | Bundle b = new Bundle(); 145 | b.putString("my_message", "download complete"); 146 | 147 | Message m = new Message(); 148 | m.setData(b); 149 | 150 | callback.sendMessage(m); 151 | } 152 | 153 | public void onStart(String fname, int arg0) { 154 | fsize = arg0; 155 | } 156 | 157 | public void onUpdate(int arg0, int arg1) { 158 | // update progress dialog 159 | mProgressDialog.setProgress((arg1 * 100) / fsize); 160 | } 161 | 162 | public void onCancel() { 163 | // delete partly downloaded file if the download is cancelled. 164 | if (downloadFile.exists()) { 165 | downloadFile.delete(); 166 | } 167 | } 168 | } 169 | } -------------------------------------------------------------------------------- /src/test/java/org/kamranzafar/jddl/SimpleTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012 Kamran Zafar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.kamranzafar.jddl; 19 | 20 | import java.io.FileNotFoundException; 21 | import java.io.FileOutputStream; 22 | import java.net.MalformedURLException; 23 | import java.net.URL; 24 | 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.junit.runners.JUnit4; 28 | 29 | /** 30 | * @author kamran 31 | * 32 | */ 33 | @RunWith(JUnit4.class) 34 | public class SimpleTest { 35 | DirectDownloader dd = new DirectDownloader(); 36 | 37 | @Test 38 | public void testSimple() throws MalformedURLException, FileNotFoundException, InterruptedException { 39 | final Thread t = new Thread(dd); 40 | final String file = "http://python.org/ftp/python/2.7.2/python-2.7.2.msi"; 41 | final String f = "target/" + file.substring(file.lastIndexOf('/') + 1); 42 | 43 | DownloadTask dt = new DownloadTask(new URL(file), new FileOutputStream(f)).addListener(new DownloadListener() { 44 | int size; 45 | 46 | public void onUpdate(int bytes, int totalDownloaded) { 47 | updateProgress((double) totalDownloaded / size); 48 | } 49 | 50 | public void onStart(String fname, int size) { 51 | System.out.println("Downloading " + fname + " of size " + size); 52 | this.size = size; 53 | updateProgress(0); 54 | } 55 | 56 | public void onComplete() { 57 | System.out.println("\n" + f + " downloaded"); 58 | } 59 | 60 | public void onCancel() { 61 | 62 | } 63 | }); 64 | 65 | dd.download(dt); 66 | 67 | t.start(); 68 | t.join(); 69 | } 70 | 71 | void updateProgress(double progressPercentage) { 72 | final int width = 50; 73 | 74 | System.out.print("\r["); 75 | int i = 0; 76 | for (; i <= (int) (progressPercentage * width); i++) { 77 | System.out.print("."); 78 | } 79 | for (; i < width; i++) { 80 | System.out.print(" "); 81 | } 82 | System.out.print("]"); 83 | } 84 | 85 | public static void main(String[] args) throws MalformedURLException, FileNotFoundException, InterruptedException { 86 | SimpleTest st = new SimpleTest(); 87 | st.testSimple(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/org/kamranzafar/jddl/SwingTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012 Kamran Zafar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package org.kamranzafar.jddl; 19 | 20 | import java.awt.Container; 21 | import java.awt.GridLayout; 22 | import java.awt.event.ActionEvent; 23 | import java.awt.event.ActionListener; 24 | import java.io.File; 25 | import java.io.FileOutputStream; 26 | import java.io.IOException; 27 | import java.net.URL; 28 | 29 | import javax.swing.BorderFactory; 30 | import javax.swing.BoxLayout; 31 | import javax.swing.JButton; 32 | import javax.swing.JFrame; 33 | import javax.swing.JPanel; 34 | import javax.swing.JProgressBar; 35 | import javax.swing.border.TitledBorder; 36 | 37 | import org.junit.Test; 38 | import org.junit.runner.RunWith; 39 | import org.junit.runners.JUnit4; 40 | 41 | /** 42 | * @author kamran 43 | * 44 | */ 45 | @RunWith(JUnit4.class) 46 | public class SwingTest { 47 | private static final String STOP = "Stop"; 48 | private static final String CANCEL = "Cancel"; 49 | private static final String PAUSE = "Pause"; 50 | private static final String RESUME = "Resume"; 51 | private static final String TOTAL = "Total"; 52 | 53 | // total progress bar 54 | private final JProgressBar totalProgressBar = new JProgressBar(); 55 | 56 | @Test 57 | public void testSwing() throws IOException, InterruptedException { 58 | JFrame f = new JFrame("jddl Swing example"); 59 | f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 60 | Container content = f.getContentPane(); 61 | content.setLayout(new GridLayout(4, 1)); 62 | 63 | // Some files 64 | String files[] = { "http://python.org/ftp/python/2.7.2/python-2.7.2.msi", 65 | "http://www.python.org/ftp/python/3.2.2/python-3.2.2.msi", 66 | "http://www.python.org/ftp/python/3.2.2/python-3.2.2.amd64.msi" }; 67 | // Create a DirectDownloader instance 68 | final DirectDownloader fd = new DirectDownloader(); 69 | 70 | // Progress bars for individual file downloads 71 | JProgressBar[] progressBar = new JProgressBar[3]; 72 | 73 | // Pause/Resume buttons 74 | JButton[] pauseButton = new JButton[3]; 75 | 76 | // Cancel 77 | JButton[] cancelButton = new JButton[3]; 78 | 79 | JButton stopButton = new JButton(STOP); 80 | stopButton.addActionListener(new ActionListener() { 81 | public void actionPerformed(ActionEvent e) { 82 | fd.cancelAll(); 83 | } 84 | }); 85 | 86 | // Initialize progress bars and create download tasks 87 | for (int i = 0; i < 3; i++) { 88 | String fname = files[i].substring(files[i].lastIndexOf('/') + 1); 89 | 90 | progressBar[i] = new JProgressBar(); 91 | pauseButton[i] = new JButton(PAUSE); 92 | cancelButton[i] = new JButton(CANCEL); 93 | 94 | JPanel panel = new JPanel(); 95 | BoxLayout box = new BoxLayout(panel, BoxLayout.X_AXIS); 96 | 97 | panel.setLayout(box); 98 | 99 | final DownloadTask dt = new DownloadTask(new URL(files[i]), new FileOutputStream(fname)); 100 | dt.addListener(new ProgressBarUpdator(fname, progressBar[i])); 101 | 102 | pauseButton[i].addActionListener(new ActionListener() { 103 | public void actionPerformed(ActionEvent e) { 104 | if (dt.isPaused()) { 105 | dt.setPaused(false); 106 | ((JButton) e.getSource()).setText(PAUSE); 107 | } else { 108 | dt.setPaused(true); 109 | ((JButton) e.getSource()).setText(RESUME); 110 | } 111 | } 112 | }); 113 | 114 | cancelButton[i].addActionListener(new ActionListener() { 115 | public void actionPerformed(ActionEvent arg0) { 116 | dt.setCancelled(true); 117 | } 118 | }); 119 | 120 | progressBar[i].setStringPainted(true); 121 | progressBar[i].setBorder(BorderFactory.createTitledBorder("Downloading " + fname + "...")); 122 | 123 | panel.add(progressBar[i]); 124 | panel.add(pauseButton[i]); 125 | panel.add(cancelButton[i]); 126 | 127 | content.add(panel); 128 | 129 | fd.download(dt); 130 | } 131 | 132 | totalProgressBar.setBorder(BorderFactory.createTitledBorder(TOTAL)); 133 | totalProgressBar.setStringPainted(true); 134 | totalProgressBar.setMaximum(0); 135 | 136 | JPanel panel = new JPanel(); 137 | BoxLayout box = new BoxLayout(panel, BoxLayout.X_AXIS); 138 | 139 | panel.setLayout(box); 140 | panel.add(totalProgressBar); 141 | panel.add(stopButton); 142 | 143 | content.add(panel); 144 | 145 | f.setSize(400, 200); 146 | f.setVisible(true); 147 | 148 | // Start downloading 149 | Thread t = new Thread(fd); 150 | t.start(); 151 | } 152 | 153 | // Class that updates the download progress 154 | class ProgressBarUpdator implements DownloadListener { 155 | private static final String DONE = "Done"; 156 | JProgressBar progressBar; 157 | int size = -1; 158 | 159 | String fname; 160 | 161 | public ProgressBarUpdator(String fname, JProgressBar progressBar) { 162 | this.progressBar = progressBar; 163 | this.fname = fname; 164 | } 165 | 166 | public void onComplete() { 167 | if (size == -1) { 168 | progressBar.setIndeterminate(false); 169 | progressBar.setValue(100); 170 | } 171 | 172 | ((TitledBorder) progressBar.getBorder()).setTitle(DONE); 173 | progressBar.repaint(); 174 | } 175 | 176 | public void onStart(String fname, int fsize) { 177 | if (fsize > -1) { 178 | progressBar.setMaximum(fsize); 179 | 180 | synchronized (totalProgressBar) { 181 | totalProgressBar.setMaximum(totalProgressBar.getMaximum() + fsize); 182 | } 183 | 184 | size = fsize; 185 | } else { 186 | progressBar.setIndeterminate(true); 187 | } 188 | } 189 | 190 | public void onUpdate(int bytes, int totalDownloaded) { 191 | if (size == -1) { 192 | progressBar.setString("" + totalDownloaded); 193 | } else { 194 | progressBar.setValue(totalDownloaded); 195 | 196 | synchronized (totalProgressBar) { 197 | totalProgressBar.setValue(totalProgressBar.getValue() + bytes); 198 | } 199 | } 200 | } 201 | 202 | public void onCancel() { 203 | new File(fname).delete(); 204 | } 205 | } 206 | 207 | public static void main(String[] args) throws IOException, InterruptedException { 208 | SwingTest st = new SwingTest(); 209 | st.testSwing(); 210 | } 211 | } 212 | --------------------------------------------------------------------------------