1 /*
2 * This program source code file is part kicad2mcad
3 *
4 * Copyright (C) 2015-2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
5 * Copyright (C) 2020-2021 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25 #include <cstdlib>
26 #include <cstring>
27 #include <fstream>
28 #include <iostream>
29 #include <mutex>
30 #include <sstream>
31
32 #include <wx/fileconf.h>
33 #include <wx/filename.h>
34 #include <wx/log.h>
35 #include <wx/thread.h>
36 #include <wx/msgdlg.h>
37 #include <wx/stdpaths.h>
38
39 #include "3d_resolver.h"
40
41 // configuration file version
42 #define CFGFILE_VERSION 1
43 #define S3D_RESOLVER_CONFIG "ExportPaths.cfg"
44
45 // flag bits used to track different one-off messages to users
46 #define ERRFLG_ALIAS (1)
47 #define ERRFLG_RELPATH (2)
48 #define ERRFLG_ENVPATH (4)
49
50
51 /**
52 * Flag to enable plugin loader trace output.
53 *
54 * @ingroup trace_env_vars
55 */
56 const wxChar* const trace3dResolver = wxT( "KICAD_3D_RESOLVER" );
57
58
59 static std::mutex mutex3D_resolver;
60
61
62 static bool getHollerith( const std::string& aString, size_t& aIndex, wxString& aResult );
63
64
S3D_RESOLVER()65 S3D_RESOLVER::S3D_RESOLVER()
66 {
67 m_errflags = 0;
68 }
69
70
Set3DConfigDir(const wxString & aConfigDir)71 bool S3D_RESOLVER::Set3DConfigDir( const wxString& aConfigDir )
72 {
73 createPathList();
74
75 return true;
76 }
77
78
createPathList(void)79 bool S3D_RESOLVER::createPathList( void )
80 {
81 if( !m_Paths.empty() )
82 return true;
83
84 readPathList();
85
86 if( m_Paths.empty() )
87 return false;
88
89 #ifdef DEBUG
90 wxLogTrace( trace3dResolver, " * [3D model] search paths:\n" );
91
92 for( const SEARCH_PATH& searchPath : m_Paths )
93 wxLogTrace( trace3dResolver, " + '%s'\n", searchPath.m_Pathexp );
94 #endif
95
96 return true;
97 }
98
99
ResolvePath(const wxString & aFileName,std::vector<wxString> & aSearchedPaths)100 wxString S3D_RESOLVER::ResolvePath( const wxString& aFileName,
101 std::vector<wxString>& aSearchedPaths )
102 {
103 std::lock_guard<std::mutex> lock( mutex3D_resolver );
104
105 if( aFileName.empty() )
106 return wxEmptyString;
107
108 if( m_Paths.empty() )
109 createPathList();
110
111 // look up the filename in the internal filename map
112 std::map<wxString, wxString, S3D::rsort_wxString>::iterator mi;
113 mi = m_NameMap.find( aFileName );
114
115 if( mi != m_NameMap.end() )
116 return mi->second;
117
118 // first attempt to use the name as specified:
119 wxString tname = aFileName;
120
121 #ifdef _WIN32
122 // translate from KiCad's internal UNIX-like path to MSWin paths
123 tname.Replace( "/", "\\" );
124 #endif
125
126 // Note: variable expansion must preferably be performed via a threadsafe wrapper for the
127 // getenv() system call. If we allow the wxFileName::Normalize() routine to perform expansion
128 // then we will have a race condition since wxWidgets does not assure a threadsafe wrapper
129 // for getenv().
130 if( tname.StartsWith( "${" ) || tname.StartsWith( "$(" ) )
131 tname = expandVars( tname );
132
133 wxFileName tmpFN( tname );
134
135 // in the case of absolute filenames we don't store a map item
136 if( !aFileName.StartsWith( "${" ) && !aFileName.StartsWith( "$(" ) && tmpFN.IsAbsolute() )
137 {
138 if( tmpFN.FileExists() )
139 {
140 tmpFN.Normalize();
141 return tmpFN.GetFullPath();
142 }
143 else
144 {
145 aSearchedPaths.push_back( tmpFN.GetFullPath() );
146 }
147 }
148
149 // this case covers full paths, leading expanded vars, and paths relative to the current
150 // working directory (which is not necessarily the current project directory)
151 tmpFN.Normalize();
152
153 if( tmpFN.FileExists() )
154 {
155 tname = tmpFN.GetFullPath();
156 m_NameMap[ aFileName ] = tname;
157
158 // special case: if a path begins with ${ENV_VAR} but is not in the resolver's path list
159 // then add it
160 if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
161 checkEnvVarPath( aFileName );
162
163 return tname;
164 }
165 else if( tmpFN.GetFullPath() != aFileName )
166 {
167 aSearchedPaths.push_back( tmpFN.GetFullPath() );
168 }
169
170 // if a path begins with ${ENV_VAR}/$(ENV_VAR) and is not resolved then the file either does
171 // not exist or the ENV_VAR is not defined
172 if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
173 {
174 m_errflags |= ERRFLG_ENVPATH;
175 return aFileName;
176 }
177
178 // at this point aFileName is:
179 // a. an aliased shortened name or
180 // b. cannot be determined
181
182 // check the path relative to the current project directory;
183 // NB: this is not necessarily the same as the current working directory, which has already
184 // been checked. This case accounts for partial paths which do not contain ${KIPRJMOD}.
185 // This check is performed before checking the path relative to ${KICAD6_3DMODEL_DIR} so that
186 // users can potentially override a model within ${KICAD6_3DMODEL_DIR}.
187 if( !m_Paths.empty() && !m_Paths.begin()->m_Pathexp.empty() && !tname.StartsWith( ":" ) )
188 {
189 tmpFN.Assign( m_Paths.begin()->m_Pathexp, "" );
190 wxString fullPath = tmpFN.GetPathWithSep() + tname;
191
192 if( fullPath.StartsWith( "${" ) || fullPath.StartsWith( "$(" ) )
193 fullPath = expandVars( fullPath );
194
195 tmpFN.Assign( fullPath );
196 tmpFN.Normalize();
197
198 if( tmpFN.FileExists() )
199 {
200 tname = tmpFN.GetFullPath();
201 m_NameMap[ aFileName ] = tname;
202 return tname;
203 }
204 else if( tmpFN.GetFullPath() != aFileName )
205 {
206 aSearchedPaths.push_back( tmpFN.GetFullPath() );
207 }
208 }
209
210 // check the partial path relative to ${KICAD6_3DMODEL_DIR} (legacy behavior)
211 if( !tname.Contains( ":" ) )
212 {
213 wxFileName fpath;
214 wxString fullPath( "${KICAD6_3DMODEL_DIR}" );
215 fullPath.Append( fpath.GetPathSeparator() );
216 fullPath.Append( tname );
217 fullPath = expandVars( fullPath );
218 fpath.Assign( fullPath );
219 fpath.Normalize();
220
221 if( fpath.FileExists() )
222 {
223 tname = fpath.GetFullPath();
224 m_NameMap[ aFileName ] = tname;
225 return tname;
226 }
227 else
228 {
229 aSearchedPaths.push_back( fpath.GetFullPath() );
230 }
231 }
232
233 // at this point the filename must contain an alias or else it is invalid
234 wxString alias; // the alias portion of the short filename
235 wxString relpath; // the path relative to the alias
236
237 if( !SplitAlias( tname, alias, relpath ) )
238 {
239 // this can happen if the file was intended to be relative to ${KICAD6_3DMODEL_DIR}
240 // but ${KICAD6_3DMODEL_DIR} is not set or is incorrect.
241 m_errflags |= ERRFLG_RELPATH;
242 return aFileName;
243 }
244
245 for( const SEARCH_PATH& path : m_Paths )
246 {
247 // ${ENV_VAR} paths have already been checked; skip them
248 if( path.m_Alias.StartsWith( "${" ) || path.m_Alias.StartsWith( "$(" ) )
249 continue;
250
251 if( path.m_Alias == alias && !path.m_Pathexp.empty() )
252 {
253 wxFileName fpath( wxFileName::DirName( path.m_Pathexp ) );
254 wxString fullPath = fpath.GetPathWithSep() + relpath;
255
256 if( fullPath.StartsWith( "${") || fullPath.StartsWith( "$(" ) )
257 fullPath = expandVars( fullPath );
258
259 wxFileName tmp( fullPath );
260 tmp.Normalize();
261
262 if( tmp.FileExists() )
263 {
264 tname = tmp.GetFullPath();
265 m_NameMap[ aFileName ] = tname;
266 return tname;
267 }
268 else
269 {
270 aSearchedPaths.push_back( tmp.GetFullPath() );
271 }
272 }
273 }
274
275 m_errflags |= ERRFLG_ALIAS;
276 return aFileName;
277 }
278
279
addPath(const SEARCH_PATH & aPath)280 bool S3D_RESOLVER::addPath( const SEARCH_PATH& aPath )
281 {
282 if( aPath.m_Alias.empty() || aPath.m_Pathvar.empty() )
283 return false;
284
285 std::lock_guard<std::mutex> lock( mutex3D_resolver );
286
287 SEARCH_PATH tpath = aPath;
288
289 #ifdef _WIN32
290 while( tpath.m_Pathvar.EndsWith( "\\" ) )
291 tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
292 #else
293 while( tpath.m_Pathvar.EndsWith( "/" ) && tpath.m_Pathvar.length() > 1 )
294 tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
295 #endif
296
297 wxFileName path( tpath.m_Pathvar, "" );
298 path.Normalize();
299
300 if( !path.DirExists() )
301 {
302 // Show a message only in debug mode
303 #ifdef DEBUG
304 if( aPath.m_Pathvar == "${KICAD6_3DMODEL_DIR}"
305 || aPath.m_Pathvar == "${KIPRJMOD}"
306 || aPath.m_Pathvar == "${KISYS3DMOD}" )
307 {
308 // suppress the message if the missing pathvar is a system variable
309 }
310 else
311 {
312 wxString msg = _( "The given path does not exist" );
313 msg.append( "\n" );
314 msg.append( tpath.m_Pathvar );
315 wxLogMessage( "%s\n", msg.ToUTF8() );
316 }
317 #endif
318
319 tpath.m_Pathexp.clear();
320 }
321 else
322 {
323 tpath.m_Pathexp = path.GetFullPath();
324
325 #ifdef _WIN32
326 while( tpath.m_Pathexp.EndsWith( "\\" ) )
327 tpath.m_Pathexp.erase( tpath.m_Pathexp.length() - 1 );
328 #else
329 while( tpath.m_Pathexp.EndsWith( "/" ) && tpath.m_Pathexp.length() > 1 )
330 tpath.m_Pathexp.erase( tpath.m_Pathexp.length() - 1 );
331 #endif
332 }
333
334 wxString pname = path.GetPath();
335 std::list< SEARCH_PATH >::iterator sPL = m_Paths.begin();
336 std::list< SEARCH_PATH >::iterator ePL = m_Paths.end();
337
338 while( sPL != ePL )
339 {
340 if( tpath.m_Alias == sPL->m_Alias )
341 {
342 wxString msg = _( "Alias:" ) + wxS( " " );
343 msg.append( tpath.m_Alias );
344 msg.append( "\n" );
345 msg.append( _( "This path:" ) + wxS( " " ) );
346 msg.append( tpath.m_Pathvar );
347 msg.append( "\n" );
348 msg.append( _( "Existing path:" ) + wxS( " " ) );
349 msg.append( sPL->m_Pathvar );
350 wxMessageBox( msg, _( "Bad alias (duplicate name)" ) );
351
352 return false;
353 }
354
355 ++sPL;
356 }
357
358 m_Paths.push_back( tpath );
359 return true;
360 }
361
362
readPathList(void)363 bool S3D_RESOLVER::readPathList( void )
364 {
365 wxFileName cfgpath( wxStandardPaths::Get().GetTempDir(), S3D_RESOLVER_CONFIG );
366 cfgpath.Normalize();
367 wxString cfgname = cfgpath.GetFullPath();
368
369 size_t nitems = m_Paths.size();
370
371 std::ifstream cfgFile;
372 std::string cfgLine;
373
374 if( !wxFileName::Exists( cfgname ) )
375 {
376 wxLogTrace( trace3dResolver, wxT( "%s:%s:d\n * no 3D configuration file '%s'" ),
377 __FILE__, __FUNCTION__, __LINE__, cfgname );
378
379 return false;
380 }
381
382 cfgFile.open( cfgname.ToUTF8() );
383
384 if( !cfgFile.is_open() )
385 {
386 wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n * Could not open configuration file '%s'" ),
387 __FILE__, __FUNCTION__, __LINE__, cfgname );
388
389 return false;
390 }
391
392 int lineno = 0;
393 SEARCH_PATH al;
394 size_t idx;
395 int vnum = 0; // version number
396
397 while( cfgFile.good() )
398 {
399 cfgLine.clear();
400 std::getline( cfgFile, cfgLine );
401 ++lineno;
402
403 if( cfgLine.empty() )
404 {
405 if( cfgFile.eof() )
406 break;
407
408 continue;
409 }
410
411 if( 1 == lineno && cfgLine.compare( 0, 2, "#V" ) == 0 )
412 {
413 // extract the version number and parse accordingly
414 if( cfgLine.size() > 2 )
415 {
416 std::istringstream istr;
417 istr.str( cfgLine.substr( 2 ) );
418 istr >> vnum;
419 }
420
421 continue;
422 }
423
424 idx = 0;
425
426 if( !getHollerith( cfgLine, idx, al.m_Alias ) )
427 continue;
428
429 if( !getHollerith( cfgLine, idx, al.m_Pathvar ) )
430 continue;
431
432 if( !getHollerith( cfgLine, idx, al.m_Description ) )
433 continue;
434
435 addPath( al );
436 }
437
438 cfgFile.close();
439
440 if( m_Paths.size() != nitems )
441 return true;
442
443 return false;
444 }
445
446
checkEnvVarPath(const wxString & aPath)447 void S3D_RESOLVER::checkEnvVarPath( const wxString& aPath )
448 {
449 bool useParen = false;
450
451 if( aPath.StartsWith( "$(" ) )
452 useParen = true;
453 else if( !aPath.StartsWith( "${" ) )
454 return;
455
456 size_t pEnd;
457
458 if( useParen )
459 pEnd = aPath.find( ")" );
460 else
461 pEnd = aPath.find( "}" );
462
463 if( pEnd == wxString::npos )
464 return;
465
466 wxString envar = aPath.substr( 0, pEnd + 1 );
467
468 // check if the alias exists; if not then add it to the end of the
469 // env var section of the path list
470 std::list< SEARCH_PATH >::iterator sPL = m_Paths.begin();
471 std::list< SEARCH_PATH >::iterator ePL = m_Paths.end();
472
473 while( sPL != ePL )
474 {
475 if( sPL->m_Alias == envar )
476 return;
477
478 if( !sPL->m_Alias.StartsWith( "${" ) )
479 break;
480
481 ++sPL;
482 }
483
484 SEARCH_PATH lpath;
485 lpath.m_Alias = envar;
486 lpath.m_Pathvar = lpath.m_Alias;
487 wxFileName tmpFN( lpath.m_Alias, "" );
488 wxUniChar psep = tmpFN.GetPathSeparator();
489 tmpFN.Normalize();
490
491 if( !tmpFN.DirExists() )
492 return;
493
494 lpath.m_Pathexp = tmpFN.GetFullPath();
495
496 if( !lpath.m_Pathexp.empty() && psep == *lpath.m_Pathexp.rbegin() )
497 lpath.m_Pathexp.erase( --lpath.m_Pathexp.end() );
498
499 if( lpath.m_Pathexp.empty() )
500 return;
501
502 m_Paths.insert( sPL, lpath );
503 return;
504 }
505
506
expandVars(const wxString & aPath)507 wxString S3D_RESOLVER::expandVars( const wxString& aPath )
508 {
509 if( aPath.empty() )
510 return wxEmptyString;
511
512 wxString result;
513
514 for( const std::pair<const wxString, wxString>& i : m_EnvVars )
515 {
516 if( !aPath.compare( 2, i.first.length(), i.first ) )
517 {
518 result = i.second;
519 result.append( aPath.substr( 3 + i.first.length() ) );
520
521 if( result.StartsWith( "${" ) || result.StartsWith( "$(" ) )
522 result = expandVars( result );
523
524 return result;
525 }
526 }
527
528 result = wxExpandEnvVars( aPath );
529
530 if( result == aPath )
531 return wxEmptyString;
532
533 if( result.StartsWith( "${" ) || result.StartsWith( "$(" ) )
534 result = expandVars( result );
535
536 return result;
537 }
538
539
ShortenPath(const wxString & aFullPathName)540 wxString S3D_RESOLVER::ShortenPath( const wxString& aFullPathName )
541 {
542 wxString fname = aFullPathName;
543
544 if( m_Paths.empty() )
545 createPathList();
546
547 std::lock_guard<std::mutex> lock( mutex3D_resolver );
548
549 std::list< SEARCH_PATH >::const_iterator sL = m_Paths.begin();
550 std::list< SEARCH_PATH >::const_iterator eL = m_Paths.end();
551 size_t idx;
552
553 while( sL != eL )
554 {
555 // undefined paths do not participate in the file name shortening procedure.
556 if( sL->m_Pathexp.empty() )
557 {
558 ++sL;
559 continue;
560 }
561
562 wxFileName fpath( sL->m_Pathexp, "" );
563 wxString fps = fpath.GetPathWithSep();
564 wxString tname;
565
566 idx = fname.find( fps );
567
568 if( std::string::npos != idx && 0 == idx )
569 {
570 fname = fname.substr( fps.size() );
571
572 #ifdef _WIN32
573 // ensure only the '/' separator is used in the internal name
574 fname.Replace( "\\", "/" );
575 #endif
576
577 if( sL->m_Alias.StartsWith( "${" ) || sL->m_Alias.StartsWith( "$(" ) )
578 {
579 // old style ENV_VAR
580 tname = sL->m_Alias;
581 tname.Append( "/" );
582 tname.append( fname );
583 }
584 else
585 {
586 // new style alias
587 tname = ":";
588 tname.append( sL->m_Alias );
589 tname.append( ":" );
590 tname.append( fname );
591 }
592
593 return tname;
594 }
595
596 ++sL;
597 }
598
599 #ifdef _WIN32
600 // it is strange to convert an MSWin full path to use the
601 // UNIX separator but this is done for consistency and can
602 // be helpful even when transferring project files from
603 // MSWin to *NIX.
604 fname.Replace( "\\", "/" );
605 #endif
606
607 return fname;
608 }
609
610
611
GetPaths(void)612 const std::list< SEARCH_PATH >* S3D_RESOLVER::GetPaths( void )
613 {
614 return &m_Paths;
615 }
616
617
SplitAlias(const wxString & aFileName,wxString & anAlias,wxString & aRelPath)618 bool S3D_RESOLVER::SplitAlias( const wxString& aFileName, wxString& anAlias, wxString& aRelPath )
619 {
620 anAlias.clear();
621 aRelPath.clear();
622
623 size_t searchStart = 0;
624
625 if( aFileName.StartsWith( wxT( ":" ) ) )
626 searchStart = 1;
627
628 size_t tagpos = aFileName.find( wxT( ":" ), searchStart );
629
630 if( tagpos == wxString::npos || tagpos == searchStart )
631 return false;
632
633 if( tagpos + 1 >= aFileName.length() )
634 return false;
635
636 anAlias = aFileName.substr( searchStart, tagpos - searchStart );
637 aRelPath = aFileName.substr( tagpos + 1 );
638
639 return true;
640 }
641
642
getHollerith(const std::string & aString,size_t & aIndex,wxString & aResult)643 static bool getHollerith( const std::string& aString, size_t& aIndex, wxString& aResult )
644 {
645 aResult.clear();
646
647 if( aIndex >= aString.size() )
648 {
649 wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n * Bad Hollerith string in line '%s'" ),
650 __FILE__, __FUNCTION__, __LINE__, aString );
651
652 return false;
653 }
654
655 size_t i2 = aString.find( '"', aIndex );
656
657 if( std::string::npos == i2 )
658 {
659 wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n * missing opening quote mark in line '%s'" ),
660 __FILE__, __FUNCTION__, __LINE__, aString );
661
662 return false;
663 }
664
665 ++i2;
666
667 if( i2 >= aString.size() )
668 {
669 wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n * unexpected end of line in line '%s'" ),
670 __FILE__, __FUNCTION__, __LINE__, aString );
671
672 return false;
673 }
674
675 std::string tnum;
676
677 while( aString[i2] >= '0' && aString[i2] <= '9' )
678 tnum.append( 1, aString[i2++] );
679
680 if( tnum.empty() || aString[i2++] != ':' )
681 {
682 wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n * Bad Hollerith string in line '%s'" ),
683 __FILE__, __FUNCTION__, __LINE__, aString );
684
685 return false;
686 }
687
688 std::istringstream istr;
689 istr.str( tnum );
690 size_t nchars;
691 istr >> nchars;
692
693 if( (i2 + nchars) >= aString.size() )
694 {
695 wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n * unexpected end of line in line '%s'" ),
696 __FILE__, __FUNCTION__, __LINE__, aString );
697
698 return false;
699 }
700
701 if( nchars > 0 )
702 {
703 aResult = wxString::FromUTF8( aString.substr( i2, nchars ).c_str() );
704 i2 += nchars;
705 }
706
707 if( i2 >= aString.size() || aString[i2] != '"' )
708 {
709 wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n * missing closing quote mark in line '%s'" ),
710 __FILE__, __FUNCTION__, __LINE__, aString );
711
712 return false;
713 }
714
715 aIndex = i2 + 1;
716 return true;
717 }
718
719
ValidateFileName(const wxString & aFileName,bool & hasAlias)720 bool S3D_RESOLVER::ValidateFileName( const wxString& aFileName, bool& hasAlias )
721 {
722 // Rules:
723 // 1. The generic form of an aliased 3D relative path is:
724 // ALIAS:relative/path
725 // 2. ALIAS is a UTF string excluding "{}[]()%~<>\"='`;:.,&?/\\|$"
726 // 3. The relative path must be a valid relative path for the platform
727 hasAlias = false;
728
729 if( aFileName.empty() )
730 return false;
731
732 wxString filename = aFileName;
733 wxString lpath;
734 size_t aliasStart = aFileName.StartsWith( ':' ) ? 1 : 0;
735 size_t aliasEnd = aFileName.find( ':' );
736
737 // ensure that the file separators suit the current platform
738 #ifdef __WINDOWS__
739 filename.Replace( "/", "\\" );
740
741 // if we see the :\ pattern then it must be a drive designator
742 if( aliasEnd != wxString::npos )
743 {
744 size_t pos1 = aFileName.find( ":\\" );
745
746 if( pos1 != wxString::npos && ( pos1 != aliasEnd || pos1 != 1 ) )
747 return false;
748
749 // if we have a drive designator then we have no alias
750 if( pos1 != wxString::npos )
751 aliasEnd = wxString::npos;
752 }
753 #else
754 filename.Replace( "\\", "/" );
755 #endif
756
757 // names may not end with ':'
758 if( aliasEnd == aFileName.length() - 1 )
759 return false;
760
761 if( aliasEnd != wxString::npos )
762 {
763 // ensure the alias component is not empty
764 if( aliasEnd == aliasStart )
765 return false;
766
767 lpath = filename.substr( aliasStart, aliasEnd );
768
769 // check the alias for restricted characters
770 if( wxString::npos != lpath.find_first_of( "{}[]()%~<>\"='`;:.,&?/\\|$" ) )
771 return false;
772
773 hasAlias = true;
774 lpath = aFileName.substr( aliasEnd + 1 );
775 }
776 else
777 {
778 lpath = aFileName;
779
780 // in the case of ${ENV_VAR}|$(ENV_VAR)/path, strip the
781 // environment string before testing
782 aliasEnd = wxString::npos;
783
784 if( aFileName.StartsWith( "${" ) )
785 aliasEnd = aFileName.find( '}' );
786 else if( aFileName.StartsWith( "$(" ) )
787 aliasEnd = aFileName.find( ')' );
788
789 if( aliasEnd != wxString::npos )
790 lpath = aFileName.substr( aliasEnd + 1 );
791 }
792
793 if( wxString::npos != lpath.find_first_of( wxFileName::GetForbiddenChars() ) )
794 return false;
795
796 return true;
797 }
798