refactor: unify basis-dir path validation and normalization
config_basis_path_valid and config_basis_append now share one normalizer (basis_path_normalize): interior empty components (a//b) collapse, '.' components and trailing slashes are dropped, and the stored form is exactly the canonical relative path used by validation, the delete-walker prefix match and the receiver's basis lookup. Degenerate inputs (empty, absolute, '..', '.' that normalizes to nothing) stay rejected.
This commit is contained in:
+52
-28
@@ -194,52 +194,76 @@ bool config_has_basis(const Config* config) {
|
|||||||
/* A basis-dir path travels from the client to the receiver and is resolved
|
/* A basis-dir path travels from the client to the receiver and is resolved
|
||||||
* below the destination root, so it must be a non-empty relative path with no
|
* below the destination root, so it must be a non-empty relative path with no
|
||||||
* "." or ".." component and no traversal: an absolute or escaping path would
|
* "." or ".." component and no traversal: an absolute or escaping path would
|
||||||
* make the receiver read or link files outside its authorized root. */
|
* make the receiver read or link files outside its authorized root.
|
||||||
bool config_basis_path_valid(const char* path) {
|
*
|
||||||
|
* Returns a malloc'd CANONICAL copy of an accepted path, or NULL when the path
|
||||||
|
* is rejected. Canonicalization collapses interior empty components ("a//b" ->
|
||||||
|
* "a/b"), drops "." components and trailing "/"s, so validation, the delete
|
||||||
|
* walker prefix match and the receiver's basis lookup all agree on one form.
|
||||||
|
* The normalizer is the single source of truth for both config_basis_path_valid
|
||||||
|
* and config_basis_append. */
|
||||||
|
static char* basis_path_normalize(const char* path) {
|
||||||
if (!path || path[0] == '\0' || path[0] == '/' || has_path_traversal(path))
|
if (!path || path[0] == '\0' || path[0] == '/' || has_path_traversal(path))
|
||||||
return false;
|
return NULL;
|
||||||
if (strcmp(path, ".") == 0)
|
if (strcmp(path, ".") == 0)
|
||||||
return false;
|
return NULL;
|
||||||
char* dup = str_dup(path);
|
char* dup = str_dup(path);
|
||||||
if (!dup)
|
if (!dup)
|
||||||
return false;
|
return NULL;
|
||||||
bool ok = true;
|
size_t out_len = 0;
|
||||||
char* saveptr = NULL;
|
char* out = malloc(strlen(path) + 1);
|
||||||
for (char* part = strtok_r(dup, "/", &saveptr); part; part = strtok_r(NULL, "/", &saveptr)) {
|
if (!out) {
|
||||||
if (strcmp(part, ".") == 0) {
|
free(dup);
|
||||||
ok = false;
|
return NULL;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
char* saveptr = NULL;
|
||||||
|
bool ok = true;
|
||||||
|
for (char* part = strtok_r(dup, "/", &saveptr); part; part = strtok_r(NULL, "/", &saveptr)) {
|
||||||
if (strcmp(part, "..") == 0) {
|
if (strcmp(part, "..") == 0) {
|
||||||
ok = false;
|
ok = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (strcmp(part, ".") == 0)
|
||||||
|
continue;
|
||||||
|
if (out_len > 0)
|
||||||
|
out[out_len++] = '/';
|
||||||
|
size_t len = strlen(part);
|
||||||
|
memcpy(out + out_len, part, len);
|
||||||
|
out_len += len;
|
||||||
}
|
}
|
||||||
free(dup);
|
free(dup);
|
||||||
return ok;
|
if (!ok || out_len == 0) {
|
||||||
|
free(out);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
out[out_len] = '\0';
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool config_basis_path_valid(const char* path) {
|
||||||
|
char* normalized = basis_path_normalize(path);
|
||||||
|
if (!normalized)
|
||||||
|
return false;
|
||||||
|
free(normalized);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int config_basis_append(Config* config, BasisDestType type, const char* path) {
|
int config_basis_append(Config* config, BasisDestType type, const char* path) {
|
||||||
if (!config || !config_basis_path_valid(path) ||
|
if (!config ||
|
||||||
(type != BASIS_DEST_COMPARE && type != BASIS_DEST_COPY && type != BASIS_DEST_LINK) ||
|
(type != BASIS_DEST_COMPARE && type != BASIS_DEST_COPY && type != BASIS_DEST_LINK) ||
|
||||||
config->basis_count >= MAX_BASIS_DIRS)
|
config->basis_count >= MAX_BASIS_DIRS)
|
||||||
return -1;
|
return -1;
|
||||||
|
char* normalized = basis_path_normalize(path);
|
||||||
|
if (!normalized)
|
||||||
|
return -1;
|
||||||
BasisDest* grown = realloc(config->basis_dirs, (config->basis_count + 1) * sizeof(BasisDest));
|
BasisDest* grown = realloc(config->basis_dirs, (config->basis_count + 1) * sizeof(BasisDest));
|
||||||
if (!grown)
|
if (!grown) {
|
||||||
|
free(normalized);
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
config->basis_dirs = grown;
|
config->basis_dirs = grown;
|
||||||
/* Normalize a user-supplied trailing slash away so the stored path matches
|
|
||||||
the delete-walker prefix form exactly. */
|
|
||||||
size_t len = strlen(path);
|
|
||||||
while (len > 1 && path[len - 1] == '/')
|
|
||||||
len--;
|
|
||||||
char* dup = malloc(len + 1);
|
|
||||||
if (!dup)
|
|
||||||
return -1;
|
|
||||||
memcpy(dup, path, len);
|
|
||||||
dup[len] = '\0';
|
|
||||||
config->basis_dirs[config->basis_count].type = type;
|
config->basis_dirs[config->basis_count].type = type;
|
||||||
config->basis_dirs[config->basis_count].path = dup;
|
config->basis_dirs[config->basis_count].path = normalized;
|
||||||
config->basis_count++;
|
config->basis_count++;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -563,9 +587,9 @@ static bool receive_basis_options(int fd, Config* c) {
|
|||||||
char* path = receive_str(fd);
|
char* path = receive_str(fd);
|
||||||
if (!path)
|
if (!path)
|
||||||
return false;
|
return false;
|
||||||
bool ok = config_basis_path_valid(path);
|
/* config_basis_append validates and canonicalizes the path; a rejected
|
||||||
if (ok)
|
path (absolute / traversal / empty) drops the whole connection. */
|
||||||
ok = config_basis_append(c, (BasisDestType)type, path) == 0;
|
bool ok = config_basis_append(c, (BasisDestType)type, path) == 0;
|
||||||
free(path);
|
free(path);
|
||||||
if (!ok)
|
if (!ok)
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user