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