From 11fd62dba4d4e75f17a128604d8578b39c6e223f Mon Sep 17 00:00:00 2001 From: Damian Minkov Date: Wed, 27 Nov 2013 11:19:47 +0200 Subject: [PATCH] Fixes handling http redirect and handling it in provisioning service when we need to fill parameters. --- .../provisioning/ProvisioningServiceImpl.java | 95 ++++++++- .../provisioning/provisioning.manifest.mf | 1 + .../service/httputil/HttpUtils.java | 186 +++++++++++++++--- 3 files changed, 252 insertions(+), 30 deletions(-) diff --git a/src/net/java/sip/communicator/plugin/provisioning/ProvisioningServiceImpl.java b/src/net/java/sip/communicator/plugin/provisioning/ProvisioningServiceImpl.java index dc711c9ad..6bdef324c 100644 --- a/src/net/java/sip/communicator/plugin/provisioning/ProvisioningServiceImpl.java +++ b/src/net/java/sip/communicator/plugin/provisioning/ProvisioningServiceImpl.java @@ -15,6 +15,7 @@ import net.java.sip.communicator.util.Logger; import net.java.sip.communicator.plugin.desktoputil.*; +import org.apache.http.*; import org.jitsi.service.configuration.*; import org.jitsi.service.resources.*; import org.jitsi.util.*; @@ -253,6 +254,21 @@ public String getProvisioningPassword() * @return provisioning file downloaded */ private File retrieveConfigurationFile(String url) + { + return retrieveConfigurationFile(url, null); + } + + /** + * Retrieve configuration file from provisioning URL. + * This method is blocking until configuration file is retrieved from the + * network or if an exception happen + * + * @param url provisioning URL + * @param parameters the already filled parameters if any. + * @return provisioning file downloaded + */ + private File retrieveConfigurationFile(String url, + List parameters) { File tmpFile = null; @@ -462,30 +478,52 @@ private File retrieveConfigurationFile(String url) paramNames = new ArrayList(args.length); paramValues = new ArrayList(args.length); + String usernameParam = "${username}"; + String passwordParam = "${password}"; + for(int i = 0; i < args.length; i++) { String s = args[i]; - String usernameParam = "${username}"; - String passwordParam = "${password}"; + int equalsIndex = s.indexOf("="); + String currentParamName = null; + + if (equalsIndex > -1) + { + currentParamName = s.substring(0, equalsIndex); + } + + // pre loaded value we will reuse. + String preloadedParamValue = + getParamValue(parameters, currentParamName); // If we find the username or password parameter at this // stage we replace it with an empty string. + // or if we have an already filled value we will reuse it. if(s.indexOf(usernameParam) != -1) { - s = s.replace(usernameParam, ""); + if(preloadedParamValue != null) + { + s = s.replace(usernameParam, preloadedParamValue); + } + else + s = s.replace(usernameParam, ""); usernameIx = paramNames.size(); } else if(s.indexOf(passwordParam) != -1) { - s = s.replace(passwordParam, ""); + if(preloadedParamValue != null) + { + s = s.replace(passwordParam, preloadedParamValue); + } + else + s = s.replace(passwordParam, ""); passwordIx = paramNames.size(); } - int equalsIndex = s.indexOf("="); if (equalsIndex > -1) { - paramNames.add(s.substring(0, equalsIndex)); + paramNames.add(currentParamName); paramValues.add(s.substring(equalsIndex + 1)); } else @@ -514,7 +552,29 @@ else if(s.indexOf(passwordParam) != -1) paramNames, paramValues, usernameIx, - passwordIx); + passwordIx, + new HttpUtils.RedirectHandler() + { + @Override + public boolean handleRedirect( + String location, + List parameters) + { + if(!hasParams(location)) + return false; + + // if we have parameters proceed + retrieveConfigurationFile(location, parameters); + + return true; + } + + @Override + public boolean hasParams(String location) + { + return location.contains("${"); + } + }); } catch(Throwable t) { @@ -660,6 +720,27 @@ else if(s.indexOf(passwordParam) != -1) } } + /** + * Search param value for the supplied name. + * @param parameters the parameters can be null. + * @param paramName the name to search. + * @return the corresponding parameter value. + */ + private static String getParamValue(List parameters, + String paramName) + { + if(parameters == null || paramName == null) + return null; + + for(NameValuePair nv : parameters) + { + if(nv.getName().equals(paramName)) + return nv.getValue(); + } + + return null; + } + /** * Update configuration with properties retrieved from provisioning URL. * diff --git a/src/net/java/sip/communicator/plugin/provisioning/provisioning.manifest.mf b/src/net/java/sip/communicator/plugin/provisioning/provisioning.manifest.mf index 8b95d2c55..b1237628e 100644 --- a/src/net/java/sip/communicator/plugin/provisioning/provisioning.manifest.mf +++ b/src/net/java/sip/communicator/plugin/provisioning/provisioning.manifest.mf @@ -19,6 +19,7 @@ Import-Package: org.osgi.framework, org.jitsi.util, net.java.sip.communicator.util, net.java.sip.communicator.plugin.desktoputil, + org.apache.http, org.apache.http.params, javax.swing, javax.swing.event, diff --git a/src/net/java/sip/communicator/service/httputil/HttpUtils.java b/src/net/java/sip/communicator/service/httputil/HttpUtils.java index 325988f81..be1151b68 100644 --- a/src/net/java/sip/communicator/service/httputil/HttpUtils.java +++ b/src/net/java/sip/communicator/service/httputil/HttpUtils.java @@ -18,6 +18,7 @@ import org.apache.http.*; import org.apache.http.Header; +import org.apache.http.ProtocolException; import org.apache.http.auth.*; import org.apache.http.client.*; import org.apache.http.client.methods.*; @@ -31,6 +32,7 @@ import org.apache.http.impl.conn.*; import org.apache.http.message.*; import org.apache.http.params.*; +import org.apache.http.protocol.*; import org.apache.http.util.*; import org.jitsi.util.*; @@ -125,7 +127,7 @@ public static HTTPResponseResult openURLConnection(String address, } } - HttpEntity result = executeMethod(httpClient, httpGet); + HttpEntity result = executeMethod(httpClient, httpGet, null, null); if(result == null) return null; @@ -149,10 +151,17 @@ public static HTTPResponseResult openURLConnection(String address, * they stay saved. * @param httpClient the configured http client to use. * @param req the request for now it is get or post. + * @param redirectHandler handles redirection, should we redirect and + * the actual redirect. + * @param parameters if we are redirecting we can use already filled + * username and password in order to avoid asking the user twice. + * * @return the result http entity. */ private static HttpEntity executeMethod(DefaultHttpClient httpClient, - HttpRequestBase req) + HttpRequestBase req, + RedirectHandler redirectHandler, + List parameters) throws Throwable { // do it when response (first execution) or till we are unauthorized @@ -211,13 +220,11 @@ private static HttpEntity executeMethod(DefaultHttpClient httpClient, String newLocation = locationHeader.getValue(); - // append query string if any - HttpEntity en = ((HttpPost) oldreq).getEntity(); - if(en != null && en instanceof StringEntity) + // lets ask redirection handler if any + if(redirectHandler != null + && redirectHandler.handleRedirect(newLocation, parameters)) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - en.writeTo(out); - newLocation += "?" + out.toString("UTF-8"); + return null; } req = new HttpGet(newLocation); @@ -296,7 +303,8 @@ public static HTTPResponseResult postFile(String address, postMethod.setEntity(reqEntity); - HttpEntity resEntity = executeMethod(httpClient, postMethod); + HttpEntity resEntity = + executeMethod(httpClient, postMethod, null, null); if(resEntity == null) return null; @@ -340,6 +348,47 @@ public static HTTPResponseResult postForm(String address, int usernameParamIx, int passwordParamIx) throws Throwable + { + return postForm( + address, + usernamePropertyName, passwordPropertyName, + formParamNames, formParamValues, + usernameParamIx, passwordParamIx, + null); + } + + /** + * Posting form to address. For submission we use POST method + * which is "application/x-www-form-urlencoded" encoded. + * @param address HTTP address. + * @param usernamePropertyName the property to use to retrieve/store + * username value if protected site is hit, for username + * ConfigurationService service is used. + * @param passwordPropertyName the property to use to retrieve/store + * password value if protected site is hit, for password + * CredentialsStorageService service is used. + * @param formParamNames the parameter names to include in post. + * @param formParamValues the corresponding parameter values to use. + * @param usernameParamIx the index of the username parameter in the + * formParamNames and formParamValues + * if any, otherwise -1. + * @param passwordParamIx the index of the password parameter in the + * formParamNames and formParamValues + * if any, otherwise -1. + * @param redirectHandler handles redirection, should we redirect and + * the actual redirect. + * @return the result or null if send was not possible or + * credentials ask if any was canceled. + */ + public static HTTPResponseResult postForm(String address, + String usernamePropertyName, + String passwordPropertyName, + ArrayList formParamNames, + ArrayList formParamValues, + int usernameParamIx, + int passwordParamIx, + RedirectHandler redirectHandler) + throws Throwable { DefaultHttpClient httpClient; HttpPost postMethod; @@ -363,12 +412,11 @@ public static HTTPResponseResult postForm(String address, httpClient, postMethod, address, - usernamePropertyName, - passwordPropertyName, formParamNames, formParamValues, usernameParamIx, - passwordParamIx); + passwordParamIx, + redirectHandler); authEx = null; } @@ -408,12 +456,6 @@ public static HTTPResponseResult postForm(String address, * @param httpClient the http client * @param postMethod the post method * @param address HTTP address. - * @param usernamePropertyName the property to use to retrieve/store - * username value if protected site is hit, for username - * ConfigurationService service is used. - * @param passwordPropertyName the property to use to retrieve/store - * password value if protected site is hit, for password - * CredentialsStorageService service is used. * @param formParamNames the parameter names to include in post. * @param formParamValues the corresponding parameter values to use. * @param usernameParamIx the index of the username parameter in the @@ -429,21 +471,25 @@ private static HttpEntity postForm( DefaultHttpClient httpClient, HttpPost postMethod, String address, - String usernamePropertyName, - String passwordPropertyName, ArrayList formParamNames, ArrayList formParamValues, int usernameParamIx, - int passwordParamIx) + int passwordParamIx, + RedirectHandler redirectHandler) throws Throwable { // if we have username and password in the parameters, lets // retrieve their values + // if there are already filled skip asking the user Credentials creds = null; if(usernameParamIx != -1 && usernameParamIx < formParamNames.size() && passwordParamIx != -1 - && passwordParamIx < formParamNames.size()) + && passwordParamIx < formParamNames.size() + && (formParamValues.get(usernameParamIx) == null + || formParamValues.get(usernameParamIx).length() == 0) + && (formParamValues.get(passwordParamIx) == null + || formParamValues.get(passwordParamIx).length() == 0)) { URL url = new URL(address); HTTPCredentialsProvider prov = (HTTPCredentialsProvider) @@ -492,6 +538,11 @@ else if(i == passwordParamIx && creds != null) } } + // our custom strategy, will check redirect handler should we redirect + // if missing will use the default handler + httpClient.setRedirectStrategy( + new CustomRedirectStrategy(redirectHandler, parameters)); + // Uses String UTF-8 to keep compatible with android version and // older versions of the http client libs, as the one used // in debian (4.1.x) @@ -504,7 +555,8 @@ else if(i == passwordParamIx && creds != null) postMethod.setEntity(entity); // execute post - return executeMethod(httpClient, postMethod); + return executeMethod( + httpClient, postMethod, redirectHandler, parameters); } /** @@ -988,4 +1040,92 @@ public String[] getCredentials() return cred; } } + + /** + * Custom redirect handler that extends DefaultRedirectStrategy + * We will check redirect handler should we redirect + * If redirect handler is missing will continue with default strategy + */ + private static class CustomRedirectStrategy + extends DefaultRedirectStrategy + { + /** + * The redirect handler to check. + */ + private final RedirectHandler handler; + + /** + * The already filled parameters to be used when redirecting. + */ + private final List parameters; + + /** + * Created custom redirect strategy. + * @param handler the redirect handler. + * @param parameters already filled parameters. + */ + CustomRedirectStrategy(RedirectHandler handler, + List parameters) + { + this.handler = handler; + this.parameters = parameters; + } + + /** + * Check whether we need to redirect. + * @param request the initial request + * @param response the response containing the location param for + * redirect. + * @param context the http context. + * @return should we redirect. + * @throws ProtocolException + */ + public boolean isRedirected( + final HttpRequest request, + final HttpResponse response, + final HttpContext context) + throws ProtocolException + { + Header locationHeader = response.getFirstHeader("location"); + + if(handler != null + && locationHeader != null + && handler.hasParams(locationHeader.getValue())) + { + //we will cancel this redirect and will schedule new redirect + handler.handleRedirect(locationHeader.getValue(), parameters); + return false; + } + + return super.isRedirected(request, response, context); + } + } + + /** + * The redirect handler will cancel/proceed the redirection. Can + * schedule new request with the redirect location, reusing the already + * filled parameters. + */ + public static interface RedirectHandler + { + /** + * Schedule new request with the redirect location, reusing the already + * filled parameters. + * + * @param location the new location. + * @param parameters the parameters that were already filled. + * @return should we continue with normal redirect. + */ + public boolean handleRedirect(String location, + List parameters); + + /** + * Do the new location has params that need to be filled, return + * true will cause to handle redirect. + * @param location the new location. + * @return true if we need to redirect in the handler or + * false if we will continue with default redirect handling. + */ + boolean hasParams(String location); + } }