[KNOX-709] - HBase request URLs must not be URL encoded
diff --git a/CHANGES b/CHANGES
index 6463762..b04d78f 100644
--- a/CHANGES
+++ b/CHANGES
@@ -65,6 +65,7 @@
     * [KNOX-706] - KnoxSSO Default IDP must not require specific URL
     * [KNOX-707] - Enter Key within KnoxSSO Default IDP Form does not Submit
     * [KNOX-708] - Wrong CSS link in KnoxAuth Application's redirecting.html
+    * [KNOX-709] - HBase request URLs must not be URL encoded
 
 ------------------------------------------------------------------------------
 Release Notes - Apache Knox - Version 0.8.0
diff --git a/gateway-provider-ha/src/main/java/org/apache/hadoop/gateway/ha/dispatch/DefaultHaDispatch.java b/gateway-provider-ha/src/main/java/org/apache/hadoop/gateway/ha/dispatch/DefaultHaDispatch.java
index 82db972..4f66273 100644
--- a/gateway-provider-ha/src/main/java/org/apache/hadoop/gateway/ha/dispatch/DefaultHaDispatch.java
+++ b/gateway-provider-ha/src/main/java/org/apache/hadoop/gateway/ha/dispatch/DefaultHaDispatch.java
@@ -127,13 +127,4 @@
     }
   }
 
-  private static URI getDispatchUrl(HttpServletRequest request) {
-    StringBuffer str = request.getRequestURL();
-    String query = request.getQueryString();
-    if ( query != null ) {
-      str.append('?');
-      str.append(query);
-    }
-    return URI.create(str.toString());
-  }
 }
