GitHub user MasMadd created a discussion: Make ensureAppRoot idempotent to
prevent double-prefixing
**Summary**
1) ensureAppRoot now checks whether the path is already prefixed with the
application root before prepending it, avoiding incorrect double-prefixed paths
(e.g. /analytics/analytics/whatever)
2) Normalizes both the root and the input path before comparison to handle edge
cases (trailing slashes, missing leading slashes, root set to /)
**Motivation**
Previously, calling ensureAppRoot multiple times on the same path (or passing a
path that already contained the application root) would result in the root
being prepended again, producing malformed paths. This made the function unsafe
to call defensively or in contexts where the path origin was uncertain.
**Changes**
1) Normalize the application root by stripping trailing slashes and falling
back to "/".
2) Normalize the input path by ensuring a leading slash.
3) Short-circuit and return the path unchanged when:
3.1. The root is "/" (no prefix needed).
3.2. The path already starts with the root (idempotency guard).
4) Only prepend the root when the path is genuinely missing it.
**Code**:
```
import { applicationRoot } from 'src/utils/getBootstrapData';
export function ensureAppRoot(path: string): string {
const root = applicationRoot();
// Normalize the root:
// - Fallback to "/"
// - Remove any trailing slashes (except if the root is exactly "/")
const r = (root || '/').replace(/\/+$/, '') || '/';
// Normalize the path:
// - Ensure it always starts with a leading slash
const p = path.startsWith('/') ? path : `/${path}`;
// If the application root is "/", no prefixing is needed
if (r === '/') return p;
// If the path is already prefixed with the root,
// return it unchanged to avoid double-prefixing
if (p === r || p.startsWith(r + '/')) return p;
// Otherwise, prepend the root to the path
return r + p;
}
```
I tested this logic against different cases:
```
var root_1 = '/'
var case_1 = '/api/v1/sqllab/';
var root_2 = '/a'
var case_2 = '/api/v1/sqllab/';
var root_3 = '/a'
var case_3 = '/a/api/v1/sqllab/';
var root_4 = '/a/b'
var case_4 = '/api/v1/sqllab/';
var root_5 = '/a/b'
var case_5 = '/a/api/v1/sqllab/';
var root_6 = '/a/b'
var case_6 = '/a/b/api/v1/sqllab/';
var root_7 = '/a/b/'
var case_7 = '/a/b/api/v1/sqllab/';
var root_8 = '/a/'
var case_8 = '/a/api/v1/sqllab/';
var root_9 = '/a/'
var case_9 = '/api/v1/sqllab/';
var result1 = ensureAppRoot(root_1, case_1);
var result2 = ensureAppRoot(root_2, case_2);
var result3 = ensureAppRoot(root_3, case_3);
var result4 = ensureAppRoot(root_4, case_4);
var result5 = ensureAppRoot(root_5, case_5);
var result6 = ensureAppRoot(root_6, case_6);
var result7 = ensureAppRoot(root_7, case_7);
var result8 = ensureAppRoot(root_8, case_8);
var result9 = ensureAppRoot(root_9, case_9);
assert(result1, result1 === '/api/v1/sqllab/', 'Equal 1', 'Not equal 1');
assert(result2, result2 === '/a/api/v1/sqllab/', 'Equal 2','Not equal 2');
assert(result3, result3 === '/a/api/v1/sqllab/', 'Equal 3','Not equal 3');
assert(result4, result4 === '/a/b/api/v1/sqllab/', 'Equal 4','Not equal 4');
assert(result5, result5 === '/a/b/a/api/v1/sqllab/','Equal 5', 'Not equal 5');
assert(result6, result6 === '/a/b/api/v1/sqllab/', 'Equal 6','Not equal 6');
assert(result7, result7 === '/a/b/api/v1/sqllab/', 'Equal 7','Not equal 7');
assert(result8, result8 === '/a/api/v1/sqllab/', 'Equal 8','Not equal 8');
assert(result9, result9 === '/a/api/v1/sqllab/', 'Equal 9','Not equal 9');
function assert(actual_value: string, condition: unknown, ok_message: string,
error_message: string): asserts condition {
if (!condition) {
console.log(actual_value);
throw new Error(error_message);
}else{
console.log(ok_message);
}
}
function ensureAppRoot(root: string, path: string): string {
// Normalize the root:
// - Fallback to "/"
// - Remove any trailing slashes (except if the root is exactly "/")
const r = (root || '/').replace(/\/+$/, '') || '/';
// Normalize the path:
// - Ensure it always starts with a leading slash
const p = path.startsWith('/') ? path : `/${path}`;
// If the application root is "/", no prefixing is needed
if (r === '/') return p;
// If the path is already prefixed with the root,
// return it unchanged to avoid double-prefixing
if (p === r || p.startsWith(r + '/')) return p;
// Otherwise, prepend the root to the path
return r + p;
}
```
GitHub link: https://github.com/apache/superset/discussions/39945
----
This is an automatically sent email for [email protected].
To unsubscribe, please send an email to:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]