Repository: StackStorm/st2

Issue:
ST2API doesn't parse percent encoded URIs
Tracking StackStorm/st2web#357 here.


Discussion:

Resolving patch:
diff --git a/st2common/st2common/router.py b/st2common/st2common/router.py
--- a/st2common/st2common/router.py
+++ b/st2common/st2common/router.py
@@ -27,6 +27,7 @@
 from six.moves.urllib import parse as urlparse  # pylint: disable=import-error
 import webob
 from webob import exc, Request
+from webob.compat import url_unquote
 
 from st2common.exceptions import rbac as rbac_exc
 from st2common.exceptions import auth as auth_exc
@@ -187,7 +188,8 @@ def add_spec(self, spec, transforms):
             LOG.debug('Route registered: %+6s %s', route.conditions['method'][0], route.routepath)
 
     def match(self, req):
-        path = req.path
+        path = url_unquote(req.path)
+        LOG.debug("Match path: %s", path)
 
         if len(path) > 1 and path.endswith('/'):
             path = path[:-1]
@@ -219,7 +221,10 @@ def __call__(self, req):
 
         At the time of writing, the only property being utilized by middleware was `x-log-result`.
         """
+        LOG.debug("Recieved call with WebOb: %s", req)
         endpoint, path_vars = self.match(req)
+        LOG.debug("Parsed endpoint: %s", endpoint)
+        LOG.debug("Parsed path_vars: %s", path_vars)
 
         context = copy.copy(getattr(self, 'mock_context', {}))
 
@@ -369,9 +374,9 @@ def __init__(self, **entries):
                 raise exc.HTTPBadRequest(detail=detail)
 
             # Validating and casting param types
-            type = param.get('type', None)
+            param_type = param.get('type', None)
             if kw[argument_name] is not None:
-                if type == 'boolean':
+                if param_type == 'boolean':
                     positive = ('true', '1', 'yes', 'y')
                     negative = ('false', '0', 'no', 'n')
 
@@ -380,7 +385,7 @@ def __init__(self, **entries):
                         raise exc.HTTPBadRequest(detail=detail)
 
                     kw[argument_name] = str(kw[argument_name]).lower() in positive
-                elif type == 'integer':
+                elif param_type == 'integer':
                     regex = r'^-?[0-9]+$'
 
                     if not re.search(regex, str(kw[argument_name])):
@@ -388,7 +393,7 @@ def __init__(self, **entries):
                         raise exc.HTTPBadRequest(detail=detail)
 
                     kw[argument_name] = int(kw[argument_name])
-                elif type == 'number':
+                elif param_type == 'number':
                     regex = r'^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$'
 
                     if not re.search(regex, str(kw[argument_name])):


Test patch:
diff --git a/st2api/tests/unit/controllers/v1/test_rules.py b/st2api/tests/unit/controllers/v1/test_rules.py
--- a/st2api/tests/unit/controllers/v1/test_rules.py
+++ b/st2api/tests/unit/controllers/v1/test_rules.py
@@ -117,6 +117,11 @@ def setUpClass(cls):
             fixtures_pack=FIXTURES_PACK,
             fixtures_dict={'rules': [file_name]})['rules'][file_name]
 
+        file_name = 'rule space.yaml'
+        TestRuleController.RULE_SPACE = TestRuleController.fixtures_loader.load_fixtures(
+            fixtures_pack=FIXTURES_PACK,
+            fixtures_dict={'rules': [file_name]})['rules'][file_name]
+
     @classmethod
     def tearDownClass(cls):
         # Replace original configured value for trigger parameter validation
@@ -177,6 +182,16 @@ def test_get_one_by_ref(self):
         self.assertEqual(get_resp.status_int, http_client.OK)
         self.__do_delete(rule_id)
 
+        post_resp = self.__do_post(TestRuleController.RULE_SPACE)
+        rule_name = post_resp.json['name']
+        rule_pack = post_resp.json['pack']
+        ref = ResourceReference.to_string_reference(name=rule_name, pack=rule_pack)
+        rule_id = post_resp.json['id']
+        get_resp = self.__do_get_one(ref)
+        self.assertEqual(get_resp.json['name'], rule_name)
+        self.assertEqual(get_resp.status_int, http_client.OK)
+        self.__do_delete(rule_id)
+
     def test_get_one_fail(self):
         resp = self.app.get('/v1/rules/1', expect_errors=True)
         self.assertEqual(resp.status_int, http_client.NOT_FOUND)