283 lines
9.2 KiB
C++
283 lines
9.2 KiB
C++
//
|
|
// Created by WolverinDEV on 29/04/2020.
|
|
//
|
|
|
|
#include "clnpath.h"
|
|
#include <cstring>
|
|
|
|
#define MAX_PATH_ELEMENTS 128 /* Number of levels of directory */
|
|
void ts_clnpath(char *path)
|
|
{
|
|
char *src;
|
|
char *dst;
|
|
char c;
|
|
int slash = 0;
|
|
|
|
/* Convert multiple adjacent slashes to single slash */
|
|
src = dst = path;
|
|
while ((c = *dst++ = *src++) != '\0')
|
|
{
|
|
if (c == '/')
|
|
{
|
|
slash = 1;
|
|
while (*src == '/')
|
|
src++;
|
|
}
|
|
}
|
|
|
|
if (slash == 0)
|
|
return;
|
|
|
|
/* Remove "./" from "./xxx" but leave "./" alone. */
|
|
/* Remove "/." from "xxx/." but reduce "/." to "/". */
|
|
/* Reduce "xxx/./yyy" to "xxx/yyy" */
|
|
src = dst = (*path == '/') ? path + 1 : path;
|
|
while (src[0] == '.' && src[1] == '/' && src[2] != '\0')
|
|
src += 2;
|
|
while ((c = *dst++ = *src++) != '\0')
|
|
{
|
|
if (c == '/' && src[0] == '.' && (src[1] == '\0' || src[1] == '/'))
|
|
{
|
|
src++;
|
|
dst--;
|
|
}
|
|
}
|
|
if (path[0] == '/' && path[1] == '.' &&
|
|
(path[2] == '\0' || (path[2] == '/' && path[3] == '\0')))
|
|
path[1] = '\0';
|
|
|
|
/* Remove trailing slash, if any. There is at most one! */
|
|
/* dst is pointing one beyond terminating null */
|
|
if ((dst -= 2) > path && *dst == '/')
|
|
*dst++ = '\0';
|
|
}
|
|
|
|
bool ts_strequal(const char* a, const char* b) {
|
|
return strcmp(a, b) == 0;
|
|
}
|
|
|
|
int ts_tokenise(char* ostring, const char* del, char** result, int max_tokens) {
|
|
int num_tokens{0};
|
|
|
|
char* token, *string, *tofree;
|
|
tofree = string = strdup(ostring);
|
|
while ((token = strsep(&string, del)) != nullptr) {
|
|
result[num_tokens++] = strdup(token);
|
|
if(num_tokens > max_tokens)
|
|
break;
|
|
}
|
|
|
|
free(tofree);
|
|
return num_tokens;
|
|
}
|
|
|
|
/*
|
|
** clnpath2() is not part of the basic clnpath() function because it can
|
|
** change the meaning of a path name if there are symbolic links on the
|
|
** system. For example, suppose /usr/tmp is a symbolic link to /var/tmp.
|
|
** If the user supplies /usr/tmp/../abcdef as the directory name, clnpath
|
|
** would transform that to /usr/abcdef, not to /var/abcdef which is what
|
|
** the kernel would interpret it as.
|
|
*/
|
|
void ts_clnpath2(char *path)
|
|
{
|
|
char *token[MAX_PATH_ELEMENTS], *otoken[MAX_PATH_ELEMENTS];
|
|
int ntok, ontok;
|
|
|
|
ts_clnpath(path);
|
|
|
|
/* Reduce "<name>/.." to "/" */
|
|
ntok = ontok = ts_tokenise(path, "/", otoken, MAX_PATH_ELEMENTS);
|
|
memcpy(token, otoken, sizeof(char*) * ntok);
|
|
|
|
if (ntok > 1) {
|
|
for (int i = 0; i < ntok - 1; i++)
|
|
{
|
|
if (!ts_strequal(token[i], "..") && ts_strequal(token[i + 1], ".."))
|
|
{
|
|
if (*token[i] == '\0')
|
|
continue;
|
|
while (i < ntok - 1)
|
|
{
|
|
token[i] = token[i + 2];
|
|
i++;
|
|
}
|
|
ntok -= 2;
|
|
i = -1; /* Restart enclosing for loop */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Reassemble string */
|
|
char *dst = path;
|
|
if (ntok == 0)
|
|
{
|
|
*dst++ = '.';
|
|
*dst = '\0';
|
|
}
|
|
else
|
|
{
|
|
if (token[0][0] == '\0')
|
|
{
|
|
int i;
|
|
for (i = 1; i < ntok && ts_strequal(token[i], ".."); i++)
|
|
;
|
|
if (i > 1)
|
|
{
|
|
int j;
|
|
for (j = 1; i < ntok; i++)
|
|
token[j++] = token[i];
|
|
ntok = j;
|
|
}
|
|
}
|
|
if (ntok == 1 && token[0][0] == '\0')
|
|
{
|
|
*dst++ = '/';
|
|
*dst = '\0';
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < ntok; i++)
|
|
{
|
|
char *src = token[i];
|
|
while ((*dst++ = *src++) != '\0')
|
|
;
|
|
*(dst - 1) = '/';
|
|
}
|
|
*(dst - 1) = '\0';
|
|
}
|
|
}
|
|
|
|
for(int i{0}; i < ontok; i++)
|
|
::free(otoken[i]);
|
|
}
|
|
|
|
std::string clnpath(const std::string_view& data) {
|
|
std::string result{data};
|
|
ts_clnpath2(result.data());
|
|
auto index = result.find((char) 0);
|
|
if(index != std::string::npos)
|
|
result.resize(index);
|
|
return result;
|
|
}
|
|
|
|
#ifdef CLN_EXEC
|
|
typedef struct p1_test_case
|
|
{
|
|
const char *input;
|
|
const char *output;
|
|
} p1_test_case;
|
|
|
|
/* This stress tests the cleaning, concentrating on the boundaries. */
|
|
static const p1_test_case p1_tests[] =
|
|
{
|
|
{ "/", "/", },
|
|
{ "//", "/", },
|
|
{ "///", "/", },
|
|
{ "/.", "/", },
|
|
{ "/./", "/", },
|
|
{ "/./.", "/", },
|
|
{ "/././.profile", "/.profile", },
|
|
{ "./", ".", },
|
|
{ "./.", ".", },
|
|
{ "././", ".", },
|
|
{ "./././.profile", ".profile", },
|
|
{ "abc/.", "abc", },
|
|
{ "abc/./def", "abc/def", },
|
|
{ "./abc", "abc", },
|
|
|
|
{ "//abcd///./abcd////", "/abcd/abcd", },
|
|
{ "//abcd///././../defg///ddd//.", "/abcd/../defg/ddd", },
|
|
{ "/abcd/./../././defg/./././ddd", "/abcd/../defg/ddd", },
|
|
{ "//abcd//././../defg///ddd//.///", "/abcd/../defg/ddd", },
|
|
|
|
/* Most of these are minimal interest in phase 1 */
|
|
{ "/usr/tmp/clnpath.c", "/usr/tmp/clnpath.c", },
|
|
{ "/usr/tmp/", "/usr/tmp", },
|
|
{ "/bin/..", "/bin/..", },
|
|
{ "bin/..", "bin/..", },
|
|
{ "/bin/.", "/bin", },
|
|
{ "sub/directory", "sub/directory", },
|
|
{ "sub/directory/file", "sub/directory/file", },
|
|
{ "/part1/part2/../.././../", "/part1/part2/../../..", },
|
|
{ "/.././../usr//.//bin/./cc", "/../../usr/bin/cc", },
|
|
};
|
|
|
|
static void p1_tester(const void *data)
|
|
{
|
|
const p1_test_case *test = (const p1_test_case *)data;
|
|
char buffer[256];
|
|
|
|
strcpy(buffer, test->input);
|
|
ts_clnpath(buffer);
|
|
if (strcmp(buffer, test->output) == 0)
|
|
printf("<<%s>> cleans to <<%s>>\n", test->input, buffer);
|
|
else
|
|
{
|
|
fprintf(stderr, "<<%s>> - unexpected output from clnpath()\n", test->input);
|
|
fprintf(stderr, "Wanted <<%s>>\n", test->output);
|
|
fprintf(stderr, "Actual <<%s>>\n", buffer);
|
|
}
|
|
}
|
|
|
|
typedef struct p2_test_case
|
|
{
|
|
const char *input;
|
|
const char *output;
|
|
} p2_test_case;
|
|
|
|
static const p2_test_case p2_tests[] =
|
|
{
|
|
{ "/abcd/../defg/ddd", "/defg/ddd" },
|
|
{ "/bin/..", "/" },
|
|
{ "bin/..", "." },
|
|
{ "/usr/bin/..", "/usr" },
|
|
{ "/usr/bin/../..", "/" },
|
|
{ "usr/bin/../..", "." },
|
|
{ "../part/of/../the/way", "../part/the/way" },
|
|
{ "/../part/of/../the/way", "/part/the/way" },
|
|
{ "part1/part2/../../part3", "part3" },
|
|
{ "part1/part2/../../../part3", "../part3" },
|
|
{ "/part1/part2/../../../part3", "/part3" },
|
|
{ "/part1/part2/../../../", "/" },
|
|
{ "/../../usr/bin/cc", "/usr/bin/cc" },
|
|
{ "../../usr/bin/cc", "../../usr/bin/cc" },
|
|
{ "part1/./part2/../../part3", "part3" },
|
|
{ "./part1/part2/../../../part3", "../part3" },
|
|
{ "/part1/part2/.././../../part3", "/part3" },
|
|
{ "/part1/part2/../.././../", "/" },
|
|
{ "/.././..//./usr///bin/cc/", "/usr/bin/cc" },
|
|
{nullptr, nullptr}
|
|
};
|
|
|
|
static void p2_tester(const void *data)
|
|
{
|
|
auto test = (const p2_test_case *)data;
|
|
char buffer[256];
|
|
|
|
strcpy(buffer, test->input);
|
|
ts_clnpath2(buffer);
|
|
if (strcmp(buffer, test->output) == 0)
|
|
printf("<<%s>> cleans to <<%s>>\n", test->input, buffer);
|
|
else
|
|
{
|
|
fprintf(stderr, "<<%s>> - unexpected output from clnpath2()\n", test->input);
|
|
fprintf(stderr, "Wanted <<%s>>\n", test->output);
|
|
fprintf(stderr, "Actual <<%s>>\n", buffer);
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
for(const auto& test : p1_tests) {
|
|
if(!test.input) break;
|
|
p1_tester(&test);
|
|
}
|
|
|
|
printf("------------------------------\n");
|
|
for(const auto& test : p2_tests) {
|
|
if(!test.input) break;
|
|
p2_tester(&test);
|
|
}
|
|
}
|
|
|
|
#endif |