1: <?php
2: namespace Dropbox;
3:
4: /**
5: * Path validation functions.
6: */
7: final class Path
8: {
9: /**
10: * Return whether the given path is a valid Dropbox path.
11: *
12: * @param string $path
13: * The path you want to check for validity.
14: *
15: * @return bool
16: * Whether the path was valid or not.
17: */
18: static function isValid($path)
19: {
20: $error = self::findError($path);
21: return ($error === null);
22: }
23:
24: /**
25: * Return whether the given path is a valid non-root Dropbox path.
26: * This is the same as {@link isValid} except <code>"/"</code> is not allowed.
27: *
28: * @param string $path
29: * The path you want to check for validity.
30: *
31: * @return bool
32: * Whether the path was valid or not.
33: */
34: static function isValidNonRoot($path)
35: {
36: $error = self::findErrorNonRoot($path);
37: return ($error === null);
38: }
39:
40: /**
41: * If the given path is a valid Dropbox path, return <code>null</code>,
42: * otherwise return an English string error message describing what is wrong with the path.
43: *
44: * @param string $path
45: * The path you want to check for validity.
46: *
47: * @return string|null
48: * If the path was valid, return <code>null</code>. Otherwise, returns
49: * an English string describing the problem.
50: */
51: static function findError($path)
52: {
53: $matchResult = preg_match('%^(?:
54: [\x09\x0A\x0D\x20-\x7E] # ASCII
55: | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
56: | \xE0[\xA0-\xBF][\x80-\xBD] # excluding overlongs, FFFE, and FFFF
57: | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
58: | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
59: )*$%xs', $path);
60:
61: if ($matchResult !== 1) {
62: return "must be valid UTF-8; BMP only, no surrogates, no U+FFFE or U+FFFF";
63: }
64:
65: if (\substr_compare($path, "/", 0, 1) !== 0) return "must start with \"/\"";
66: $l = strlen($path);
67: if ($l === 1) return null; // Special case for "/"
68:
69: if ($path[$l-1] === "/") return "must not end with \"/\"";
70:
71: // TODO: More checks.
72:
73: return null;
74: }
75:
76: /**
77: * If the given path is a valid non-root Dropbox path, return <code>null</code>,
78: * otherwise return an English string error message describing what is wrong with the path.
79: * This is the same as {@link findError} except <code>"/"</code> will yield an error message.
80: *
81: * @param string $path
82: * The path you want to check for validity.
83: *
84: * @return string|null
85: * If the path was valid, return <code>null</code>. Otherwise, returns
86: * an English string describing the problem.
87: */
88: static function findErrorNonRoot($path)
89: {
90: if ($path == "/") return "root path not allowed";
91: return self::findError($path);
92: }
93:
94: /**
95: * Return the last component of a path (the file or folder name).
96: *
97: * <code>
98: * Path::getName("/Misc/Notes.txt") // "Notes.txt"
99: * Path::getName("/Misc") // "Misc"
100: * Path::getName("/") // null
101: * </code>
102: *
103: * @param string $path
104: * The full path you want to get the last component of.
105: *
106: * @return null|string
107: * The last component of <code>$path</code> or <code>null</code> if the given
108: * <code>$path</code> was <code>"/"<code>.
109: */
110: static function getName($path)
111: {
112: $lastSlash = strrpos($path, "/");
113: // If the slash is the last character, give up.
114: if ($lastSlash === strlen($path) - 1) {
115: return null;
116: }
117: return substr($path, $lastSlash+1);
118: }
119:
120: /**
121: * @internal
122: *
123: * @param string $argName
124: * @param mixed $value
125: * @throws \InvalidArgumentException
126: */
127: static function checkArg($argName, $value)
128: {
129: if ($value === null) throw new \InvalidArgumentException("'$argName' must not be null");
130: if (!is_string($value)) throw new \InvalidArgumentException("'$argName' must be a string");
131: $error = self::findError($value);
132: if ($error !== null) throw new \InvalidArgumentException("'$argName'': bad path: $error: ".var_export($value, true));
133: }
134:
135: /**
136: * @internal
137: *
138: * @param string $argName
139: * @param mixed $value
140: * @throws \InvalidArgumentException
141: */
142: static function checkArgNonRoot($argName, $value)
143: {
144: if ($value === null) throw new \InvalidArgumentException("'$argName' must not be null");
145: if (!is_string($value)) throw new \InvalidArgumentException("'$argName' must be a string");
146: $error = self::findErrorNonRoot($value);
147: if ($error !== null) throw new \InvalidArgumentException("'$argName'': bad path: $error: ".var_export($value, true));
148: }
149: }
150: