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
 |