return 1;
}
+/* Computes the depth of the given path, excluding any basename. This
+ * assumes that the path does not walk through any symlinks.
+ *
+ * For use with unsafe_symlink. */
+int path_depth(const char *path)
+{
+ const char *name, *slash;
+ int depth = 0;
+
+ for (name = path; (slash = strchr(name, '/')) != 0; name = slash+1) {
+ /* ".." segment starts the count over. "." segment is ignored. */
+ if (strncmp(name, "./", 2) == 0)
+ ;
+ else if (strncmp(name, "../", 3) == 0)
+ depth = 0;
+ else
+ depth++;
+ while (slash[1] == '/') slash++; /* just in case path isn't clean */
+ }
+ if (strcmp(name, "..") == 0)
+ depth = 0;
+
+ return depth;
+}
+
/* Determine if a symlink points outside the current directory tree.
* This is considered "unsafe" because e.g. when mirroring somebody
* else's machine it might allow them to establish a symlink to
*
* "dest" is the target of the symlink in question.
*
- * "src" is the top source directory currently applicable at the level
- * of the referenced symlink. This is usually the symlink's full path
- * (including its name), as referenced from the root of the transfer. */
-int unsafe_symlink(const char *dest, const char *src)
+ * "depth" is the depth of the symlink inside the current directory
+ * tree. It can be computed with "path_depth". */
+int unsafe_symlink(const char *dest, int depth)
{
const char *name, *slash;
- int depth = 0;
/* all absolute and null symlinks are unsafe */
if (!dest || !*dest || *dest == '/')
return 1;
- /* find out what our safety margin is */
- for (name = src; (slash = strchr(name, '/')) != 0; name = slash+1) {
- /* ".." segment starts the count over. "." segment is ignored. */
- if (*name == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))) {
- if (name[1] == '.')
- depth = 0;
- } else
- depth++;
- while (slash[1] == '/') slash++; /* just in case src isn't clean */
- }
- if (*name == '.' && name[1] == '.' && name[2] == '\0')
- depth = 0;
-
for (name = dest; (slash = strchr(name, '/')) != 0; name = slash+1) {
- if (*name == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))) {
- if (name[1] == '.') {
- /* if at any point we go outside the current directory
- then stop - it is unsafe */
- if (--depth < 0)
- return 1;
- }
+ if (strncmp(name, "./", 2) == 0)
+ ;
+ else if (strncmp(name, "../", 3) == 0) {
+ /* if at any point we go outside the current directory
+ then stop - it is unsafe */
+ if (--depth < 0)
+ return 1;
} else
+ /* XXX: We could be walking through another symlink! */
depth++;
while (slash[1] == '/') slash++;
}
- if (*name == '.' && name[1] == '.' && name[2] == '\0')
- depth--;
+ if (strcmp(name, "..") == 0) {
+ if (--depth < 0)
+ return 1;
+ }
- return depth < 0;
+ return 0;
}
/* Return the date and time as a string. Some callers tweak returned buf. */