/**
*
* Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
**/
package lucee.commons.net.http.httpclient;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import lucee.commons.io.IOUtil;
import lucee.commons.io.TemporaryStream;
import lucee.commons.io.res.Resource;
import lucee.commons.io.res.util.ResourceUtil;
import lucee.commons.lang.ExceptionUtil;
import lucee.commons.lang.StringUtil;
import lucee.commons.net.http.Entity;
import lucee.commons.net.http.HTTPEngine;
import lucee.commons.net.http.HTTPResponse;
import lucee.commons.net.http.httpclient.entity.ByteArrayHttpEntity;
import lucee.commons.net.http.httpclient.entity.EmptyHttpEntity;
import lucee.commons.net.http.httpclient.entity.ResourceHttpEntity;
import lucee.commons.net.http.httpclient.entity.TemporaryStreamHttpEntity;
import lucee.runtime.PageContext;
import lucee.runtime.PageContextImpl;
import lucee.runtime.engine.ThreadLocalPageContext;
import lucee.runtime.exp.PageException;
import lucee.runtime.net.http.ReqRspUtil;
import lucee.runtime.net.proxy.ProxyData;
import lucee.runtime.net.proxy.ProxyDataImpl;
import lucee.runtime.op.Caster;
import lucee.runtime.op.Decision;
import lucee.runtime.tag.Http;
import lucee.runtime.type.dt.TimeSpanImpl;
import lucee.runtime.type.util.CollectionUtil;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
import org.apache.http.HttpMessage;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.NTCredentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.CookieStore;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
public class HTTPEngine4Impl {
/**
* does a http get request
* @param url
* @param username
* @param password
* @param timeout
* @param charset
* @param useragent
* @param proxyserver
* @param proxyport
* @param proxyuser
* @param proxypassword
* @param headers
* @return
* @throws IOException
*/
public static HTTPResponse get(URL url, String username,String password, long timeout, boolean redirect,
String charset, String useragent,
ProxyData proxy, lucee.commons.net.http.Header[] headers) throws IOException {
HttpGet get = new HttpGet(url.toExternalForm());
return _invoke(url,get, username, password, timeout, redirect, charset, useragent, proxy, headers,null);
}
/**
* does a http post request
* @param url
* @param username
* @param password
* @param timeout
* @param charset
* @param useragent
* @param proxyserver
* @param proxyport
* @param proxyuser
* @param proxypassword
* @param headers
* @return
* @throws IOException
*/
public static HTTPResponse post(URL url, String username,String password, long timeout, boolean redirect,
String charset, String useragent,
ProxyData proxy, lucee.commons.net.http.Header[] headers) throws IOException {
HttpPost post = new HttpPost(url.toExternalForm());
return _invoke(url,post, username, password, timeout, redirect, charset, useragent, proxy, headers,null);
}
public static HTTPResponse post(URL url, String username,String password, long timeout, boolean redirect,
String charset, String useragent,
ProxyData proxy, lucee.commons.net.http.Header[] headers,Map<String,String> formfields) throws IOException {
HttpPost post = new HttpPost(url.toExternalForm());
return _invoke(url,post, username, password, timeout, redirect, charset, useragent, proxy, headers,formfields);
}
/**
* does a http put request
* @param url
* @param username
* @param password
* @param timeout
* @param charset
* @param useragent
* @param proxyserver
* @param proxyport
* @param proxyuser
* @param proxypassword
* @param headers
* @param body
* @return
* @throws IOException
* @throws PageException
*/
public static HTTPResponse put(URL url, String username,String password, long timeout, boolean redirect,
String mimetype,String charset, String useragent,
ProxyData proxy, lucee.commons.net.http.Header[] headers, Object body) throws IOException {
HttpPut put= new HttpPut(url.toExternalForm());
setBody(put,body,mimetype,charset);
return _invoke(url,put, username, password, timeout, redirect, charset, useragent, proxy, headers,null);
}
/**
* does a http delete request
* @param url
* @param username
* @param password
* @param timeout
* @param charset
* @param useragent
* @param proxyserver
* @param proxyport
* @param proxyuser
* @param proxypassword
* @param headers
* @return
* @throws IOException
*/
public static HTTPResponse delete(URL url, String username,String password, long timeout, boolean redirect,
String charset, String useragent,
ProxyData proxy, lucee.commons.net.http.Header[] headers) throws IOException {
HttpDelete delete= new HttpDelete(url.toExternalForm());
return _invoke(url,delete, username, password, timeout, redirect, charset, useragent, proxy, headers,null);
}
/**
* does a http head request
* @param url
* @param username
* @param password
* @param timeout
* @param charset
* @param useragent
* @param proxyserver
* @param proxyport
* @param proxyuser
* @param proxypassword
* @param headers
* @return
* @throws IOException
*/
public static HTTPResponse head(URL url, String username,String password, long timeout, boolean redirect,
String charset, String useragent,
ProxyData proxy, lucee.commons.net.http.Header[] headers) throws IOException {
HttpHead head= new HttpHead(url.toExternalForm());
return _invoke(url,head, username, password, timeout, redirect, charset, useragent, proxy, headers,null);
}
public static lucee.commons.net.http.Header header(String name, String value) {
return new HeaderImpl(name, value);
}
private static Header toHeader(lucee.commons.net.http.Header header) {
if(header instanceof Header) return (Header) header;
if(header instanceof HeaderWrap) return ((HeaderWrap)header).header;
return new HeaderImpl(header.getName(), header.getValue());
}
private static HTTPResponse _invoke(URL url,HttpUriRequest request,String username,String password, long timeout, boolean redirect,
String charset, String useragent,
ProxyData proxy, lucee.commons.net.http.Header[] headers, Map<String,String> formfields) throws IOException {
HttpClientBuilder builder = HttpClients.custom();
// redirect
if(redirect) builder.setRedirectStrategy(new DefaultRedirectStrategy());
else builder.disableRedirectHandling();
HttpHost hh=new HttpHost(url.getHost(),url.getPort());
setHeader(request,headers);
if(CollectionUtil.isEmpty(formfields))setContentType(request,charset);
setFormFields(request,formfields,charset);
setUserAgent(request,useragent);
if(timeout>0)Http.setTimeout(builder,TimeSpanImpl.fromMillis(timeout));
HttpContext context=setCredentials(builder,hh, username, password,false);
setProxy(builder,request,proxy);
CloseableHttpClient client = builder.build();
if(context==null)context = new BasicHttpContext();
return new HTTPResponse4Impl(url,context,request,client.execute(request,context));
}
private static void setFormFields(HttpUriRequest request, Map<String, String> formfields, String charset) throws IOException {
if(!CollectionUtil.isEmpty(formfields)) {
if(!(request instanceof HttpPost)) throw new IOException("form fields are only suppported for post request");
HttpPost post=(HttpPost) request;
List<NameValuePair> list = new ArrayList<NameValuePair>();
Iterator<Entry<String, String>> it = formfields.entrySet().iterator();
Entry<String, String> e;
while(it.hasNext()){
e = it.next();
list.add(new BasicNameValuePair(e.getKey(),e.getValue()));
}
if(StringUtil.isEmpty(charset)) charset=((PageContextImpl)ThreadLocalPageContext.get()).getWebCharset().name();
post.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(list,charset));
}
}
private static void setUserAgent(HttpMessage hm, String useragent) {
if(useragent!=null)hm.setHeader("User-Agent",useragent);
}
private static void setContentType(HttpMessage hm, String charset) {
if(charset!=null) hm.setHeader("Content-type", "text/html; charset="+charset);
}
private static void setHeader(HttpMessage hm,lucee.commons.net.http.Header[] headers) {
addHeader(hm, headers);
}
private static void addHeader(HttpMessage hm,lucee.commons.net.http.Header[] headers) {
if(headers!=null) {
for(int i=0;i<headers.length;i++)
hm.addHeader(toHeader(headers[i]));
}
}
public static BasicHttpContext setCredentials(HttpClientBuilder builder, HttpHost httpHost, String username,String password, boolean preAuth) {
// set Username and Password
if(!StringUtil.isEmpty(username,true)) {
if(password==null)password="";
CredentialsProvider cp = new BasicCredentialsProvider();
builder.setDefaultCredentialsProvider(cp);
cp.setCredentials(
new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT),
new UsernamePasswordCredentials(username,password));
BasicHttpContext httpContext = new BasicHttpContext();
if(preAuth) {
AuthCache authCache = new BasicAuthCache();
authCache.put(httpHost, new BasicScheme());
httpContext.setAttribute(ClientContext.AUTH_CACHE, authCache);
}
return httpContext;
}
return null;
}
public static void setNTCredentials(HttpClientBuilder builder, String username,String password, String workStation, String domain) {
// set Username and Password
if(!StringUtil.isEmpty(username,true)) {
if(password==null)password="";
CredentialsProvider cp = new BasicCredentialsProvider();
builder.setDefaultCredentialsProvider(cp);
cp.setCredentials(
new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT),
new NTCredentials(username,password,workStation,domain));
}
}
public static void setClientSSL(PageContext pc, HttpClientBuilder builder, String clientCert, String clientCertPassword) {
if(!StringUtil.isEmpty(clientCert,true)) {
if(clientCertPassword==null)clientCertPassword="";
try {
Resource ks = ResourceUtil.toResourceExisting(pc, clientCert, pc.getConfig().allowRealPath());
pc.getConfig().getSecurityManager().checkFileLocation(ks);
KeyStore clientStore = KeyStore.getInstance("PKCS12");
clientStore.load(ks.getInputStream(), clientCertPassword.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientStore, clientCertPassword.toCharArray());
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(kmf.getKeyManagers(), null, null);//new SecureRandom()
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslContext,
new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" },
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
builder.setSSLSocketFactory(sslsf);
} catch(Exception e) {}
}
}
public static void setBody(HttpEntityEnclosingRequest req, Object body, String mimetype,String charset) throws IOException {
if(body!=null)req.setEntity(toHttpEntity(body,mimetype,charset));
}
public static void setProxy(HttpClientBuilder builder, HttpUriRequest request, ProxyData proxy) {
// set Proxy
if(ProxyDataImpl.isValid(proxy)) {
HttpHost host = new HttpHost(proxy.getServer(), proxy.getPort()==-1?80:proxy.getPort());
builder.setProxy(host);
// username/password
if(!StringUtil.isEmpty(proxy.getUsername())) {
CredentialsProvider cp = new BasicCredentialsProvider();
builder.setDefaultCredentialsProvider(cp);
cp.setCredentials(
new AuthScope(proxy.getServer(), proxy.getPort()),
new UsernamePasswordCredentials(proxy.getUsername(),proxy.getPassword()));
}
}
}
public static void addCookie(CookieStore cookieStore, String domain, String name, String value, String path, String charset) {
if(ReqRspUtil.needEncoding(name,false)) name=ReqRspUtil.encode(name, charset);
if(ReqRspUtil.needEncoding(value,false)) value=ReqRspUtil.encode(value, charset);
BasicClientCookie cookie = new BasicClientCookie(name, value);
if(!StringUtil.isEmpty(domain,true))cookie.setDomain(domain);
if(!StringUtil.isEmpty(path,true))cookie.setPath(path);
cookieStore.addCookie(cookie);
}
/**
* convert input to HTTP Entity
* @param value
* @param mimetype not used for binary input
* @param charset not used for binary input
* @return
* @throws IOException
*/
private static HttpEntity toHttpEntity(Object value, String mimetype, String charset) throws IOException {
if(value instanceof HttpEntity) return (HttpEntity) value;
// content type
ContentType ct=HTTPEngine.toContentType(mimetype, charset);
try{
if(value instanceof TemporaryStream) {
if(ct!=null)
return new TemporaryStreamHttpEntity((TemporaryStream)value,ct);
return new TemporaryStreamHttpEntity((TemporaryStream)value,null);
}
else if(value instanceof InputStream) {
if(ct!=null)
return new ByteArrayEntity(IOUtil.toBytes((InputStream)value),ct);
return new ByteArrayEntity(IOUtil.toBytes((InputStream)value));
}
else if(Decision.isCastableToBinary(value,false)){
if(ct!=null)
return new ByteArrayEntity(Caster.toBinary(value),ct);
return new ByteArrayEntity(Caster.toBinary(value));
}
else {
boolean wasNull=false;
if(ct==null) {
wasNull=true;
ct=ContentType.APPLICATION_OCTET_STREAM;
}
String str = Caster.toString(value);
if(str.equals("<empty>")) {
return new EmptyHttpEntity(ct);
}
if(wasNull && !StringUtil.isEmpty(charset,true))
return new StringEntity(str,charset.trim());
else return new StringEntity(str,ct);
}
}
catch(Exception e){
throw ExceptionUtil.toIOException(e);
}
}
public static Entity getEmptyEntity(ContentType contentType) {
return new EmptyHttpEntity(contentType);
}
public static Entity getByteArrayEntity(byte[] barr, ContentType contentType) {
return new ByteArrayHttpEntity(barr,contentType);
}
public static Entity getTemporaryStreamEntity(TemporaryStream ts,ContentType contentType) {
return new TemporaryStreamHttpEntity(ts,contentType);
}
public static Entity getResourceEntity(Resource res, ContentType contentType) {
return new ResourceHttpEntity(res,contentType);
}
}