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
							 |