diff --git a/gateway-service-hbase/pom.xml b/gateway-service-hbase/pom.xml
index caed7a5..c536815 100644
--- a/gateway-service-hbase/pom.xml
+++ b/gateway-service-hbase/pom.xml
@@ -37,6 +37,11 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>test</scope>
diff --git a/gateway-service-hbase/src/main/java/org/apache/hadoop/gateway/hbase/HBaseDispatch.java b/gateway-service-hbase/src/main/java/org/apache/hadoop/gateway/hbase/HBaseDispatch.java
index f909852..beb9f02 100644
--- a/gateway-service-hbase/src/main/java/org/apache/hadoop/gateway/hbase/HBaseDispatch.java
+++ b/gateway-service-hbase/src/main/java/org/apache/hadoop/gateway/hbase/HBaseDispatch.java
@@ -17,6 +17,11 @@
  */
 package org.apache.hadoop.gateway.hbase;
 
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLDecoder;
+import javax.servlet.http.HttpServletRequest;
+
 import org.apache.hadoop.gateway.dispatch.DefaultDispatch;
 
 /**
@@ -26,5 +31,22 @@
 @Deprecated
 public class HBaseDispatch extends DefaultDispatch {
 
+  // KNOX-709: HBase can't handle URL encoded paths.
+  public URI getDispatchUrl( HttpServletRequest request) {
+    String base = request.getRequestURI();
+    StringBuffer str = new StringBuffer();
+    try {
+      str.append( URLDecoder.decode( base, "UTF-8" ) );
+    } catch( UnsupportedEncodingException e ) {
+      str.append( base );
+    } String query = request.getQueryString();
+    if ( query != null ) {
+      str.append( '?' );
+      str.append( query );
+    }
+    URI uri = URI.create( str.toString() );
+    return uri;
+  }
+
 }
 
diff --git a/gateway-service-hbase/src/test/java/org/apache/hadoop/gateway/hbase/HBaseDispatchTest.java b/gateway-service-hbase/src/test/java/org/apache/hadoop/gateway/hbase/HBaseDispatchTest.java
new file mode 100644
index 0000000..349f768
--- /dev/null
+++ b/gateway-service-hbase/src/test/java/org/apache/hadoop/gateway/hbase/HBaseDispatchTest.java
@@ -0,0 +1,88 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.gateway.hbase;
+
+import java.net.URI;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.hadoop.gateway.dispatch.Dispatch;
+import org.apache.hadoop.test.TestUtils;
+import org.apache.hadoop.test.category.FastTests;
+import org.apache.hadoop.test.category.UnitTests;
+import org.easymock.EasyMock;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
+@Category( { UnitTests.class, FastTests.class } )
+public class HBaseDispatchTest {
+
+  @Test( timeout = TestUtils.SHORT_TIMEOUT )
+  public void testGetDispatchUrl() throws Exception {
+    HttpServletRequest request;
+    Dispatch dispatch;
+    String path;
+    String query;
+    URI uri;
+
+    dispatch = new HBaseDispatch();
+
+    path = "http://test-host:42/test-path";
+    request = EasyMock.createNiceMock( HttpServletRequest.class );
+    EasyMock.expect( request.getRequestURI() ).andReturn( path ).anyTimes();
+    EasyMock.expect( request.getRequestURL() ).andReturn( new StringBuffer( path ) ).anyTimes();
+    EasyMock.expect( request.getQueryString() ).andReturn( null ).anyTimes();
+    EasyMock.replay( request );
+    uri = dispatch.getDispatchUrl( request );
+    assertThat( uri.toASCIIString(), is( "http://test-host:42/test-path" ) );
+
+    path = "http://test-host:42/test,path";
+    request = EasyMock.createNiceMock( HttpServletRequest.class );
+    EasyMock.expect( request.getRequestURI() ).andReturn( path ).anyTimes();
+    EasyMock.expect( request.getRequestURL() ).andReturn( new StringBuffer( path ) ).anyTimes();
+    EasyMock.expect( request.getQueryString() ).andReturn( null ).anyTimes();
+    EasyMock.replay( request );
+    uri = dispatch.getDispatchUrl( request );
+    assertThat( uri.toASCIIString(), is( "http://test-host:42/test,path" ) );
+
+    // KNOX-709: HBase request URLs must not be URL encoded
+    path = "http://test-host:42/test%2Cpath";
+    request = EasyMock.createNiceMock( HttpServletRequest.class );
+    EasyMock.expect( request.getRequestURI() ).andReturn( path ).anyTimes();
+    EasyMock.expect( request.getRequestURL() ).andReturn( new StringBuffer( path ) ).anyTimes();
+    EasyMock.expect( request.getQueryString() ).andReturn( null ).anyTimes();
+    EasyMock.replay( request );
+    uri = dispatch.getDispatchUrl( request );
+    assertThat( uri.toASCIIString(), is( "http://test-host:42/test,path" ) );
+
+    // KNOX-709: HBase request URLs must not be URL encoded
+    path = "http://test-host:42/test%2Cpath";
+    query = "test%26name=test%3Dvalue";
+    request = EasyMock.createNiceMock( HttpServletRequest.class );
+    EasyMock.expect( request.getRequestURI() ).andReturn( path ).anyTimes();
+    EasyMock.expect( request.getRequestURL() ).andReturn( new StringBuffer( path ) ).anyTimes();
+    EasyMock.expect( request.getQueryString() ).andReturn( query ).anyTimes();
+    EasyMock.replay( request );
+    uri = dispatch.getDispatchUrl( request );
+    assertThat( uri.toASCIIString(), is( "http://test-host:42/test,path?test%26name=test%3Dvalue" ) );
+
+  }
+
+}
\ No newline at end of file
diff --git a/gateway-service-webhdfs/src/main/java/org/apache/hadoop/gateway/hdfs/dispatch/WebHdfsHaDispatch.java b/gateway-service-webhdfs/src/main/java/org/apache/hadoop/gateway/hdfs/dispatch/WebHdfsHaDispatch.java
index d0bfd34..dbb5374 100644
--- a/gateway-service-webhdfs/src/main/java/org/apache/hadoop/gateway/hdfs/dispatch/WebHdfsHaDispatch.java
+++ b/gateway-service-webhdfs/src/main/java/org/apache/hadoop/gateway/hdfs/dispatch/WebHdfsHaDispatch.java
@@ -181,15 +181,4 @@
       }
    }
 
-  private static URI getDispatchUrl(HttpServletRequest request) {
-    StringBuffer str = request.getRequestURL();
-    String query = request.getQueryString();
-    if ( query != null ) {
-      str.append('?');
-      str.append(query);
-    }
-    URI url = URI.create(str.toString());
-    return url;
-  }
-
 }
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/dispatch/AbstractGatewayDispatch.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/dispatch/AbstractGatewayDispatch.java
index 622d37b..ddb806b 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/dispatch/AbstractGatewayDispatch.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/dispatch/AbstractGatewayDispatch.java
@@ -77,6 +77,18 @@
     this.client = client;
   }
 
+  @Override
+  public URI getDispatchUrl(HttpServletRequest request) {
+    StringBuffer str = request.getRequestURL();
+    String query = request.getQueryString();
+    if ( query != null ) {
+      str.append('?');
+      str.append(query);
+    }
+    URI url = URI.create(str.toString());
+    return url;
+  }
+
   public void doGet( URI url, HttpServletRequest request, HttpServletResponse response )
       throws IOException, URISyntaxException {
     response.sendError( HttpServletResponse.SC_METHOD_NOT_ALLOWED );
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/dispatch/Dispatch.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/dispatch/Dispatch.java
index 0ce1339..de08117 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/dispatch/Dispatch.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/dispatch/Dispatch.java
@@ -36,6 +36,8 @@
 
   void setHttpClient(HttpClient httpClient);
 
+  URI getDispatchUrl( HttpServletRequest request );
+
   void doGet( URI url, HttpServletRequest request, HttpServletResponse response )
       throws IOException, ServletException, URISyntaxException;
 
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/dispatch/GatewayDispatchFilter.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/dispatch/GatewayDispatchFilter.java
index 86601db..acfa92e 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/dispatch/GatewayDispatchFilter.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/dispatch/GatewayDispatchFilter.java
@@ -112,17 +112,6 @@
     }
   }
 
-  protected static URI getDispatchUrl(HttpServletRequest request) {
-    StringBuffer str = request.getRequestURL();
-    String query = request.getQueryString();
-    if ( query != null ) {
-      str.append('?');
-      str.append(query);
-    }
-    URI url = URI.create(str.toString());
-    return url;
-  }
-
   private interface Adapter {
     public void doMethod(Dispatch dispatch, HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException, URISyntaxException;
@@ -131,35 +120,35 @@
   private static class GetAdapter implements Adapter {
     public void doMethod(Dispatch dispatch, HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException, URISyntaxException {
-      dispatch.doGet(getDispatchUrl(request), request, response);
+      dispatch.doGet( dispatch.getDispatchUrl(request), request, response);
     }
   }
 
   private static class PostAdapter implements Adapter {
     public void doMethod(Dispatch dispatch, HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException, URISyntaxException {
-      dispatch.doPost(getDispatchUrl(request), request, response);
+      dispatch.doPost( dispatch.getDispatchUrl(request), request, response);
     }
   }
 
   private static class PutAdapter implements Adapter {
     public void doMethod(Dispatch dispatch, HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException, URISyntaxException {
-      dispatch.doPut(getDispatchUrl(request), request, response);
+      dispatch.doPut( dispatch.getDispatchUrl(request), request, response);
     }
   }
 
   private static class DeleteAdapter implements Adapter {
     public void doMethod(Dispatch dispatch, HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException, URISyntaxException {
-      dispatch.doDelete(getDispatchUrl(request), request, response);
+      dispatch.doDelete( dispatch.getDispatchUrl(request), request, response);
     }
   }
 
   private static class OptionsAdapter implements Adapter {
     public void doMethod(Dispatch dispatch, HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException, URISyntaxException {
-      dispatch.doOptions(getDispatchUrl(request), request, response);
+      dispatch.doOptions( dispatch.getDispatchUrl(request), request, response);
     }
   }
 
diff --git a/gateway-spi/src/test/java/org/apache/hadoop/gateway/dispatch/DefaultDispatchTest.java b/gateway-spi/src/test/java/org/apache/hadoop/gateway/dispatch/DefaultDispatchTest.java
index 4c360ff..37451fd 100644
--- a/gateway-spi/src/test/java/org/apache/hadoop/gateway/dispatch/DefaultDispatchTest.java
+++ b/gateway-spi/src/test/java/org/apache/hadoop/gateway/dispatch/DefaultDispatchTest.java
@@ -17,6 +17,7 @@
  */
 package org.apache.hadoop.gateway.dispatch;
 
