KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_wx_filename.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
24
25#include <boost/test/tools/old/interface.hpp>
27
28// Code under test
29#include <wx_filename.h>
30
31#include <wx/arrstr.h>
32
33#include <string>
34#include <utility>
35#include <vector>
36
40BOOST_AUTO_TEST_SUITE( WxFilename )
41
42
44{
45 // Ctor params
46 std::string m_path;
47 std::string m_name;
48
49 // Split results
50 std::string m_exp_name;
51 std::string m_exp_full_name;
52 std::string m_exp_path;
53 std::string m_exp_full_path;
54};
55
56
57// clang-format off
58static const std::vector<WX_FILENAME_SPLIT_CASE> split_cases = {
59 {
60 "",
61 "",
62 "",
63 "",
64 "",
65 "/", // This doesn't look right...
66 },
67 {
68 "",
69 "name.ext",
70 "name",
71 "name.ext",
72 "",
73 "/name.ext", // This doesn't look right...
74 },
75 {
76 "/tmp/example",
77 "",
78 "",
79 "",
80 "/tmp/example",
81 "/tmp/example/",
82 },
83 {
84 "/tmp/example",
85 "name.ext",
86 "name",
87 "name.ext",
88 "/tmp/example",
89 "/tmp/example/name.ext",
90 },
91 {
92 "/tmp/example",
93 "name", // no extension
94 "name",
95 "name",
96 "/tmp/example",
97 "/tmp/example/name",
98 },
99 {
100 "/tmp/example",
101 "name.ext1.ext2", // two extensions
102 "name.ext1", // remove the first one
103 "name.ext1.ext2",
104 "/tmp/example",
105 "/tmp/example/name.ext1.ext2",
106 },
107};
108// clang-format on
109
114{
115 for( const auto& c : split_cases )
116 {
117 std::stringstream ss;
118 ss << c.m_path << ", " << c.m_name;
119 BOOST_TEST_CONTEXT( ss.str() )
120 {
121 // Const: all methods called must be const
122 const WX_FILENAME wx_fn( c.m_path, c.m_name );
123
124 BOOST_CHECK_EQUAL( c.m_exp_name, wx_fn.GetName() );
125 BOOST_CHECK_EQUAL( c.m_exp_full_name, wx_fn.GetFullName() );
126 BOOST_CHECK_EQUAL( c.m_exp_path, wx_fn.GetPath() );
127 BOOST_CHECK_EQUAL( c.m_exp_full_path, wx_fn.GetFullPath() );
128 }
129 }
130}
131
132
133// Directory traversal guards for untrusted archive entry names
134static const std::vector<wxString> hostile_entry_names = {
135 wxT( "../evil" ),
136 wxT( "a/../../evil" ),
137 wxT( "plugins/../../../../tmp/PCM_PWNED" ), // issue 25227, PCM package install
138 wxT( "../../../../tmp/PWNED" ), // issue 25175, project unarchive
139 wxT( "a/b/../../../evil" ),
140 wxT( ".." ),
141 wxT( "/etc/passwd" ),
142 wxT( "//etc/passwd" ),
143 wxT( "C:/evil" ),
144 wxT( "C:evil" ),
145 wxT( "..\\evil" ),
146 wxT( "plugins\\..\\..\\..\\tmp\\PCM_PWNED" ),
147 wxT( "\\\\server\\share\\evil" ),
148 wxT( "" ),
149};
150
151
152// ".." and "." are only special as whole components, so "a..b" and "...leading" are legal.
153static const std::vector<std::pair<wxString, wxString>> benign_entry_names = {
154 { wxT( "board.kicad_pcb" ), wxT( "board.kicad_pcb" ) },
155 { wxT( "./board.kicad_pcb" ), wxT( "board.kicad_pcb" ) },
156 { wxT( "sub/board.kicad_pcb" ), wxT( "sub/board.kicad_pcb" ) },
157 { wxT( "sub//board.kicad_pcb" ), wxT( "sub/board.kicad_pcb" ) },
158 { wxT( "sub/./deep/board.kicad_pcb" ), wxT( "sub/deep/board.kicad_pcb" ) },
159 { wxT( "a..b/c.kicad_sch" ), wxT( "a..b/c.kicad_sch" ) },
160 { wxT( "...leading/x.txt" ), wxT( "...leading/x.txt" ) },
161};
162
163
164BOOST_AUTO_TEST_CASE( SplitArchiveEntryName_RejectsTraversal )
165{
166 for( const wxString& name : hostile_entry_names )
167 {
169 {
170 wxArrayString parts;
171
172 BOOST_CHECK( !WX_FILENAME::SplitArchiveEntryName( name, parts ) );
173 }
174 }
175}
176
177
178BOOST_AUTO_TEST_CASE( SplitArchiveEntryName_AcceptsRelativeNames )
179{
180 for( const auto& [name, expected] : benign_entry_names )
181 {
183 {
184 wxArrayString parts;
185
187 BOOST_CHECK_EQUAL( wxJoin( parts, '/', (wxChar) 0 ), expected );
188 }
189 }
190}
191
192
193BOOST_AUTO_TEST_CASE( ResolveArchiveEntryPath_RejectsTraversal )
194{
195 const wxString dest = wxFileName::GetTempDir() + wxFileName::GetPathSeparator() + wxT( "kicad-unarchive-dest" );
196
197 for( const wxString& name : hostile_entry_names )
198 {
200 {
201 wxFileName resolved;
202
203 BOOST_CHECK( !WX_FILENAME::ResolveArchiveEntryPath( dest, name, resolved ) );
204 }
205 }
206}
207
208
209BOOST_AUTO_TEST_CASE( ResolveArchiveEntryPath_StaysBelowDestination )
210{
211 const wxString dest = wxFileName::GetTempDir() + wxFileName::GetPathSeparator() + wxT( "kicad-unarchive-dest" );
212 const wxString destWithSep = wxFileName::DirName( dest ).GetPathWithSep();
213
214 for( const auto& [name, expected] : benign_entry_names )
215 {
217 {
218 wxFileName resolved;
219
221 BOOST_CHECK( resolved.GetFullPath().StartsWith( destWithSep ) );
222
223 wxFileName relative = resolved;
224 relative.MakeRelativeTo( dest );
225 BOOST_CHECK_EQUAL( relative.GetFullPath( wxPATH_UNIX ), expected );
226 }
227 }
228
229 // A sibling directory that merely shares a prefix with the destination is still outside.
230 wxFileName resolved;
231 BOOST_REQUIRE( WX_FILENAME::ResolveArchiveEntryPath( dest, wxT( "x" ), resolved ) );
232 BOOST_CHECK( !WX_FILENAME::ResolveArchiveEntryPath( dest, wxT( "../kicad-unarchive-dest-evil/x" ), resolved ) );
233}
234
235
236BOOST_AUTO_TEST_CASE( IsSafeChildPath_RejectsTraversal )
237{
238 for( const wxString& name : hostile_entry_names )
239 {
241 {
242 BOOST_CHECK( !WX_FILENAME::IsSafeChildPath( name ) );
243 }
244 }
245
246 // A child is exactly one component: a name that would need normalizing is rejected rather
247 // than rewritten, so callers always get back the name they asked for.
248 const std::vector<wxString> nested_names = {
249 wxT( "sub/board.kicad_pcb" ),
250 wxT( "./board.kicad_pcb" ),
251 wxT( "sub/./deep/board.kicad_pcb" ),
252 wxT( "board.kicad_pcb/" ),
253 };
254
255 for( const wxString& name : nested_names )
256 {
258 {
259 BOOST_CHECK( !WX_FILENAME::IsSafeChildPath( name ) );
260 }
261 }
262}
263
264
265BOOST_AUTO_TEST_CASE( IsSafeChildPath_AcceptsSingleComponentNames )
266{
267 // ".." and "." are only special as whole components, so these are all legal child names.
268 const std::vector<wxString> child_names = {
269 wxT( "board.kicad_pcb" ),
270 wxT( "a..b" ),
271 wxT( "...leading" ),
272 wxT( "name with spaces.txt" ),
273 };
274
275 for( const wxString& name : child_names )
276 {
278 {
279 BOOST_CHECK( WX_FILENAME::IsSafeChildPath( name ) );
280 }
281 }
282}
283
const char * name
A wrapper around a wxFileName which is much more performant with a subset of the API.
Definition wx_filename.h:46
wxString GetPath() const
wxString GetName() const
static bool IsSafeChildPath(const wxString &aChild)
Check that a child path is safe to append to a parent directory.
static bool ResolveArchiveEntryPath(const wxString &aDestDir, const wxString &aEntryName, wxFileName &aResult)
Resolve an untrusted archive entry name against the directory it is extracted into.
static bool SplitArchiveEntryName(const wxString &aEntryName, wxArrayString &aParts)
Split an untrusted archive entry name into its path components.
wxString GetFullPath() const
wxString GetFullName() const
Declare the test suite.
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I expected(15, 30, 45)
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_CHECK_EQUAL(result, "25.4")
static const std::vector< WX_FILENAME_SPLIT_CASE > split_cases
static const std::vector< wxString > hostile_entry_names
static const std::vector< std::pair< wxString, wxString > > benign_entry_names
BOOST_AUTO_TEST_CASE(Split)
Check the various split cases work correctly.