Turns multiple adjacent slashes into a single slash (possible exception: * the preserving of two leading slashes at the start), drops all leading or * interior "." elements unless CFN_KEEP_DOT_DIRS is flagged. Will also drop * a trailing '.' after a '/' if CFN_DROP_TRAILING_DOT_DIR is flagged, removes * a trailing slash (perhaps after removing the aforementioned dot) unless * CFN_KEEP_TRAILI
| 958 | * (except at the start) if CFN_COLLAPSE_DOT_DOT_DIRS is flagged. If the |
| 959 | * resulting name would be empty, returns ".". */ |
| 960 | int clean_fname(char *name, int flags) |
| 961 | { |
| 962 | char *limit = name, *t = name, *f = name; |
| 963 | int anchored; |
| 964 | |
| 965 | if (!name) |
| 966 | return 0; |
| 967 | |
| 968 | #define DOT_IS_DOT_DOT_DIR(bp) (bp[1] == '.' && (bp[2] == '/' || !bp[2])) |
| 969 | |
| 970 | if ((anchored = *f == '/') != 0) { |
| 971 | *t++ = *f++; |
| 972 | #ifdef __CYGWIN__ |
| 973 | /* If there are exactly 2 slashes at the start, preserve |
| 974 | * them. Would break daemon excludes unless the paths are |
| 975 | * really treated differently, so used this sparingly. */ |
| 976 | if (*f == '/' && f[1] != '/') |
| 977 | *t++ = *f++; |
| 978 | #endif |
| 979 | } else if (flags & CFN_KEEP_DOT_DIRS && *f == '.' && f[1] == '/') { |
| 980 | *t++ = *f++; |
| 981 | *t++ = *f++; |
| 982 | } else if (flags & CFN_REFUSE_DOT_DOT_DIRS && *f == '.' && DOT_IS_DOT_DOT_DIR(f)) |
| 983 | return -1; |
| 984 | while (*f) { |
| 985 | /* discard extra slashes */ |
| 986 | if (*f == '/') { |
| 987 | f++; |
| 988 | continue; |
| 989 | } |
| 990 | if (*f == '.') { |
| 991 | /* discard interior "." dirs */ |
| 992 | if (f[1] == '/' && !(flags & CFN_KEEP_DOT_DIRS)) { |
| 993 | f += 2; |
| 994 | continue; |
| 995 | } |
| 996 | if (f[1] == '\0' && flags & CFN_DROP_TRAILING_DOT_DIR) |
| 997 | break; |
| 998 | /* collapse ".." dirs */ |
| 999 | if (flags & (CFN_COLLAPSE_DOT_DOT_DIRS|CFN_REFUSE_DOT_DOT_DIRS) && DOT_IS_DOT_DOT_DIR(f)) { |
| 1000 | char *s = t - 1; |
| 1001 | if (flags & CFN_REFUSE_DOT_DOT_DIRS) |
| 1002 | return -1; |
| 1003 | if (s == name && anchored) { |
| 1004 | f += 2; |
| 1005 | continue; |
| 1006 | } |
| 1007 | /* backing up for ".." — avoid reading before 'name' */ |
| 1008 | while (s > limit && s[-1] != '/') |
| 1009 | s--; |
| 1010 | |
| 1011 | /* If found prior '/', or we reached the start, adjust t. */ |
| 1012 | if (s != t - 1 && (s <= name || *s == '/')) { |
| 1013 | t = (s == name) ? name : s + 1; |
| 1014 | f += 2; |
| 1015 | continue; |
| 1016 | } |
| 1017 | limit = t + 2; |
no outgoing calls
no test coverage detected