Skip to content

Commit 34b5768

Browse files
authored
Safer weakly_canonical and canonical improved implementations (#1933)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent 16f9607 commit 34b5768

File tree

6 files changed

+91
-8
lines changed

6 files changed

+91
-8
lines changed

src/lang/io/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME io)
1+
sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME io SOURCES io.cc)
22

33
if(SOURCEMETA_CORE_INSTALL)
44
sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME io)

src/lang/io/include/sourcemeta/core/io.h

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
#ifndef SOURCEMETA_CORE_IO_H_
22
#define SOURCEMETA_CORE_IO_H_
33

4+
#ifndef SOURCEMETA_CORE_IO_EXPORT
5+
#include <sourcemeta/core/io_export.h>
6+
#endif
7+
48
#include <cassert> // assert
59
#include <filesystem> // std::filesystem
610
#include <fstream> // std::basic_ifstream
@@ -17,6 +21,37 @@
1721

1822
namespace sourcemeta::core {
1923

24+
/// @ingroup io
25+
///
26+
/// A safe variant of `std::filesystem::canonical` that takes into account
27+
/// platform-specific oddities like FIFO on GNU/Linux. For example:
28+
///
29+
/// ```cpp
30+
/// #include <sourcemeta/core/io.h>
31+
/// #include <cassert>
32+
///
33+
/// const auto output{sourcemeta::core::canonical("/tmp/../foo.json")};
34+
/// assert(output == "/foo.json");
35+
/// ```
36+
SOURCEMETA_CORE_IO_EXPORT
37+
auto canonical(const std::filesystem::path &path) -> std::filesystem::path;
38+
39+
/// @ingroup io
40+
///
41+
/// A safe variant of `std::filesystem::weakly_canonical` that takes into
42+
/// account platform-specific oddities like FIFO on GNU/Linux. For example:
43+
///
44+
/// ```cpp
45+
/// #include <sourcemeta/core/io.h>
46+
/// #include <cassert>
47+
///
48+
/// const auto output{sourcemeta::core::weakly_canonical("/tmp/../foo.json")};
49+
/// assert(output == "/foo.json");
50+
/// ```
51+
SOURCEMETA_CORE_IO_EXPORT
52+
auto weakly_canonical(const std::filesystem::path &path)
53+
-> std::filesystem::path;
54+
2055
/// @ingroup io
2156
///
2257
/// A convenience function to open a stream from a file. For example:
@@ -37,12 +72,7 @@ auto read_file(const std::filesystem::path &path)
3772
std::make_error_code(std::errc::is_a_directory));
3873
}
3974

40-
std::ifstream stream{
41-
// On Linux, FIFO files (like /dev/fd/XX due to process substitution)
42-
// cannot be
43-
// made canonical
44-
// See https://github.com/sourcemeta/jsonschema/issues/252
45-
std::filesystem::is_fifo(path) ? path : std::filesystem::canonical(path)};
75+
std::ifstream stream{sourcemeta::core::canonical(path)};
4676
stream.exceptions(std::ifstream::badbit);
4777
assert(!stream.fail());
4878
assert(stream.is_open());

src/lang/io/io.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <sourcemeta/core/io.h>
2+
3+
namespace sourcemeta::core {
4+
5+
auto canonical(const std::filesystem::path &path) -> std::filesystem::path {
6+
// On Linux, FIFO files (like /dev/fd/XX due to process substitution)
7+
// cannot be made canonical
8+
// See https://github.com/sourcemeta/jsonschema/issues/252
9+
return std::filesystem::is_fifo(path) ? path
10+
: std::filesystem::canonical(path);
11+
}
12+
13+
auto weakly_canonical(const std::filesystem::path &path)
14+
-> std::filesystem::path {
15+
// On Linux, FIFO files (like /dev/fd/XX due to process substitution)
16+
// cannot be made canonical
17+
// See https://github.com/sourcemeta/jsonschema/issues/252
18+
return std::filesystem::is_fifo(path)
19+
? path
20+
: std::filesystem::weakly_canonical(path);
21+
}
22+
23+
} // namespace sourcemeta::core

test/io/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
sourcemeta_googletest(NAMESPACE sourcemeta PROJECT core NAME io
2-
SOURCES io_read_file_test.cc)
2+
SOURCES io_canonical_test.cc io_weakly_canonical_test.cc io_read_file_test.cc)
33

44
target_link_libraries(sourcemeta_core_io_unit
55
PRIVATE sourcemeta::core::io)

test/io/io_canonical_test.cc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <sourcemeta/core/io.h>
4+
5+
TEST(IO_canonical, test_txt) {
6+
const auto path{sourcemeta::core::canonical(
7+
std::filesystem::path{TEST_DIRECTORY} / ".." / "io" / "test.txt")};
8+
EXPECT_EQ(path, std::filesystem::path{TEST_DIRECTORY} / "test.txt");
9+
}
10+
11+
TEST(IO_canonical, not_exists) {
12+
EXPECT_THROW(sourcemeta::core::canonical(
13+
std::filesystem::path{TEST_DIRECTORY} / "foo.txt"),
14+
std::filesystem::filesystem_error);
15+
}

test/io/io_weakly_canonical_test.cc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <sourcemeta/core/io.h>
4+
5+
TEST(IO_weakly_canonical, test_txt) {
6+
const auto path{sourcemeta::core::weakly_canonical(
7+
std::filesystem::path{TEST_DIRECTORY} / ".." / "io" / "test.txt")};
8+
EXPECT_EQ(path, std::filesystem::path{TEST_DIRECTORY} / "test.txt");
9+
}
10+
11+
TEST(IO_weakly_canonical, not_exists) {
12+
const auto path{sourcemeta::core::weakly_canonical(
13+
std::filesystem::path{TEST_DIRECTORY} / "foo.txt")};
14+
EXPECT_EQ(path, std::filesystem::path{TEST_DIRECTORY} / "foo.txt");
15+
}

0 commit comments

Comments
 (0)