|
11 | 11 | import com.marklogic.client.ext.ssl.SslUtil; |
12 | 12 | import okhttp3.OkHttpClient; |
13 | 13 | import org.springframework.util.StringUtils; |
| 14 | +import org.springframework.web.util.UriComponentsBuilder; |
14 | 15 |
|
15 | 16 | import javax.net.ssl.SSLContext; |
16 | 17 | import java.net.URI; |
17 | | -import java.net.URISyntaxException; |
18 | 18 |
|
19 | 19 | public class RestConfig { |
20 | 20 |
|
@@ -158,28 +158,61 @@ public String toString() { |
158 | 158 | } |
159 | 159 |
|
160 | 160 | /** |
161 | | - * Using the java.net.URI constructor that takes a string. Using any other constructor runs into encoding problems, |
162 | | - * e.g. when a mimetype has a plus in it, that plus needs to be encoded, but doing as %2B will result in the % being |
163 | | - * double encoded. Unfortunately, it seems some encoding is still needed - e.g. for a pipeline like "Flexible Replication" |
164 | | - * with a space in its name, the space must be encoded properly as a "+". |
| 161 | + * Builds a URI using Spring's UriComponentsBuilder to prevent URL manipulation attacks. |
| 162 | + * This replaces the previous vulnerable implementation that allowed user-controllable URL construction. |
| 163 | + * Handles query parameters that may be included in the path for backwards compatibility. |
165 | 164 | * |
166 | 165 | * @param path |
167 | 166 | * @return |
168 | 167 | */ |
169 | 168 | public URI buildUri(String path) { |
170 | | - String basePathToAppend = ""; |
171 | | - if (basePath != null) { |
172 | | - if (!basePath.startsWith("/")) { |
173 | | - basePathToAppend = "/"; |
| 169 | + try { |
| 170 | + UriComponentsBuilder builder = UriComponentsBuilder.newInstance() |
| 171 | + .scheme(getScheme()) |
| 172 | + .host(getHost()) |
| 173 | + .port(getPort()); |
| 174 | + |
| 175 | + // Handle base path |
| 176 | + String fullPath = path; |
| 177 | + if (basePath != null) { |
| 178 | + String normalizedBasePath = basePath.startsWith("/") ? basePath : "/" + basePath; |
| 179 | + if (path.startsWith("/") && normalizedBasePath.endsWith("/")) { |
| 180 | + normalizedBasePath = normalizedBasePath.substring(0, normalizedBasePath.length() - 1); |
| 181 | + } |
| 182 | + fullPath = normalizedBasePath + path; |
174 | 183 | } |
175 | | - basePathToAppend += basePath; |
176 | | - if (path.startsWith("/") && basePathToAppend.endsWith("/")) { |
177 | | - basePathToAppend = basePathToAppend.substring(0, basePathToAppend.length() - 1); |
| 184 | + |
| 185 | + // Check if the path contains query parameters |
| 186 | + int queryIndex = fullPath.indexOf('?'); |
| 187 | + if (queryIndex != -1) { |
| 188 | + // Split path and query parameters |
| 189 | + String pathPart = fullPath.substring(0, queryIndex); |
| 190 | + String queryPart = fullPath.substring(queryIndex + 1); |
| 191 | + |
| 192 | + builder.path(pathPart); |
| 193 | + |
| 194 | + // Parse and add query parameters |
| 195 | + if (!queryPart.isEmpty()) { |
| 196 | + String[] params = queryPart.split("&"); |
| 197 | + for (String param : params) { |
| 198 | + int equalIndex = param.indexOf('='); |
| 199 | + if (equalIndex != -1) { |
| 200 | + String key = param.substring(0, equalIndex); |
| 201 | + String value = param.substring(equalIndex + 1); |
| 202 | + builder.queryParam(key, value); |
| 203 | + } else { |
| 204 | + // Handle parameters without values |
| 205 | + builder.queryParam(param, ""); |
| 206 | + } |
| 207 | + } |
| 208 | + } |
| 209 | + } else { |
| 210 | + // No query parameters, just set the path |
| 211 | + builder.path(fullPath); |
178 | 212 | } |
179 | | - } |
180 | | - try { |
181 | | - return new URI(String.format("%s://%s:%d%s%s", getScheme(), getHost(), getPort(), basePathToAppend, path.replace(" ", "+"))); |
182 | | - } catch (URISyntaxException ex) { |
| 213 | + |
| 214 | + return builder.build().toUri(); |
| 215 | + } catch (Exception ex) { |
183 | 216 | throw new RuntimeException("Unable to build URI for path: " + path + "; cause: " + ex.getMessage(), ex); |
184 | 217 | } |
185 | 218 | } |
|
0 commit comments