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.net.URL;
024import java.util.ArrayList;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028import java.util.Map.Entry;
029
030import lucee.commons.io.IOUtil;
031import lucee.commons.io.TemporaryStream;
032import lucee.commons.io.res.Resource;
033import lucee.commons.lang.ExceptionUtil;
034import lucee.commons.lang.StringUtil;
035import lucee.commons.net.http.Entity;
036import lucee.commons.net.http.HTTPResponse;
037import lucee.commons.net.http.httpclient4.entity.ByteArrayHttpEntity;
038import lucee.commons.net.http.httpclient4.entity.EmptyHttpEntity;
039import lucee.commons.net.http.httpclient4.entity.ResourceHttpEntity;
040import lucee.commons.net.http.httpclient4.entity.TemporaryStreamHttpEntity;
041import lucee.runtime.PageContextImpl;
042import lucee.runtime.engine.ThreadLocalPageContext;
043import lucee.runtime.exp.PageException;
044import lucee.runtime.net.http.ReqRspUtil;
045import lucee.runtime.net.proxy.ProxyData;
046import lucee.runtime.net.proxy.ProxyDataImpl;
047import lucee.runtime.op.Caster;
048import lucee.runtime.op.Decision;
049import lucee.runtime.tag.Http41;
050import lucee.runtime.type.dt.TimeSpanImpl;
051import lucee.runtime.type.util.CollectionUtil;
052
053import org.apache.http.Header;
054import org.apache.http.HttpEntity;
055import org.apache.http.HttpEntityEnclosingRequest;
056import org.apache.http.HttpHost;
057import org.apache.http.HttpMessage;
058import org.apache.http.NameValuePair;
059import org.apache.http.auth.AuthScope;
060import org.apache.http.auth.NTCredentials;
061import org.apache.http.auth.UsernamePasswordCredentials;
062import org.apache.http.client.AuthCache;
063import org.apache.http.client.CookieStore;
064import org.apache.http.client.CredentialsProvider;
065import org.apache.http.client.methods.HttpDelete;
066import org.apache.http.client.methods.HttpGet;
067import org.apache.http.client.methods.HttpHead;
068import org.apache.http.client.methods.HttpPost;
069import org.apache.http.client.methods.HttpPut;
070import org.apache.http.client.methods.HttpUriRequest;
071import org.apache.http.client.protocol.ClientContext;
072import org.apache.http.entity.ByteArrayEntity;
073import org.apache.http.entity.StringEntity;
074import org.apache.http.impl.auth.BasicScheme;
075import org.apache.http.impl.client.BasicAuthCache;
076import org.apache.http.impl.client.BasicCredentialsProvider;
077import org.apache.http.impl.client.CloseableHttpClient;
078import org.apache.http.impl.client.DefaultRedirectStrategy;
079import org.apache.http.impl.client.HttpClientBuilder;
080import org.apache.http.impl.client.HttpClients;
081import org.apache.http.impl.cookie.BasicClientCookie;
082import org.apache.http.message.BasicNameValuePair;
083import org.apache.http.protocol.BasicHttpContext;
084import org.apache.http.protocol.HttpContext;
085
086public class HTTPEngineImpl {
087        
088        /**
089         * does a http get request
090         * @param url
091         * @param username
092         * @param password
093         * @param timeout
094         * @param charset
095         * @param useragent
096         * @param proxyserver
097         * @param proxyport
098         * @param proxyuser
099         * @param proxypassword
100         * @param headers
101         * @return
102         * @throws IOException
103         */
104        public static HTTPResponse get(URL url, String username,String password, long timeout,  boolean redirect,
105            String charset, String useragent,
106            ProxyData proxy, lucee.commons.net.http.Header[] headers) throws IOException {
107                HttpGet get = new HttpGet(url.toExternalForm());
108                return _invoke(url,get, username, password, timeout, redirect, charset, useragent, proxy, headers,null);
109        }
110
111    /**
112         * does a http post request
113     * @param url
114     * @param username
115     * @param password
116     * @param timeout
117     * @param charset
118     * @param useragent
119     * @param proxyserver
120     * @param proxyport
121     * @param proxyuser
122     * @param proxypassword
123     * @param headers
124     * @return
125     * @throws IOException
126     */
127    public static HTTPResponse post(URL url, String username,String password, long timeout,  boolean redirect,
128            String charset, String useragent,
129            ProxyData proxy, lucee.commons.net.http.Header[] headers) throws IOException {
130        HttpPost post = new HttpPost(url.toExternalForm());
131        return _invoke(url,post, username, password, timeout, redirect, charset, useragent, proxy, headers,null);
132    }
133    
134
135    
136
137    public static HTTPResponse post(URL url, String username,String password, long timeout, boolean redirect,
138            String charset, String useragent,
139            ProxyData proxy, lucee.commons.net.http.Header[] headers,Map<String,String> formfields) throws IOException {
140        HttpPost post = new HttpPost(url.toExternalForm());
141        
142        return _invoke(url,post, username, password, timeout, redirect, charset, useragent, proxy, headers,formfields);
143    }
144    
145    
146    /**
147         * does a http put request
148     * @param url
149     * @param username
150     * @param password
151     * @param timeout
152     * @param charset
153     * @param useragent
154     * @param proxyserver
155     * @param proxyport
156     * @param proxyuser
157     * @param proxypassword
158     * @param headers
159     * @param body
160     * @return
161     * @throws IOException
162     * @throws PageException 
163     */
164    public static HTTPResponse put(URL url, String username,String password, long timeout, boolean redirect,
165                String mimetype,String charset, String useragent,
166            ProxyData proxy, lucee.commons.net.http.Header[] headers, Object body) throws IOException {
167                HttpPut put= new HttpPut(url.toExternalForm());
168                setBody(put,body,mimetype,charset);
169        return _invoke(url,put, username, password, timeout, redirect, charset, useragent, proxy, headers,null);
170                 
171        }
172    
173    /**
174         * does a http delete request
175     * @param url
176     * @param username
177     * @param password
178     * @param timeout
179     * @param charset
180     * @param useragent
181     * @param proxyserver
182     * @param proxyport
183     * @param proxyuser
184     * @param proxypassword
185     * @param headers
186     * @return
187     * @throws IOException
188     */
189    public static HTTPResponse delete(URL url, String username,String password, long timeout, boolean redirect,
190            String charset, String useragent,
191            ProxyData proxy, lucee.commons.net.http.Header[] headers) throws IOException {
192        HttpDelete delete= new HttpDelete(url.toExternalForm());
193                return _invoke(url,delete, username, password, timeout, redirect, charset, useragent, proxy, headers,null);    
194        }
195
196    /**
197         * does a http head request
198     * @param url
199     * @param username
200     * @param password
201     * @param timeout
202     * @param charset
203     * @param useragent
204     * @param proxyserver
205     * @param proxyport
206     * @param proxyuser
207     * @param proxypassword
208     * @param headers
209     * @return
210     * @throws IOException
211     */
212    public static HTTPResponse head(URL url, String username,String password, long timeout, boolean redirect,
213            String charset, String useragent,
214            ProxyData proxy, lucee.commons.net.http.Header[] headers) throws IOException {
215        HttpHead head= new HttpHead(url.toExternalForm());
216                return _invoke(url,head, username, password, timeout, redirect, charset, useragent, proxy, headers,null);    
217        }
218    
219        
220
221        public static lucee.commons.net.http.Header header(String name, String value) {
222                return new HeaderImpl(name, value);
223        }
224        
225
226        private static Header toHeader(lucee.commons.net.http.Header header) {
227                if(header instanceof Header) return (Header) header;
228                if(header instanceof HeaderWrap) return ((HeaderWrap)header).header;
229                return new HeaderImpl(header.getName(), header.getValue());
230        }
231        
232        private static HTTPResponse _invoke(URL url,HttpUriRequest request,String username,String password, long timeout, boolean redirect,
233            String charset, String useragent,
234            ProxyData proxy, lucee.commons.net.http.Header[] headers, Map<String,String> formfields) throws IOException {
235        
236                HttpClientBuilder builder = HttpClients.custom();
237        
238        // redirect
239        if(redirect)  builder.setRedirectStrategy(new DefaultRedirectStrategy());
240        else builder.disableRedirectHandling();
241        
242        HttpHost hh=new HttpHost(url.getHost(),url.getPort());
243        setHeader(request,headers);
244        if(CollectionUtil.isEmpty(formfields))setContentType(request,charset);
245        setFormFields(request,formfields,charset);
246        setUserAgent(request,useragent);
247        if(timeout>0)Http41.setTimeout(builder,TimeSpanImpl.fromMillis(timeout));
248        HttpContext context=setCredentials(builder,hh, username, password,false);  
249        setProxy(builder,request,proxy);
250        CloseableHttpClient client = builder.build();
251        if(context==null)context = new BasicHttpContext();
252        return new HTTPResponse4Impl(url,context,request,client.execute(request,context));
253    }
254        
255        private static void setFormFields(HttpUriRequest request, Map<String, String> formfields, String charset) throws IOException {
256                if(!CollectionUtil.isEmpty(formfields)) {
257                        if(!(request instanceof HttpPost)) throw new IOException("form fields are only suppported for post request");
258                        HttpPost post=(HttpPost) request;
259                List<NameValuePair> list = new ArrayList<NameValuePair>();
260                Iterator<Entry<String, String>> it = formfields.entrySet().iterator();
261                Entry<String, String> e;
262                while(it.hasNext()){
263                        e = it.next();
264                        list.add(new BasicNameValuePair(e.getKey(),e.getValue()));
265                }
266                if(StringUtil.isEmpty(charset)) charset=((PageContextImpl)ThreadLocalPageContext.get()).getWebCharset().name();
267                
268                post.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(list,charset));
269        }
270        }
271        
272        
273        
274
275        private static void setUserAgent(HttpMessage hm, String useragent) {
276        if(useragent!=null)hm.setHeader("User-Agent",useragent);
277        }
278
279        private static void setContentType(HttpMessage hm, String charset) {
280                if(charset!=null) hm.setHeader("Content-type", "text/html; charset="+charset);
281        }
282
283        private static void setHeader(HttpMessage hm,lucee.commons.net.http.Header[] headers) {
284                addHeader(hm, headers);
285        }
286        
287        private static void addHeader(HttpMessage hm,lucee.commons.net.http.Header[] headers) {
288                if(headers!=null) {
289                for(int i=0;i<headers.length;i++)
290                        hm.addHeader(toHeader(headers[i]));
291        }
292        }
293
294
295        public static BasicHttpContext setCredentials(HttpClientBuilder builder, HttpHost httpHost, String username,String password, boolean preAuth) {
296        // set Username and Password
297        if(!StringUtil.isEmpty(username,true)) {
298            if(password==null)password="";
299            CredentialsProvider cp = new BasicCredentialsProvider();
300            builder.setDefaultCredentialsProvider(cp);
301            
302            cp.setCredentials(
303                new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), 
304                new UsernamePasswordCredentials(username,password));
305            
306            
307            
308            BasicHttpContext httpContext = new BasicHttpContext();
309            if(preAuth) {
310                    AuthCache authCache = new BasicAuthCache();
311                    authCache.put(httpHost, new BasicScheme());
312                    httpContext.setAttribute(ClientContext.AUTH_CACHE, authCache);
313            }
314            return httpContext;
315        }
316        return null;
317        }
318        
319        public static void setNTCredentials(HttpClientBuilder builder, String username,String password, String workStation, String domain) {
320        // set Username and Password
321        if(!StringUtil.isEmpty(username,true)) {
322            if(password==null)password="";
323            CredentialsProvider cp = new BasicCredentialsProvider();
324            builder.setDefaultCredentialsProvider(cp);
325            
326            cp.setCredentials(
327                new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), 
328                new NTCredentials(username,password,workStation,domain));
329        }
330        }
331
332    public static void setBody(HttpEntityEnclosingRequest req, Object body, String mimetype,String charset) throws IOException {
333        if(body!=null)req.setEntity(toHttpEntity(body,mimetype,charset));
334        }
335
336        public static void setProxy(HttpClientBuilder builder, HttpUriRequest request, ProxyData proxy) {
337                // set Proxy
338        if(ProxyDataImpl.isValid(proxy)) {
339                HttpHost host = new HttpHost(proxy.getServer(), proxy.getPort()==-1?80:proxy.getPort());
340                builder.setProxy(host);
341                
342                // username/password
343                if(!StringUtil.isEmpty(proxy.getUsername())) {   
344                        CredentialsProvider cp = new BasicCredentialsProvider();
345                builder.setDefaultCredentialsProvider(cp);
346                cp.setCredentials(
347                        new AuthScope(proxy.getServer(), proxy.getPort()),
348                        new UsernamePasswordCredentials(proxy.getUsername(),proxy.getPassword()));
349            }
350        } 
351        }
352        
353        public static void addCookie(CookieStore cookieStore, String domain, String name, String value, String path, String charset) {
354                if(ReqRspUtil.needEncoding(name,false)) name=ReqRspUtil.encode(name, charset);
355                if(ReqRspUtil.needEncoding(value,false)) value=ReqRspUtil.encode(value, charset);
356                BasicClientCookie cookie=new BasicClientCookie(name, value);
357                if(!StringUtil.isEmpty(domain,true))cookie.setDomain(domain);
358                if(!StringUtil.isEmpty(path,true))cookie.setPath(path);
359                cookieStore.addCookie(cookie);
360        }
361
362        /**
363         * convert input to  HTTP Entity
364         * @param value
365         * @param mimetype not used for binary input
366         * @param charset not used for binary input
367         * @return
368         * @throws IOException
369         */
370        private static HttpEntity toHttpEntity(Object value, String mimetype, String charset) throws IOException {
371                if(value instanceof HttpEntity) return (HttpEntity) value;
372        try{
373                if(value instanceof InputStream) {
374                        return new ByteArrayEntity(IOUtil.toBytes((InputStream)value));
375                        }
376                        else if(Decision.isCastableToBinary(value,false)){
377                                return new ByteArrayEntity(Caster.toBinary(value));
378                        }
379                        else {
380                                return new StringEntity(Caster.toString(value),mimetype,charset);
381                        }
382        }
383        catch(Exception e){
384                throw ExceptionUtil.toIOException(e);
385        }
386    }
387        
388
389        public static Entity getEmptyEntity(String contentType) {
390                return new EmptyHttpEntity(contentType);
391        }
392
393        public static Entity getByteArrayEntity(byte[] barr, String contentType) {
394                return new ByteArrayHttpEntity(barr,contentType);
395        }
396
397        public static Entity getTemporaryStreamEntity(TemporaryStream ts,String contentType) {
398                return new TemporaryStreamHttpEntity(ts,contentType);
399        }
400
401        public static Entity getResourceEntity(Resource res, String contentType) {
402                return new ResourceHttpEntity(res,contentType);
403        }
404
405        
406}