/** * Normalize the given absolute path. * * This includes: * - Uppercase the drive letter if there is one. * - Remove redundant slashes after the drive spec. * - resolve all ./, .\, ../ and ..\ sequences. * - DOS style paths that start with a drive letter will have * \ as the separator. * @param string $path Path to normalize. * @return string */ function normalize($path) { $path = (string) $path; $orig = $path; $path = str_replace('/', DIRECTORY_SEPARATOR, str_replace('\\', DIRECTORY_SEPARATOR, $path)); // make sure we are dealing with an absolute path if (!StringHelper::startsWith(DIRECTORY_SEPARATOR, $path) && !(strlen($path) >= 2 && Character::isLetter($path[0]) && $path[1] === ':')) { throw new IOException("{$path} is not an absolute path"); } $dosWithDrive = false; $root = null; // Eliminate consecutive slashes after the drive spec if (strlen($path) >= 2 && Character::isLetter($path[0]) && $path[1] === ':') { $dosWithDrive = true; $ca = str_replace('/', '\\', $path); $ca = StringHelper::toCharArray($ca); $path = strtoupper($ca[0]) . ':'; for ($i = 2, $_i = count($ca); $i < $_i; $i++) { if ($ca[$i] !== '\\' || $ca[$i] === '\\' && $ca[$i - 1] !== '\\') { $path .= $ca[$i]; } } $path = str_replace('\\', DIRECTORY_SEPARATOR, $path); if (strlen($path) == 2) { $root = $path; $path = ""; } else { $root = substr($path, 0, 3); $path = substr($path, 3); } } else { if (strlen($path) == 1) { $root = DIRECTORY_SEPARATOR; $path = ""; } else { if ($path[1] == DIRECTORY_SEPARATOR) { // UNC drive $root = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; $path = substr($path, 2); } else { $root = DIRECTORY_SEPARATOR; $path = substr($path, 1); } } } $s = array(); array_push($s, $root); $tok = strtok($path, DIRECTORY_SEPARATOR); while ($tok !== false) { $thisToken = $tok; if ("." === $thisToken) { $tok = strtok(DIRECTORY_SEPARATOR); continue; } elseif (".." === $thisToken) { if (count($s) < 2) { // using '..' in path that is too short throw new IOException("Cannot resolve path: {$orig}"); } else { array_pop($s); } } else { // plain component array_push($s, $thisToken); } $tok = strtok(DIRECTORY_SEPARATOR); } $sb = ""; for ($i = 0, $_i = count($s); $i < $_i; $i++) { if ($i > 1) { // not before the filesystem root and not after it, since root // already contains one $sb .= DIRECTORY_SEPARATOR; } $sb .= (string) $s[$i]; } $path = (string) $sb; if ($dosWithDrive === true) { $path = str_replace('/', '\\', $path); } return $path; }
/** * Tests whether or not a string matches against a pattern. * The pattern may contain two special characters:<br> * '*' means zero or more characters<br> * '?' means one and only one character * * @param pattern The pattern to match against. * Must not be <code>null</code>. * @param str The string which must be matched against the pattern. * Must not be <code>null</code>. * @param isCaseSensitive Whether or not matching should be performed * case sensitively. * * * @return <code>true</code> if the string matches against the pattern, * or <code>false</code> otherwise. */ public static function match($pattern, $str, $isCaseSensitive = true) { $patArr = StringHelper::toCharArray($pattern); $strArr = StringHelper::toCharArray($str); $patIdxStart = 0; $patIdxEnd = count($patArr) - 1; $strIdxStart = 0; $strIdxEnd = count($strArr) - 1; $containsStar = false; for ($i = 0, $size = count($patArr); $i < $size; $i++) { if ($patArr[$i] == '*') { $containsStar = true; break; } } if (!$containsStar) { // No '*'s, so we make a shortcut if ($patIdxEnd != $strIdxEnd) { return false; // Pattern and string do not have the same size } for ($i = 0; $i <= $patIdxEnd; $i++) { $ch = $patArr[$i]; if ($ch != '?') { if ($isCaseSensitive && $ch !== $strArr[$i]) { return false; // Character mismatch } if (!$isCaseSensitive && strtoupper($ch) !== strtoupper($strArr[$i])) { return false; // Character mismatch } } } return true; // String matches against pattern } if ($patIdxEnd == 0) { return true; // Pattern contains only '*', which matches anything } // Process characters before first star while (($ch = $patArr[$patIdxStart]) != '*' && $strIdxStart <= $strIdxEnd) { if ($ch != '?') { if ($isCaseSensitive && $ch !== $strArr[$strIdxStart]) { return false; // Character mismatch } if (!$isCaseSensitive && strtoupper($ch) !== strtoupper($strArr[$strIdxStart])) { return false; // Character mismatch } } $patIdxStart++; $strIdxStart++; } if ($strIdxStart > $strIdxEnd) { // All characters in the string are used. Check if only '*'s are // left in the pattern. If so, we succeeded. Otherwise failure. for ($i = $patIdxStart; $i <= $patIdxEnd; $i++) { if ($patArr[$i] != '*') { return false; } } return true; } // Process characters after last star while (($ch = $patArr[$patIdxEnd]) != '*' && $strIdxStart <= $strIdxEnd) { if ($ch != '?') { if ($isCaseSensitive && $ch !== $strArr[$strIdxEnd]) { return false; // Character mismatch } if (!$isCaseSensitive && strtoupper($ch) !== strtoupper($strArr[$strIdxEnd])) { return false; // Character mismatch } } $patIdxEnd--; $strIdxEnd--; } if ($strIdxStart > $strIdxEnd) { // All characters in the string are used. Check if only '*'s are // left in the pattern. If so, we succeeded. Otherwise failure. for ($i = $patIdxStart; $i <= $patIdxEnd; $i++) { if ($patArr[$i] != '*') { return false; } } return true; } // process pattern between stars. padIdxStart and patIdxEnd point // always to a '*'. while ($patIdxStart !== $patIdxEnd && $strIdxStart <= $strIdxEnd) { $patIdxTmp = -1; for ($i = $patIdxStart + 1; $i <= $patIdxEnd; $i++) { if ($patArr[$i] == '*') { $patIdxTmp = $i; break; } } if ($patIdxTmp === $patIdxStart + 1) { // Two stars next to each other, skip the first one. $patIdxStart++; continue; } // Find the pattern between padIdxStart & padIdxTmp in str between // strIdxStart & strIdxEnd $patLength = $patIdxTmp - $patIdxStart - 1; $strLength = $strIdxEnd - $strIdxStart + 1; $foundIdx = -1; //strLoop: for ($i = 0; $i <= $strLength - $patLength; $i++) { for ($j = 0; $j < $patLength; $j++) { $ch = $patArr[$patIdxStart + $j + 1]; if ($ch != '?') { if ($isCaseSensitive && $ch !== $strArr[$strIdxStart + $i + $j]) { continue 2; //continue to strLoop: } if (!$isCaseSensitive && strtoupper($ch) !== strtoupper($strArr[$strIdxStart + $i + $j])) { continue 2; //continue to strLoop: } } } // only reached if sub loop completed w/o invoking continue 2 $foundIdx = $strIdxStart + $i; break; } if ($foundIdx == -1) { return false; } $patIdxStart = $patIdxTmp; $strIdxStart = $foundIdx + $patLength; } // All characters in the string are used. Check if only '*'s are left // in the pattern. If so, we succeeded. Otherwise failure. for ($i = $patIdxStart; $i <= $patIdxEnd; $i++) { if ($patArr[$i] != '*') { return false; } } return true; }