| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "sdk/lib/fdio/cleanpath.h" |
| |
| #include <limits.h> |
| #include <string.h> |
| |
| namespace fdio_internal { |
| |
| #define IS_SEPARATOR(c) ((c) == '/' || (c) == 0) |
| |
| // Checks that if we increment this index forward, we'll |
| // still have enough space for a null terminator within |
| // PATH_MAX bytes. |
| #define CHECK_CAN_INCREMENT(i) \ |
| if (unlikely((i) + 1 >= PATH_MAX)) { \ |
| return ZX_ERR_BAD_PATH; \ |
| } |
| |
| // Cleans an input path, transforming it to out, according to the |
| // rules defined by "Lexical File Names in Plan 9 or Getting Dot-Dot Right", |
| // accessible at: https://9p.io/sys/doc/lexnames.html |
| // |
| // Code heavily inspired by Go's filepath.Clean function, from: |
| // https://golang.org/src/path/filepath/path.go |
| // |
| // out is expected to be PATH_MAX bytes long. |
| // Sets is_dir to 'true' if the path is a directory, and 'false' otherwise. |
| zx_status_t cleanpath(const char* in, char* out, size_t* outlen, bool* is_dir) { |
| if (in[0] == 0) { |
| strcpy(out, "."); |
| *outlen = 1; |
| *is_dir = true; |
| return ZX_OK; |
| } |
| |
| bool rooted = (in[0] == '/'); |
| size_t in_index = 0; // Index of the next byte to read |
| size_t out_index = 0; // Index of the next byte to write |
| |
| if (rooted) { |
| out[out_index++] = '/'; |
| in_index++; |
| *is_dir = true; |
| } |
| size_t dotdot = out_index; // The output index at which '..' cannot be cleaned further. |
| |
| while (in[in_index] != 0) { |
| *is_dir = true; |
| if (in[in_index] == '/') { |
| // 1. Reduce multiple slashes to a single slash |
| CHECK_CAN_INCREMENT(in_index); |
| in_index++; |
| } else if (in[in_index] == '.' && IS_SEPARATOR(in[in_index + 1])) { |
| // 2. Eliminate . path name elements (the current directory) |
| CHECK_CAN_INCREMENT(in_index); |
| in_index++; |
| } else if (in[in_index] == '.' && in[in_index + 1] == '.' && IS_SEPARATOR(in[in_index + 2])) { |
| CHECK_CAN_INCREMENT(in_index + 1); |
| in_index += 2; |
| if (out_index > dotdot) { |
| // 3. Eliminate .. path elements (the parent directory) and the element that |
| // precedes them. |
| out_index--; |
| while (out_index > dotdot && out[out_index] != '/') { |
| out_index--; |
| } |
| } else if (rooted) { |
| // 4. Eliminate .. elements that begin a rooted path, that is, replace /.. by / at |
| // the beginning of a path. |
| continue; |
| } else if (!rooted) { |
| if (out_index > 0) { |
| out[out_index++] = '/'; |
| } |
| // 5. Leave intact .. elements that begin a non-rooted path. |
| out[out_index++] = '.'; |
| out[out_index++] = '.'; |
| dotdot = out_index; |
| } |
| } else { |
| *is_dir = false; |
| if ((rooted && out_index != 1) || (!rooted && out_index != 0)) { |
| // Add '/' before normal path component, for non-root components. |
| out[out_index++] = '/'; |
| } |
| |
| while (!IS_SEPARATOR(in[in_index])) { |
| CHECK_CAN_INCREMENT(in_index); |
| out[out_index++] = in[in_index++]; |
| } |
| } |
| } |
| |
| if (out_index == 0) { |
| strcpy(out, "."); |
| *outlen = 1; |
| *is_dir = true; |
| return ZX_OK; |
| } |
| |
| // Append null character |
| *outlen = out_index; |
| out[out_index++] = 0; |
| return ZX_OK; |
| } |
| |
| } // namespace fdio_internal |