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.httpclient3;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.net.MalformedURLException;
024import java.net.URL;
025import java.util.Iterator;
026import java.util.Map;
027import java.util.Map.Entry;
028
029import lucee.commons.io.TemporaryStream;
030import lucee.commons.io.res.Resource;
031import lucee.commons.lang.ExceptionUtil;
032import lucee.commons.lang.StringUtil;
033import lucee.commons.net.http.Entity;
034import lucee.commons.net.http.HTTPEngine;
035import lucee.commons.net.http.HTTPResponse;
036import lucee.commons.net.http.Header;
037import lucee.commons.net.http.httpclient3.entity.EmptyRequestEntity;
038import lucee.commons.net.http.httpclient3.entity.ResourceRequestEntity;
039import lucee.commons.net.http.httpclient3.entity.TemporaryStreamRequestEntity;
040import lucee.commons.net.http.httpclient3.entity._ByteArrayRequestEntity;
041import lucee.runtime.exp.PageException;
042import lucee.runtime.net.proxy.ProxyData;
043import lucee.runtime.net.proxy.ProxyDataImpl;
044import lucee.runtime.op.Caster;
045import lucee.runtime.op.Decision;
046import lucee.runtime.type.util.CollectionUtil;
047
048import org.apache.commons.httpclient.HostConfiguration;
049import org.apache.commons.httpclient.HttpClient;
050import org.apache.commons.httpclient.HttpException;
051import org.apache.commons.httpclient.HttpMethod;
052import org.apache.commons.httpclient.HttpState;
053import org.apache.commons.httpclient.NameValuePair;
054import org.apache.commons.httpclient.UsernamePasswordCredentials;
055import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
056import org.apache.commons.httpclient.methods.DeleteMethod;
057import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
058import org.apache.commons.httpclient.methods.GetMethod;
059import org.apache.commons.httpclient.methods.HeadMethod;
060import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
061import org.apache.commons.httpclient.methods.PostMethod;
062import org.apache.commons.httpclient.methods.PutMethod;
063import org.apache.commons.httpclient.methods.RequestEntity;
064import org.apache.commons.httpclient.methods.StringRequestEntity;
065
066/**
067 * 
068 */
069public final class HTTPEngine3Impl {
070        
071
072    
073    public static HTTPResponse get(URL url, String username, String password, long timeout, int maxRedirect,
074        String charset, String useragent,ProxyData proxy, Header[] headers) throws IOException {
075        return _invoke(new GetMethod(url.toExternalForm()), url, username, password, timeout,maxRedirect, charset, useragent, proxy, headers,null,null);
076    }
077    
078    public static HTTPResponse head(URL url, String username, String password, int timeout, int maxRedirect,
079        String charset, String useragent,ProxyData proxy, Header[] headers) throws IOException {
080                return _invoke(new HeadMethod(url.toExternalForm()), url, username, password, timeout,maxRedirect, charset, useragent, proxy, headers,null,null);
081        }
082    
083    public static HTTPResponse post(URL url, String username, String password, long timeout, int maxRedirect,
084        String charset, String useragent, ProxyData proxy, Header[] headers, Map<String,String> params) throws IOException {
085        return _invoke(new PostMethod(url.toExternalForm()), url, username, password, timeout,maxRedirect, charset, useragent, proxy, headers,params,null);
086    }
087    
088        public static HTTPResponse put(URL url, String username, String password, int timeout, int maxRedirect,
089        String charset, String useragent,ProxyData proxy, Header[] headers, Object body) throws IOException {
090                return _invoke(new PutMethod(url.toExternalForm()), url, username, password, timeout,maxRedirect, charset, useragent, proxy, headers,null,body);     
091        }
092    
093    public static HTTPResponse delete(URL url, String username, String password, int timeout, int maxRedirect,
094        String charset, String useragent,ProxyData proxy, Header[] headers) throws IOException {
095        return _invoke(new DeleteMethod(url.toExternalForm()), url, username, password, timeout,maxRedirect, charset, useragent, proxy, headers,null,null);
096        }
097    
098
099        private static HTTPResponse _invoke(HttpMethod httpMethod, URL url, String username, String password, long timeout, int maxRedirect,
100            String charset, String useragent, ProxyData proxy, Header[] headers, Map<String,String> params, Object body) throws IOException {
101
102        HttpClient client = new HttpClient();
103        HostConfiguration config = client.getHostConfiguration();
104        HttpState state = client.getState();
105        
106        setHeader(httpMethod,headers);
107        if(CollectionUtil.isEmpty(params))setContentType(httpMethod,charset);
108        setUserAgent(httpMethod,useragent);
109        setTimeout(client,timeout);
110        setParams(httpMethod,params);
111        setCredentials(client,httpMethod,username,password);  
112        setProxy(config,state,proxy);
113        if(body!=null && httpMethod instanceof EntityEnclosingMethod)setBody((EntityEnclosingMethod)httpMethod,body);
114        return new HTTPResponse3Impl(execute(client,httpMethod,maxRedirect),url);
115    }
116
117        /**
118     * Execute a HTTTP Client and follow redirect over different hosts
119     * @param client
120     * @param method
121     * @param doRedirect
122     * @return
123     * @throws IOException
124     * @throws HttpException
125     */
126    public static HttpMethod execute(HttpClient client, HttpMethod method, int maxRedirect) throws HttpException, IOException {
127        short count=0;
128        method.setFollowRedirects(false);
129        
130        while(isRedirect(client.executeMethod(method)) && count++ < maxRedirect) {
131                method=rewrite(method);
132        }
133        return method;
134    }
135
136    /**
137     * rewrite request method
138     * @param method
139     * @return
140     * @throws MalformedURLException
141     */
142    private static HttpMethod rewrite(HttpMethod method) throws MalformedURLException {
143        org.apache.commons.httpclient.Header location = method.getResponseHeader("location");
144        if(location==null) return method;
145
146        HostConfiguration config = method.getHostConfiguration();
147        URL url;
148        try {
149            url = new URL(location.getValue());
150        } 
151        catch (MalformedURLException e) {
152            
153            url=new URL(config.getProtocol().getScheme(),
154                    config.getHost(),
155                    config.getPort(),
156                    mergePath(method.getPath(),location.getValue()));
157        }
158        
159        method= clone(method,url);
160        
161        return method;
162    }
163
164    /**
165     * FUNKTIONIERT NICHT, HOST WIRD NICHT UEBERNOMMEN
166     * Clones a http method and sets a new url
167     * @param src
168     * @param url
169     * @return
170     */
171    private static HttpMethod clone(HttpMethod src, URL url) {
172        HttpMethod trg = HttpMethodCloner.clone(src);
173        HostConfiguration trgConfig = trg.getHostConfiguration();
174        trgConfig.setHost(url.getHost(),url.getPort(),url.getProtocol());
175        trg.setPath(url.getPath());
176        trg.setQueryString(url.getQuery());
177        
178        return trg;
179    }
180    
181    /**
182     * merge to pathes to one
183     * @param current
184     * @param relPath
185     * @return
186     * @throws MalformedURLException
187     */
188    private static String mergePath(String current, String relPath) throws MalformedURLException {
189        
190        // get current directory
191        String currDir;
192        if(current==null || current.indexOf('/')==-1)currDir="/";
193        else if(current.endsWith("/"))currDir=current;
194        else currDir=current.substring(0,current.lastIndexOf('/')+1);
195        
196        // merge together
197        String path;
198        if(relPath.startsWith("./"))path=currDir+relPath.substring(2);
199        else if(relPath.startsWith("/"))path=relPath;
200        else if(!relPath.startsWith("../"))path=currDir+relPath;
201        else {
202            while(relPath.startsWith("../") || currDir.length()==0) {
203                relPath=relPath.substring(3);
204                currDir=currDir.substring(0,currDir.length()-1);
205                int index = currDir.lastIndexOf('/');
206                if(index==-1)throw new MalformedURLException("invalid relpath definition for URL");
207                currDir=currDir.substring(0,index+1);
208            }
209            path=currDir+relPath;
210        }
211        
212        return path;
213    }   
214
215    /**
216     * checks if status code is a redirect
217     * @param status
218     * @return is redirect
219     */
220    private static boolean isRedirect(int status) {
221        return 
222                status==HTTPEngine.STATUS_REDIRECT_FOUND || 
223                status==HTTPEngine.STATUS_REDIRECT_MOVED_PERMANENTLY ||
224                status==HTTPEngine.STATUS_REDIRECT_SEE_OTHER;
225    }
226
227    
228
229    private static void setBody(EntityEnclosingMethod httpMethod, Object body) throws IOException {
230        if(body!=null)
231                        try {
232                                httpMethod.setRequestEntity(toRequestEntity(body));
233                        } catch (PageException e) {
234                                throw ExceptionUtil.toIOException(e);
235                        }
236        }
237
238        private static void setProxy(HostConfiguration config, HttpState state, ProxyData data) {
239        // set Proxy
240            if(ProxyDataImpl.isValid(data)) {
241                config.setProxy(data.getServer(),data.getPort()<=0?80:data.getPort());
242                if(ProxyDataImpl.hasCredentials(data)) {
243                    state.setProxyCredentials(null,null,new UsernamePasswordCredentials(data.getUsername(),StringUtil.emptyIfNull(data.getPassword())));
244                }
245            } 
246        }
247
248        private static void setCredentials(HttpClient client, HttpMethod httpMethod, String username,String password) {
249        // set Username and Password
250            if(username!=null) {
251                if(password==null)password="";
252                client.getState().setCredentials(null,null,new UsernamePasswordCredentials(username, password));
253                httpMethod.setDoAuthentication( true );
254            }
255        }
256
257        private static void setTimeout(HttpClient client, long timeout) {
258        if(timeout>0){
259                client.setConnectionTimeout((int)timeout);
260                client.setTimeout((int)timeout);
261        }
262        }
263
264        private static void setUserAgent(HttpMethod httpMethod, String useragent) {
265        if(useragent!=null)httpMethod.setRequestHeader("User-Agent",useragent);
266        }
267
268        private static void setContentType(HttpMethod httpMethod, String charset) {
269        if(charset!=null)httpMethod.addRequestHeader("Content-type", "text/html; charset="+charset );
270        }
271
272        private static void setHeader(HttpMethod httpMethod,Header[] headers) {
273        if(headers!=null) {
274                for(int i=0;i<headers.length;i++)
275                        httpMethod.addRequestHeader(headers[i].getName(), headers[i].getValue());
276        }
277        }
278        
279    private static void setParams(HttpMethod httpMethod, Map<String, String> params) {
280                if(params==null || !(httpMethod instanceof PostMethod)) return;
281        PostMethod post=(PostMethod) httpMethod;
282        Iterator<Entry<String, String>> it = params.entrySet().iterator();
283        Entry<String, String> e;
284        while(it.hasNext()){
285                e = it.next();
286                post.addParameter(new NameValuePair(e.getKey(),e.getValue()));
287        }
288        }
289
290        private static RequestEntity toRequestEntity(Object value) throws PageException {
291        if(value instanceof RequestEntity) return (RequestEntity) value;
292        
293        else if(value instanceof InputStream) {
294                        return new InputStreamRequestEntity((InputStream)value,"application/octet-stream");
295                }
296                else if(Decision.isCastableToBinary(value,false)){
297                        return new ByteArrayRequestEntity(Caster.toBinary(value));
298                }
299                else {
300                        return new StringRequestEntity(Caster.toString(value));
301                }
302    }
303
304        public static Header header(String name, String value) {
305                return new HeaderImpl(name, value);
306        }
307
308        public static Entity getEmptyEntity(String contentType) {
309                return new EmptyRequestEntity(contentType);
310        }
311                
312        public static Entity getByteArrayEntity(byte[] barr, String contentType) {
313                return new _ByteArrayRequestEntity(barr, contentType);
314        }
315        
316        public static Entity getTemporaryStreamEntity(TemporaryStream ts, String contentType) {
317                return new TemporaryStreamRequestEntity(ts,contentType);
318        }
319        
320        public static Entity getResourceEntity(Resource res, String contentType) {
321                return new ResourceRequestEntity(res,contentType);
322        }
323}