001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.commons.net.http.httpclient4;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.lang.reflect.InvocationTargetException;
024import java.lang.reflect.Method;
025import java.net.URL;
026import java.util.ArrayList;
027import java.util.Iterator;
028import java.util.List;
029import java.util.Map;
030import java.util.Map.Entry;
031
032import lucee.commons.io.IOUtil;
033import lucee.commons.io.TemporaryStream;
034import lucee.commons.io.res.Resource;
035import lucee.commons.lang.ExceptionUtil;
036import lucee.commons.lang.StringUtil;
037import lucee.commons.net.http.Entity;
038import lucee.commons.net.http.HTTPResponse;
039import lucee.commons.net.http.httpclient4.entity.ByteArrayHttpEntity;
040import lucee.commons.net.http.httpclient4.entity.EmptyHttpEntity;
041import lucee.commons.net.http.httpclient4.entity.ResourceHttpEntity;
042import lucee.commons.net.http.httpclient4.entity.TemporaryStreamHttpEntity;
043import lucee.runtime.PageContextImpl;
044import lucee.runtime.engine.ThreadLocalPageContext;
045import lucee.runtime.exp.PageException;
046import lucee.runtime.net.http.ReqRspUtil;
047import lucee.runtime.net.proxy.ProxyData;
048import lucee.runtime.net.proxy.ProxyDataImpl;
049import lucee.runtime.op.Caster;
050import lucee.runtime.op.Decision;
051import lucee.runtime.type.dt.TimeSpan;
052import lucee.runtime.type.dt.TimeSpanImpl;
053import lucee.runtime.type.util.CollectionUtil;
054
055import org.apache.http.Header;
056import org.apache.http.HttpEntity;
057import org.apache.http.HttpEntityEnclosingRequest;
058import org.apache.http.HttpHost;
059import org.apache.http.HttpMessage;
060import org.apache.http.HttpResponse;
061import org.apache.http.NameValuePair;
062import org.apache.http.auth.AuthScope;
063import org.apache.http.auth.NTCredentials;
064import org.apache.http.auth.UsernamePasswordCredentials;
065import org.apache.http.client.AuthCache;
066import org.apache.http.client.ClientProtocolException;
067import org.apache.http.client.CredentialsProvider;
068import org.apache.http.client.HttpClient;
069import org.apache.http.client.methods.HttpDelete;
070import org.apache.http.client.methods.HttpGet;
071import org.apache.http.client.methods.HttpHead;
072import org.apache.http.client.methods.HttpPost;
073import org.apache.http.client.methods.HttpPut;
074import org.apache.http.client.methods.HttpUriRequest;
075import org.apache.http.client.params.ClientPNames;
076import org.apache.http.client.protocol.ClientContext;
077import org.apache.http.conn.params.ConnRoutePNames;
078import org.apache.http.entity.ByteArrayEntity;
079import org.apache.http.entity.StringEntity;
080import org.apache.http.impl.auth.BasicScheme;
081import org.apache.http.impl.client.BasicAuthCache;
082import org.apache.http.impl.client.DefaultHttpClient;
083import org.apache.http.impl.cookie.BasicClientCookie;
084import org.apache.http.message.BasicNameValuePair;
085import org.apache.http.params.BasicHttpParams;
086import org.apache.http.params.HttpConnectionParams;
087import org.apache.http.params.HttpParams;
088import org.apache.http.protocol.BasicHttpContext;
089import org.apache.http.protocol.HttpContext;
090
091public class HTTPEngine4Impl {
092        
093        /**
094         * does a http get request
095         * @param url
096         * @param username
097         * @param password
098         * @param timeout in ms
099         * @param charset
100         * @param useragent
101         * @param proxyserver
102         * @param proxyport
103         * @param proxyuser
104         * @param proxypassword
105         * @param headers
106         * @return
107         * @throws IOException
108         */
109        public static HTTPResponse get(URL url, String username,String password, long timeout,  int maxRedirect,
110            String charset, String useragent,
111            ProxyData proxy, lucee.commons.net.http.Header[] headers) throws IOException {
112                HttpGet get = new HttpGet(url.toExternalForm());
113                return _invoke(url,get, username, password, timeout,maxRedirect, charset, useragent, proxy, headers,null);
114        }
115
116    /**
117         * does a http post request
118     * @param url
119     * @param username
120     * @param password
121     * @param timeout in ms
122     * @param charset
123     * @param useragent
124     * @param proxyserver
125     * @param proxyport
126     * @param proxyuser
127     * @param proxypassword
128     * @param headers
129     * @return
130     * @throws IOException
131     */
132    public static HTTPResponse post(URL url, String username,String password, long timeout,  int maxRedirect,
133            String charset, String useragent,
134            ProxyData proxy, lucee.commons.net.http.Header[] headers) throws IOException {
135        HttpPost post = new HttpPost(url.toExternalForm());
136        return _invoke(url,post, username, password, timeout,maxRedirect, charset, useragent, proxy, headers,null);
137    }
138    
139
140    
141
142    public static HTTPResponse post(URL url, String username,String password, long timeout, int maxRedirect,
143            String charset, String useragent,
144            ProxyData proxy, lucee.commons.net.http.Header[] headers,Map<String,String> formfields) throws IOException {
145        HttpPost post = new HttpPost(url.toExternalForm());
146        
147        return _invoke(url,post, username, password, timeout,maxRedirect, charset, useragent, proxy, headers,formfields);
148    }
149    
150    
151    /**
152         * does a http put request
153     * @param url
154     * @param username
155     * @param password
156     * @param timeout in ms
157     * @param charset
158     * @param useragent
159     * @param proxyserver
160     * @param proxyport
161     * @param proxyuser
162     * @param proxypassword
163     * @param headers
164     * @param body
165     * @return
166     * @throws IOException
167     * @throws PageException 
168     */
169    public static HTTPResponse put(URL url, String username,String password, long timeout,  int maxRedirect,
170                String mimetype,String charset, String useragent,
171            ProxyData proxy, lucee.commons.net.http.Header[] headers, Object body) throws IOException {
172                HttpPut put= new HttpPut(url.toExternalForm());
173                setBody(put,body,mimetype,charset);
174        return _invoke(url,put, username, password, timeout, maxRedirect, charset, useragent, proxy, headers,null);
175                 
176        }
177    
178    /**
179         * does a http delete request
180     * @param url
181     * @param username
182     * @param password
183     * @param timeout in ms
184     * @param charset
185     * @param useragent
186     * @param proxyserver
187     * @param proxyport
188     * @param proxyuser
189     * @param proxypassword
190     * @param headers
191     * @return
192     * @throws IOException
193     */
194    public static HTTPResponse delete(URL url, String username,String password, long timeout,  int maxRedirect,
195            String charset, String useragent,
196            ProxyData proxy, lucee.commons.net.http.Header[] headers) throws IOException {
197        HttpDelete delete= new HttpDelete(url.toExternalForm());
198                return _invoke(url,delete, username, password, timeout, maxRedirect, charset, useragent, proxy, headers,null);    
199        }
200
201    /**
202         * does a http head request
203     * @param url
204     * @param username
205     * @param password
206     * @param timeout in ms
207     * @param charset
208     * @param useragent
209     * @param proxyserver
210     * @param proxyport
211     * @param proxyuser
212     * @param proxypassword
213     * @param headers
214     * @return
215     * @throws IOException
216     */
217    public static HTTPResponse head(URL url, String username,String password, long timeout, int maxRedirect,
218            String charset, String useragent,
219            ProxyData proxy, lucee.commons.net.http.Header[] headers) throws IOException {
220        HttpHead head= new HttpHead(url.toExternalForm());
221                return _invoke(url,head, username, password, timeout, maxRedirect, charset, useragent, proxy, headers,null);    
222        }
223    
224        
225
226        public static lucee.commons.net.http.Header header(String name, String value) {
227                return new HeaderImpl(name, value);
228        }
229        
230
231        private static Header toHeader(lucee.commons.net.http.Header header) {
232                if(header instanceof Header) return (Header) header;
233                if(header instanceof HeaderWrap) return ((HeaderWrap)header).header;
234                return new HeaderImpl(header.getName(), header.getValue());
235        }
236        
237        private static HTTPResponse _invoke(URL url,HttpUriRequest request,String username,String password, long timeout, int maxRedirect,
238            String charset, String useragent,
239            ProxyData proxy, lucee.commons.net.http.Header[] headers, Map<String,String> formfields) throws IOException {
240        
241        // TODO HttpConnectionManager manager=new SimpleHttpConnectionManager();//MultiThreadedHttpConnectionManager();
242                BasicHttpParams params = new BasicHttpParams();
243        DefaultHttpClient client = createClient(params,maxRedirect);
244        HttpHost hh=new HttpHost(url.getHost(),url.getPort());
245        setHeader(request,headers);
246        if(CollectionUtil.isEmpty(formfields))setContentType(request,charset);
247        setFormFields(request,formfields,charset);
248        setUserAgent(request,useragent);
249        setTimeout(params,timeout>0?TimeSpanImpl.fromMillis(timeout):null);
250        HttpContext context=setCredentials(client,hh, username, password,false);  
251        setProxy(client,request,proxy);
252        if(context==null)context = new BasicHttpContext();
253        return new HTTPResponse4Impl(url,context,request,execute(client,request,context));
254    }
255        
256        private static void setFormFields(HttpUriRequest request, Map<String, String> formfields, String charset) throws IOException {
257                if(!CollectionUtil.isEmpty(formfields)) {
258                        if(!(request instanceof HttpPost)) throw new IOException("form fields are only suppported for post request");
259                        HttpPost post=(HttpPost) request;
260                List<NameValuePair> list = new ArrayList<NameValuePair>();
261                Iterator<Entry<String, String>> it = formfields.entrySet().iterator();
262                Entry<String, String> e;
263                while(it.hasNext()){
264                        e = it.next();
265                        list.add(new BasicNameValuePair(e.getKey(),e.getValue()));
266                }
267                if(StringUtil.isEmpty(charset)) charset=((PageContextImpl)ThreadLocalPageContext.get()).getWebCharset().name();
268                
269                post.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(list,charset));
270        }
271        }
272
273        public static DefaultHttpClient createClient(BasicHttpParams params, int maxRedirect) {
274        params.setParameter(ClientPNames.HANDLE_REDIRECTS, maxRedirect==0?Boolean.FALSE:Boolean.TRUE);
275        if(maxRedirect>0)params.setParameter(ClientPNames.MAX_REDIRECTS, new Integer(maxRedirect));
276        params.setParameter(ClientPNames.REJECT_RELATIVE_REDIRECT, Boolean.FALSE);
277        return new DefaultHttpClient(params);
278        }
279
280        private static void setUserAgent(HttpMessage hm, String useragent) {
281        if(useragent!=null)hm.setHeader("User-Agent",useragent);
282        }
283
284        private static void setContentType(HttpMessage hm, String charset) {
285                if(charset!=null) hm.setHeader("Content-type", "text/html; charset="+charset);
286        }
287
288        private static void setHeader(HttpMessage hm,lucee.commons.net.http.Header[] headers) {
289                addHeader(hm, headers);
290        }
291        
292        private static void addHeader(HttpMessage hm,lucee.commons.net.http.Header[] headers) {
293                if(headers!=null) {
294                for(int i=0;i<headers.length;i++)
295                        hm.addHeader(toHeader(headers[i]));
296        }
297        }
298
299        /**
300         * sets the timeout for the connection and socket (same value)
301         */
302        public static void setTimeout(HttpParams params, TimeSpan timeout) {
303        if(timeout!=null && timeout.getMillis()>0){
304                int ms = (int)timeout.getMillis();
305                if(ms<0) ms=Integer.MAX_VALUE;
306                
307                HttpConnectionParams.setConnectionTimeout(params,ms);
308                HttpConnectionParams.setSoTimeout(params, ms);
309        }
310        }
311
312        public static BasicHttpContext setCredentials(DefaultHttpClient client, HttpHost httpHost, String username,String password, boolean preAuth) {
313        // set Username and Password
314        if(!StringUtil.isEmpty(username,true)) {
315            if(password==null)password="";
316            CredentialsProvider cp = client.getCredentialsProvider();
317            cp.setCredentials(
318                new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), 
319                new UsernamePasswordCredentials(username,password));
320            
321            
322            
323            BasicHttpContext httpContext = new BasicHttpContext();
324            if(preAuth) {
325                    AuthCache authCache = new BasicAuthCache();
326                    authCache.put(httpHost, new BasicScheme());
327                    httpContext.setAttribute(ClientContext.AUTH_CACHE, authCache);
328            }
329            return httpContext;
330        }
331        return null;
332        }
333        
334        public static void setNTCredentials(DefaultHttpClient client, String username,String password, String workStation, String domain) {
335        // set Username and Password
336        if(!StringUtil.isEmpty(username,true)) {
337            if(password==null)password="";
338            CredentialsProvider cp = client.getCredentialsProvider();
339            cp.setCredentials(
340                new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), 
341                new NTCredentials(username,password,workStation,domain));
342            //httpMethod.setDoAuthentication( true );
343        }
344        }
345
346    public static void setBody(HttpEntityEnclosingRequest req, Object body, String mimetype,String charset) throws IOException {
347        if(body!=null)req.setEntity(toHttpEntity(body,mimetype,charset));
348        }
349
350        public static void setProxy(DefaultHttpClient client, HttpUriRequest request, ProxyData proxy) {
351                // set Proxy
352        if(ProxyDataImpl.isValid(proxy)) {
353                HttpHost host = new HttpHost(proxy.getServer(), proxy.getPort()==-1?80:proxy.getPort());
354                client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, host);
355            if(!StringUtil.isEmpty(proxy.getUsername())) {
356                
357                client.getCredentialsProvider().setCredentials(
358                        new AuthScope(proxy.getServer(), proxy.getPort()),
359                        new UsernamePasswordCredentials(proxy.getUsername(),proxy.getPassword()));
360            }
361        } 
362        }
363        
364        public static void addCookie(DefaultHttpClient client, String domain, String name, String value, String path, String charset) {
365                if(ReqRspUtil.needEncoding(name,false)) name=ReqRspUtil.encode(name, charset);
366                if(ReqRspUtil.needEncoding(value,false)) value=ReqRspUtil.encode(value, charset);
367                BasicClientCookie cookie = new BasicClientCookie(name, value);
368                if(!StringUtil.isEmpty(domain,true))cookie.setDomain(domain);
369                if(!StringUtil.isEmpty(path,true))cookie.setPath(path);
370                
371                client.getCookieStore().addCookie(cookie);
372        }
373
374        /**
375         * convert input to  HTTP Entity
376         * @param value
377         * @param mimetype not used for binary input
378         * @param charset not used for binary input
379         * @return
380         * @throws IOException
381         */
382        private static HttpEntity toHttpEntity(Object value, String mimetype, String charset) throws IOException {
383                if(value instanceof HttpEntity) return (HttpEntity) value;
384        try{
385                if(value instanceof InputStream) {
386                        return new ByteArrayEntity(IOUtil.toBytes((InputStream)value));
387                        }
388                        else if(Decision.isCastableToBinary(value,false)){
389                                return new ByteArrayEntity(Caster.toBinary(value));
390                        }
391                        else {
392                                return new StringEntity(Caster.toString(value),mimetype,charset);
393                        }
394        }
395        catch(Exception e){
396                throw ExceptionUtil.toIOException(e);
397        }
398    }
399        
400
401        public static Entity getEmptyEntity(String contentType) {
402                return new EmptyHttpEntity(contentType);
403        }
404
405        public static Entity getByteArrayEntity(byte[] barr, String contentType) {
406                return new ByteArrayHttpEntity(barr,contentType);
407        }
408
409        public static Entity getTemporaryStreamEntity(TemporaryStream ts,String contentType) {
410                return new TemporaryStreamHttpEntity(ts,contentType);
411        }
412
413        public static Entity getResourceEntity(Resource res, String contentType) {
414                return new ResourceHttpEntity(res,contentType);
415        }
416
417        /*
418         * this method exist because the method execute is returning a different type depending on the version of the library
419         */
420        public static HttpResponse execute(HttpClient client, HttpUriRequest req, HttpContext context) throws ClientProtocolException, IOException {
421                try {
422                        Method exe = client.getClass().getMethod("execute", new Class[]{HttpUriRequest.class,HttpContext.class});
423                        return (HttpResponse)exe.invoke(client, new Object[]{req,context});
424                }
425                catch (InvocationTargetException ite) {
426                        Throwable t = ite.getTargetException();
427                        if(t instanceof IOException)throw (IOException)t;
428                        if(t instanceof ClientProtocolException)throw (ClientProtocolException)t;
429                        throw new RuntimeException(t);
430                }
431                catch (Exception e) {
432                        throw new RuntimeException(e);
433                }
434        }
435
436        
437}