+import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.instanceOf;
@@ -39,6 +40,9 @@
 
 import org.apache.hadoop.gateway.config.GatewayConfig;
 import org.apache.hadoop.gateway.servlet.SynchronousServletOutputStreamAdapter;
+import org.apache.hadoop.test.TestUtils;
+import org.apache.hadoop.test.category.FastTests;
+import org.apache.hadoop.test.category.UnitTests;
 import org.apache.http.HttpEntity;
 import org.apache.http.client.methods.HttpUriRequest;
 import org.apache.http.impl.client.DefaultHttpClient;
@@ -46,7 +50,9 @@
 import org.easymock.EasyMock;
 import org.easymock.IAnswer;
 import org.junit.Test;
+import org.junit.experimental.categories.Category;
 
+@Category( { UnitTests.class, FastTests.class } )
 public class DefaultDispatchTest {
 
   // Make sure Hadoop cluster topology isn't exposed to client when there is a connectivity issue.
@@ -165,4 +171,53 @@
     assertEquals(defaultDispatch.getReplayBufferSize(), 16);
   }
 
+  @Test( timeout = TestUtils.SHORT_TIMEOUT )
+  public void testGetDispatchUrl() throws Exception {
+    HttpServletRequest request;
+    Dispatch dispatch;
+    String path;
+    String query;
+    URI uri;
+
+    dispatch = new DefaultDispatch();
+
+    path = "http://test-host:42/test-path";
+    request = EasyMock.createNiceMock( HttpServletRequest.class );
+    EasyMock.expect( request.getRequestURI() ).andReturn( path ).anyTimes();
+    EasyMock.expect( request.getRequestURL() ).andReturn( new StringBuffer( path ) ).anyTimes();
+    EasyMock.expect( request.getQueryString() ).andReturn( null ).anyTimes();
+    EasyMock.replay( request );
+    uri = dispatch.getDispatchUrl( request );
+    assertThat( uri.toASCIIString(), is( "http://test-host:42/test-path" ) );
+
+    path = "http://test-host:42/test,path";
+    request = EasyMock.createNiceMock( HttpServletRequest.class );
+    EasyMock.expect( request.getRequestURI() ).andReturn( path ).anyTimes();
+    EasyMock.expect( request.getRequestURL() ).andReturn( new StringBuffer( path ) ).anyTimes();
+    EasyMock.expect( request.getQueryString() ).andReturn( null ).anyTimes();
+    EasyMock.replay( request );
+    uri = dispatch.getDispatchUrl( request );
+    assertThat( uri.toASCIIString(), is( "http://test-host:42/test,path" ) );
+
+    path = "http://test-host:42/test%2Cpath";
+    request = EasyMock.createNiceMock( HttpServletRequest.class );
+    EasyMock.expect( request.getRequestURI() ).andReturn( path ).anyTimes();
+    EasyMock.expect( request.getRequestURL() ).andReturn( new StringBuffer( path ) ).anyTimes();
+    EasyMock.expect( request.getQueryString() ).andReturn( null ).anyTimes();
+    EasyMock.replay( request );
+    uri = dispatch.getDispatchUrl( request );
+    assertThat( uri.toASCIIString(), is( "http://test-host:42/test%2Cpath" ) );
+
+    path = "http://test-host:42/test%2Cpath";
+    query = "test%26name=test%3Dvalue";
+    request = EasyMock.createNiceMock( HttpServletRequest.class );
+    EasyMock.expect( request.getRequestURI() ).andReturn( path ).anyTimes();
+    EasyMock.expect( request.getRequestURL() ).andReturn( new StringBuffer( path ) ).anyTimes();
+    EasyMock.expect( request.getQueryString() ).andReturn( query ).anyTimes();
+    EasyMock.replay( request );
+    uri = dispatch.getDispatchUrl( request );
+    assertThat( uri.toASCIIString(), is( "http://test-host:42/test%2Cpath?test%26name=test%3Dvalue" ) );
+
+  }
+
 }