Skip to content

Commit 3d6f04e

Browse files
committed
fix: adds option to escape non-param colons in path.
1 parent 21fcfc3 commit 3d6f04e

File tree

4 files changed

+114
-1
lines changed

4 files changed

+114
-1
lines changed

adapter/vertxweb/src/main/java/io/gatehill/imposter/server/vertxweb/util/VertxResourceUtil.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,18 @@ package io.gatehill.imposter.server.vertxweb.util
4545
import com.google.common.base.Strings
4646
import com.google.common.collect.BiMap
4747
import com.google.common.collect.HashBiMap
48+
import io.gatehill.imposter.config.util.EnvVars
4849
import io.gatehill.imposter.http.HttpMethod
4950
import io.gatehill.imposter.http.HttpRoute
50-
import java.util.*
51+
import org.apache.logging.log4j.LogManager
52+
import java.util.UUID
5153

5254
/**
5355
* @author Pete Cornish
5456
*/
5557
object VertxResourceUtil {
58+
private val LOGGER = LogManager.getLogger(VertxResourceUtil::class.java)
59+
5660
/**
5761
* Vert.x documentation says:
5862
* > The placeholders consist of : followed by the parameter name.
@@ -66,6 +70,14 @@ object VertxResourceUtil {
6670

6771
private val METHODS: BiMap<HttpMethod, io.vertx.core.http.HttpMethod?> = HashBiMap.create()
6872

73+
/**
74+
* Vert.x shouldn't interpret a colon in a path component as the
75+
* start of a path parameter.
76+
* Work-around for https://github.com/outofcoffee/imposter/issues/587
77+
*/
78+
private val escapeColonsInPath: Boolean
79+
get() = EnvVars.getEnv("IMPOSTER_ESCAPE_COLONS_IN_PATH").toBoolean()
80+
6981
init {
7082
METHODS[HttpMethod.GET] = io.vertx.core.http.HttpMethod.GET
7183
METHODS[HttpMethod.HEAD] = io.vertx.core.http.HttpMethod.HEAD
@@ -119,6 +131,10 @@ object VertxResourceUtil {
119131
fun normalisePath(normalisedParams: MutableMap<String, String>, rawPath: String): String {
120132
var path = rawPath
121133
if (!Strings.isNullOrEmpty(path)) {
134+
if (escapeColonsInPath) {
135+
path = path.replace(":", "%3A")
136+
}
137+
122138
var matchFound: Boolean
123139
do {
124140
val matcher = HttpRoute.PATH_PARAM_PLACEHOLDER.matcher(path)

docs/environment_variables.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The following environment variables are supported:
1010
| IMPOSTER_CONFIG_DIR | The path to the configuration directory. Can be specified as a comma-separated list. See [configuration location](config_location.md). | Empty | `/path/to/config/dir` - See [Configuration Location](./config_location.md). |
1111
| IMPOSTER_CONFIG_SCAN_RECURSIVE | Scan for configuration files recursively within the configuration directories. See [recursive configuration discovery](./config_discovery.md). | `false` | `true` |
1212
| IMPOSTER_CONFIG_DISCOVER_ENVFILES | Discover envfiles. See below. | `true` | `false` |
13+
| IMPOSTER_ESCAPE_COLONS_IN_PATH | Escape colons in paths. | `false` | `true` |
1314
| IMPOSTER_FEATURES | Enables or disables features. See [Features](./features.md) documentation. | Per [default features](./features.md). | `metrics=false,stores=true` |
1415
| IMPOSTER_IGNORE_CONFIG_ERRORS | Log errors encountered during configuration parsing or plugin configuration instead of throwing an exception. Defaults to `false`, as skipping a configuration might also skip the security conditions it contains. | `false` | boolean |
1516
| IMPOSTER_JS_PLUGIN | Sets the JavaScript implementation. | `js-nashorn` | Can be changed to `js-graal` plugin, if installed. |
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright (c) 2016-2024.
3+
*
4+
* This file is part of Imposter.
5+
*
6+
* "Commons Clause" License Condition v1.0
7+
*
8+
* The Software is provided to you by the Licensor under the License, as
9+
* defined below, subject to the following condition.
10+
*
11+
* Without limiting other conditions in the License, the grant of rights
12+
* under the License will not include, and the License does not grant to
13+
* you, the right to Sell the Software.
14+
*
15+
* For purposes of the foregoing, "Sell" means practicing any or all of
16+
* the rights granted to you under the License to provide to third parties,
17+
* for a fee or other consideration (including without limitation fees for
18+
* hosting or consulting/support services related to the Software), a
19+
* product or service whose value derives, entirely or substantially, from
20+
* the functionality of the Software. Any license notice or attribution
21+
* required by the License must also include this Commons Clause License
22+
* Condition notice.
23+
*
24+
* Software: Imposter
25+
*
26+
* License: GNU Lesser General Public License version 3
27+
*
28+
* Licensor: Peter Cornish
29+
*
30+
* Imposter is free software: you can redistribute it and/or modify
31+
* it under the terms of the GNU Lesser General Public License as published by
32+
* the Free Software Foundation, either version 3 of the License, or
33+
* (at your option) any later version.
34+
*
35+
* Imposter is distributed in the hope that it will be useful,
36+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
37+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38+
* GNU Lesser General Public License for more details.
39+
*
40+
* You should have received a copy of the GNU Lesser General Public License
41+
* along with Imposter. If not, see <https://www.gnu.org/licenses/>.
42+
*/
43+
package io.gatehill.imposter.server
44+
45+
import io.gatehill.imposter.config.util.EnvVars
46+
import io.gatehill.imposter.plugin.test.TestPluginImpl
47+
import io.restassured.RestAssured
48+
import io.vertx.ext.unit.TestContext
49+
import io.vertx.ext.unit.junit.VertxUnitRunner
50+
import org.hamcrest.Matchers.equalTo
51+
import org.junit.Before
52+
import org.junit.Test
53+
import org.junit.runner.RunWith
54+
55+
/**
56+
* Tests for paths with parameters adjacent to reserved characters.
57+
*
58+
* @author Pete Cornish
59+
*/
60+
@RunWith(VertxUnitRunner::class)
61+
class ReservedCharsInPathTest : BaseVerticleTest() {
62+
override val pluginClass = TestPluginImpl::class.java
63+
64+
@Before
65+
@Throws(Exception::class)
66+
override fun setUp(testContext: TestContext) {
67+
super.setUp(testContext)
68+
RestAssured.baseURI = "http://$host:$listenPort"
69+
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails()
70+
71+
// enable work-around for non-parameter colons in paths
72+
EnvVars.populate("IMPOSTER_ESCAPE_COLONS_IN_PATH" to "true")
73+
}
74+
75+
override val testConfigDirs = listOf(
76+
"/reserved-chars-in-path"
77+
)
78+
79+
/**
80+
* Match against a path with an exact match over that with a placeholder.
81+
*/
82+
@Test
83+
fun `should serve response for path containing reserved character`() {
84+
RestAssured.given().`when`()
85+
.get("/example/foo:qux")
86+
.then()
87+
.body(equalTo("baz"))
88+
}
89+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
plugin: "io.gatehill.imposter.plugin.test.TestPluginImpl"
2+
3+
resources:
4+
- method: GET
5+
path: /example/foo:{bar}
6+
response:
7+
content: "baz"

0 commit comments

Comments
 (0)