You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

824 lines
23 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2015-2020 Cirilo Bernardo <cirilo.bernardo@gmail.com>
  5. * Copyright (C) 2015-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. #include <fstream>
  25. #include <mutex>
  26. #include <sstream>
  27. #include <wx/log.h>
  28. #include <wx/msgdlg.h>
  29. #include <pgm_base.h>
  30. #include <trace_helpers.h>
  31. #include "common.h"
  32. #include "filename_resolver.h"
  33. #include <wx_filename.h>
  34. // configuration file version
  35. #define CFGFILE_VERSION 1
  36. // flag bits used to track different one-off messages to users
  37. #define ERRFLG_ALIAS (1)
  38. #define ERRFLG_RELPATH (2)
  39. #define ERRFLG_ENVPATH (4)
  40. #define MASK_3D_RESOLVER "3D_RESOLVER"
  41. static std::mutex mutex_resolver;
  42. FILENAME_RESOLVER::FILENAME_RESOLVER() :
  43. m_pgm( nullptr ),
  44. m_project( nullptr )
  45. {
  46. m_errflags = 0;
  47. }
  48. bool FILENAME_RESOLVER::Set3DConfigDir( const wxString& aConfigDir )
  49. {
  50. if( aConfigDir.empty() )
  51. return false;
  52. wxFileName cfgdir( ExpandEnvVarSubstitutions( aConfigDir, m_project ), "" );
  53. cfgdir.Normalize( FN_NORMALIZE_FLAGS );
  54. if( !cfgdir.DirExists() )
  55. return false;
  56. m_configDir = cfgdir.GetPath();
  57. createPathList();
  58. return true;
  59. }
  60. bool FILENAME_RESOLVER::SetProject( PROJECT* aProject, bool* flgChanged )
  61. {
  62. m_project = aProject;
  63. if( !aProject )
  64. return false;
  65. wxFileName projdir( ExpandEnvVarSubstitutions( aProject->GetProjectPath(), aProject ), "" );
  66. projdir.Normalize( FN_NORMALIZE_FLAGS );
  67. if( !projdir.DirExists() )
  68. return false;
  69. m_curProjDir = projdir.GetPath();
  70. if( flgChanged )
  71. *flgChanged = false;
  72. if( m_paths.empty() )
  73. {
  74. SEARCH_PATH al;
  75. al.m_Alias = "${KIPRJMOD}";
  76. al.m_Pathvar = "${KIPRJMOD}";
  77. al.m_Pathexp = m_curProjDir;
  78. m_paths.push_back( al );
  79. if( flgChanged )
  80. *flgChanged = true;
  81. }
  82. else
  83. {
  84. if( m_paths.front().m_Pathexp != m_curProjDir )
  85. {
  86. m_paths.front().m_Pathexp = m_curProjDir;
  87. if( flgChanged )
  88. *flgChanged = true;
  89. }
  90. else
  91. {
  92. return true;
  93. }
  94. }
  95. #ifdef DEBUG
  96. {
  97. std::ostringstream ostr;
  98. ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
  99. ostr << " * [INFO] changed project dir to ";
  100. ostr << m_paths.front().m_Pathexp.ToUTF8();
  101. wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
  102. }
  103. #endif
  104. return true;
  105. }
  106. wxString FILENAME_RESOLVER::GetProjectDir() const
  107. {
  108. return m_curProjDir;
  109. }
  110. void FILENAME_RESOLVER::SetProgramBase( PGM_BASE* aBase )
  111. {
  112. m_pgm = aBase;
  113. if( !m_pgm || m_paths.empty() )
  114. return;
  115. // recreate the path list
  116. m_paths.clear();
  117. createPathList();
  118. }
  119. bool FILENAME_RESOLVER::createPathList()
  120. {
  121. if( !m_paths.empty() )
  122. return true;
  123. // add an entry for the default search path; at this point
  124. // we cannot set a sensible default so we use an empty string.
  125. // the user may change this later with a call to SetProjectDir()
  126. SEARCH_PATH lpath;
  127. lpath.m_Alias = "${KIPRJMOD}";
  128. lpath.m_Pathvar = "${KIPRJMOD}";
  129. lpath.m_Pathexp = m_curProjDir;
  130. m_paths.push_back( lpath );
  131. wxFileName fndummy;
  132. wxUniChar psep = fndummy.GetPathSeparator();
  133. std::list< wxString > epaths;
  134. if( GetKicadPaths( epaths ) )
  135. {
  136. for( const wxString& currPath : epaths )
  137. {
  138. wxString currPathVarFormat = currPath;
  139. currPathVarFormat.Prepend( wxS( "${" ) );
  140. currPathVarFormat.Append( wxS( "}" ) );
  141. wxString pathVal = ExpandEnvVarSubstitutions( currPathVarFormat, m_project );
  142. if( pathVal.empty() )
  143. {
  144. lpath.m_Pathexp.clear();
  145. }
  146. else
  147. {
  148. fndummy.Assign( pathVal, "" );
  149. fndummy.Normalize( FN_NORMALIZE_FLAGS );
  150. lpath.m_Pathexp = fndummy.GetFullPath();
  151. }
  152. lpath.m_Alias = currPath;
  153. lpath.m_Pathvar = currPath;
  154. if( !lpath.m_Pathexp.empty() && psep == *lpath.m_Pathexp.rbegin() )
  155. lpath.m_Pathexp.erase( --lpath.m_Pathexp.end() );
  156. // we add it first with the alias set to the non-variable format
  157. m_paths.push_back( lpath );
  158. // now add it with the "new variable format ${VAR}"
  159. lpath.m_Alias = currPathVarFormat;
  160. m_paths.push_back( lpath );
  161. }
  162. }
  163. if( m_paths.empty() )
  164. return false;
  165. #ifdef DEBUG
  166. wxLogTrace( MASK_3D_RESOLVER, " * [3D model] search paths:\n" );
  167. std::list< SEARCH_PATH >::const_iterator sPL = m_paths.begin();
  168. while( sPL != m_paths.end() )
  169. {
  170. wxLogTrace( MASK_3D_RESOLVER, " + %s : '%s'\n", (*sPL).m_Alias.GetData(),
  171. (*sPL).m_Pathexp.GetData() );
  172. ++sPL;
  173. }
  174. #endif
  175. return true;
  176. }
  177. bool FILENAME_RESOLVER::UpdatePathList( const std::vector< SEARCH_PATH >& aPathList )
  178. {
  179. wxUniChar envMarker( '$' );
  180. while( !m_paths.empty() && envMarker != *m_paths.back().m_Alias.rbegin() )
  181. m_paths.pop_back();
  182. for( const SEARCH_PATH& path : aPathList )
  183. addPath( path );
  184. return true;
  185. }
  186. wxString FILENAME_RESOLVER::ResolvePath( const wxString& aFileName, const wxString& aWorkingPath )
  187. {
  188. std::lock_guard<std::mutex> lock( mutex_resolver );
  189. if( aFileName.empty() )
  190. return wxEmptyString;
  191. if( m_paths.empty() )
  192. createPathList();
  193. // first attempt to use the name as specified:
  194. wxString tname = aFileName;
  195. #ifdef _WIN32
  196. // translate from KiCad's internal UNIX-like path to MSWin paths
  197. tname.Replace( wxT( "/" ), wxT( "\\" ) );
  198. #endif
  199. // Note: variable expansion must preferably be performed via a threadsafe wrapper for the
  200. // getenv() system call. If we allow the wxFileName::Normalize() routine to perform expansion
  201. // then we will have a race condition since wxWidgets does not assure a threadsafe wrapper
  202. // for getenv().
  203. tname = ExpandEnvVarSubstitutions( tname, m_project );
  204. wxFileName tmpFN( tname );
  205. // this case covers full paths, leading expanded vars, and paths relative to the current
  206. // working directory (which is not necessarily the current project directory)
  207. if( tmpFN.FileExists() )
  208. {
  209. tmpFN.Normalize( FN_NORMALIZE_FLAGS );
  210. tname = tmpFN.GetFullPath();
  211. // special case: if a path begins with ${ENV_VAR} but is not in the resolver's path list
  212. // then add it.
  213. if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
  214. checkEnvVarPath( aFileName );
  215. return tname;
  216. }
  217. // if a path begins with ${ENV_VAR}/$(ENV_VAR) and is not resolved then the file either does
  218. // not exist or the ENV_VAR is not defined
  219. if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
  220. {
  221. if( !( m_errflags & ERRFLG_ENVPATH ) )
  222. {
  223. m_errflags |= ERRFLG_ENVPATH;
  224. wxString errmsg = "[3D File Resolver] No such path; ensure the environment var is defined";
  225. errmsg.append( "\n" );
  226. errmsg.append( tname );
  227. errmsg.append( "\n" );
  228. wxLogTrace( tracePathsAndFiles, errmsg );
  229. }
  230. return wxEmptyString;
  231. }
  232. // at this point aFileName is:
  233. // a. an aliased shortened name or
  234. // b. cannot be determined
  235. // check the path relative to the current project directory;
  236. // NB: this is not necessarily the same as the current working directory, which has already
  237. // been checked. This case accounts for partial paths which do not contain ${KIPRJMOD}.
  238. // This check is performed before checking the path relative to ${KICAD6_3DMODEL_DIR} so that
  239. // users can potentially override a model within ${KICAD6_3DMODEL_DIR}.
  240. if( !m_paths.begin()->m_Pathexp.empty() && !tname.StartsWith( ":" ) )
  241. {
  242. tmpFN.Assign( m_paths.begin()->m_Pathexp, "" );
  243. wxString fullPath = tmpFN.GetPathWithSep() + tname;
  244. fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
  245. if( wxFileName::FileExists( fullPath ) )
  246. {
  247. tmpFN.Assign( fullPath );
  248. tmpFN.Normalize( FN_NORMALIZE_FLAGS );
  249. tname = tmpFN.GetFullPath();
  250. return tname;
  251. }
  252. }
  253. // check path relative to search path
  254. if( !aWorkingPath.IsEmpty() && !tname.StartsWith( ":" ) )
  255. {
  256. wxString tmp = aWorkingPath;
  257. tmp.Append( tmpFN.GetPathSeparator() );
  258. tmp.Append( tname );
  259. tmpFN.Assign( tmp );
  260. if( tmpFN.MakeAbsolute() && tmpFN.FileExists() )
  261. {
  262. tname = tmpFN.GetFullPath();
  263. return tname;
  264. }
  265. }
  266. // check the partial path relative to ${KICAD6_3DMODEL_DIR} (legacy behavior)
  267. if( !tname.StartsWith( ":" ) )
  268. {
  269. wxFileName fpath;
  270. wxString fullPath( "${KICAD6_3DMODEL_DIR}" );
  271. fullPath.Append( fpath.GetPathSeparator() );
  272. fullPath.Append( tname );
  273. fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
  274. fpath.Assign( fullPath );
  275. if( fpath.Normalize( FN_NORMALIZE_FLAGS ) && fpath.FileExists() )
  276. {
  277. tname = fpath.GetFullPath();
  278. return tname;
  279. }
  280. }
  281. // at this point the filename must contain an alias or else it is invalid
  282. wxString alias; // the alias portion of the short filename
  283. wxString relpath; // the path relative to the alias
  284. if( !SplitAlias( tname, alias, relpath ) )
  285. {
  286. if( !( m_errflags & ERRFLG_RELPATH ) )
  287. {
  288. // this can happen if the file was intended to be relative to ${KICAD6_3DMODEL_DIR}
  289. // but ${KICAD6_3DMODEL_DIR} is not set or is incorrect.
  290. m_errflags |= ERRFLG_RELPATH;
  291. wxString errmsg = "[3D File Resolver] No such path";
  292. errmsg.append( "\n" );
  293. errmsg.append( tname );
  294. errmsg.append( "\n" );
  295. wxLogTrace( tracePathsAndFiles, errmsg );
  296. }
  297. return wxEmptyString;
  298. }
  299. for( const SEARCH_PATH& path : m_paths )
  300. {
  301. // ${ENV_VAR} paths have already been checked; skip them
  302. if( path.m_Alias.StartsWith( "${" ) || path.m_Alias.StartsWith( "$(" ) )
  303. continue;
  304. if( path.m_Alias == alias && !path.m_Pathexp.empty() )
  305. {
  306. wxFileName fpath( wxFileName::DirName( path.m_Pathexp ) );
  307. wxString fullPath = fpath.GetPathWithSep() + relpath;
  308. fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
  309. if( wxFileName::FileExists( fullPath ) )
  310. {
  311. tname = fullPath;
  312. wxFileName tmp( fullPath );
  313. if( tmp.Normalize( FN_NORMALIZE_FLAGS ) )
  314. tname = tmp.GetFullPath();
  315. return tname;
  316. }
  317. }
  318. }
  319. if( !( m_errflags & ERRFLG_ALIAS ) )
  320. {
  321. m_errflags |= ERRFLG_ALIAS;
  322. wxString errmsg = "[3D File Resolver] No such path; ensure the path alias is defined";
  323. errmsg.append( "\n" );
  324. errmsg.append( tname.substr( 1 ) );
  325. errmsg.append( "\n" );
  326. wxLogTrace( tracePathsAndFiles, errmsg );
  327. }
  328. return wxEmptyString;
  329. }
  330. bool FILENAME_RESOLVER::addPath( const SEARCH_PATH& aPath )
  331. {
  332. if( aPath.m_Alias.empty() || aPath.m_Pathvar.empty() )
  333. return false;
  334. std::lock_guard<std::mutex> lock( mutex_resolver );
  335. SEARCH_PATH tpath = aPath;
  336. #ifdef _WIN32
  337. while( tpath.m_Pathvar.EndsWith( wxT( "\\" ) ) )
  338. tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
  339. #else
  340. while( tpath.m_Pathvar.EndsWith( wxT( "/" ) ) && tpath.m_Pathvar.length() > 1 )
  341. tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
  342. #endif
  343. wxFileName path( ExpandEnvVarSubstitutions( tpath.m_Pathvar, m_project ), "" );
  344. path.Normalize( FN_NORMALIZE_FLAGS );
  345. if( !path.DirExists() )
  346. {
  347. if( aPath.m_Pathvar == "${KICAD6_3DMODEL_DIR}"
  348. || aPath.m_Pathvar == "${KIPRJMOD}" || aPath.m_Pathvar == "$(KIPRJMOD)"
  349. || aPath.m_Pathvar == "${KISYS3DMOD}" || aPath.m_Pathvar == "$(KISYS3DMOD)" )
  350. {
  351. // suppress the message if the missing pathvar is a system variable
  352. }
  353. else
  354. {
  355. wxString msg = _( "The given path does not exist" );
  356. msg.append( wxT( "\n" ) );
  357. msg.append( tpath.m_Pathvar );
  358. wxMessageBox( msg, _( "3D model search path" ) );
  359. }
  360. tpath.m_Pathexp.clear();
  361. }
  362. else
  363. {
  364. tpath.m_Pathexp = path.GetFullPath();
  365. #ifdef _WIN32
  366. while( tpath.m_Pathexp.EndsWith( wxT( "\\" ) ) )
  367. tpath.m_Pathexp.erase( tpath.m_Pathexp.length() - 1 );
  368. #else
  369. while( tpath.m_Pathexp.EndsWith( wxT( "/" ) ) && tpath.m_Pathexp.length() > 1 )
  370. tpath.m_Pathexp.erase( tpath.m_Pathexp.length() - 1 );
  371. #endif
  372. }
  373. std::list< SEARCH_PATH >::iterator sPL = m_paths.begin();
  374. std::list< SEARCH_PATH >::iterator ePL = m_paths.end();
  375. while( sPL != ePL )
  376. {
  377. if( tpath.m_Alias == sPL->m_Alias )
  378. {
  379. wxString msg = _( "Alias: " );
  380. msg.append( tpath.m_Alias );
  381. msg.append( wxT( "\n" ) );
  382. msg.append( _( "This path:" ) + wxS( " " ) );
  383. msg.append( tpath.m_Pathvar );
  384. msg.append( wxT( "\n" ) );
  385. msg.append( _( "Existing path:" ) + wxS( " " ) );
  386. msg.append( sPL->m_Pathvar );
  387. wxMessageBox( msg, _( "Bad alias (duplicate name)" ) );
  388. return false;
  389. }
  390. ++sPL;
  391. }
  392. m_paths.push_back( tpath );
  393. return true;
  394. }
  395. void FILENAME_RESOLVER::checkEnvVarPath( const wxString& aPath )
  396. {
  397. bool useParen = false;
  398. if( aPath.StartsWith( "$(" ) )
  399. useParen = true;
  400. else if( !aPath.StartsWith( "${" ) )
  401. return;
  402. size_t pEnd;
  403. if( useParen )
  404. pEnd = aPath.find( ")" );
  405. else
  406. pEnd = aPath.find( "}" );
  407. if( pEnd == wxString::npos )
  408. return;
  409. wxString envar = aPath.substr( 0, pEnd + 1 );
  410. // check if the alias exists; if not then add it to the end of the
  411. // env var section of the path list
  412. auto sPL = m_paths.begin();
  413. auto ePL = m_paths.end();
  414. while( sPL != ePL )
  415. {
  416. if( sPL->m_Alias == envar )
  417. return;
  418. if( !sPL->m_Alias.StartsWith( "${" ) )
  419. break;
  420. ++sPL;
  421. }
  422. SEARCH_PATH lpath;
  423. lpath.m_Alias = envar;
  424. lpath.m_Pathvar = lpath.m_Alias;
  425. wxFileName tmpFN( ExpandEnvVarSubstitutions( lpath.m_Alias, m_project ), "" );
  426. wxUniChar psep = tmpFN.GetPathSeparator();
  427. tmpFN.Normalize( FN_NORMALIZE_FLAGS );
  428. if( !tmpFN.DirExists() )
  429. return;
  430. lpath.m_Pathexp = tmpFN.GetFullPath();
  431. if( !lpath.m_Pathexp.empty() && psep == *lpath.m_Pathexp.rbegin() )
  432. lpath.m_Pathexp.erase( --lpath.m_Pathexp.end() );
  433. if( lpath.m_Pathexp.empty() )
  434. return;
  435. m_paths.insert( sPL, lpath );
  436. }
  437. wxString FILENAME_RESOLVER::ShortenPath( const wxString& aFullPathName )
  438. {
  439. wxString fname = aFullPathName;
  440. if( m_paths.empty() )
  441. createPathList();
  442. std::lock_guard<std::mutex> lock( mutex_resolver );
  443. std::list< SEARCH_PATH >::const_iterator sL = m_paths.begin();
  444. size_t idx;
  445. while( sL != m_paths.end() )
  446. {
  447. // undefined paths do not participate in the
  448. // file name shortening procedure
  449. if( sL->m_Pathexp.empty() )
  450. {
  451. ++sL;
  452. continue;
  453. }
  454. wxFileName fpath;
  455. // in the case of aliases, ensure that we use the most recent definition
  456. if( sL->m_Alias.StartsWith( "${" ) || sL->m_Alias.StartsWith( "$(" ) )
  457. {
  458. wxString tpath = ExpandEnvVarSubstitutions( sL->m_Alias, m_project );
  459. if( tpath.empty() )
  460. {
  461. ++sL;
  462. continue;
  463. }
  464. fpath.Assign( tpath, wxT( "" ) );
  465. }
  466. else
  467. {
  468. fpath.Assign( sL->m_Pathexp, wxT( "" ) );
  469. }
  470. wxString fps = fpath.GetPathWithSep();
  471. wxString tname;
  472. idx = fname.find( fps );
  473. if( idx == 0 )
  474. {
  475. fname = fname.substr( fps.size() );
  476. #ifdef _WIN32
  477. // ensure only the '/' separator is used in the internal name
  478. fname.Replace( wxT( "\\" ), wxT( "/" ) );
  479. #endif
  480. if( sL->m_Alias.StartsWith( "${" ) || sL->m_Alias.StartsWith( "$(" ) )
  481. {
  482. // old style ENV_VAR
  483. tname = sL->m_Alias;
  484. tname.Append( "/" );
  485. tname.append( fname );
  486. }
  487. else
  488. {
  489. // new style alias
  490. tname = ":";
  491. tname.append( sL->m_Alias );
  492. tname.append( ":" );
  493. tname.append( fname );
  494. }
  495. return tname;
  496. }
  497. ++sL;
  498. }
  499. #ifdef _WIN32
  500. // it is strange to convert an MSWin full path to use the
  501. // UNIX separator but this is done for consistency and can
  502. // be helpful even when transferring project files from
  503. // MSWin to *NIX.
  504. fname.Replace( wxT( "\\" ), wxT( "/" ) );
  505. #endif
  506. return fname;
  507. }
  508. const std::list< SEARCH_PATH >* FILENAME_RESOLVER::GetPaths() const
  509. {
  510. return &m_paths;
  511. }
  512. bool FILENAME_RESOLVER::SplitAlias( const wxString& aFileName,
  513. wxString& anAlias, wxString& aRelPath ) const
  514. {
  515. anAlias.clear();
  516. aRelPath.clear();
  517. size_t searchStart = 0;
  518. if( aFileName.StartsWith( wxT( ":" ) ) )
  519. searchStart = 1;
  520. size_t tagpos = aFileName.find( wxT( ":" ), searchStart );
  521. if( tagpos == wxString::npos || tagpos == searchStart )
  522. return false;
  523. if( tagpos + 1 >= aFileName.length() )
  524. return false;
  525. anAlias = aFileName.substr( searchStart, tagpos - searchStart );
  526. aRelPath = aFileName.substr( tagpos + 1 );
  527. return true;
  528. }
  529. bool FILENAME_RESOLVER::ValidateFileName( const wxString& aFileName, bool& hasAlias ) const
  530. {
  531. // Rules:
  532. // 1. The generic form of an aliased 3D relative path is:
  533. // ALIAS:relative/path
  534. // 2. ALIAS is a UTF string excluding wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" )
  535. // 3. The relative path must be a valid relative path for the platform
  536. hasAlias = false;
  537. if( aFileName.empty() )
  538. return false;
  539. wxString filename = aFileName;
  540. wxString lpath;
  541. size_t aliasStart = aFileName.StartsWith( ':' ) ? 1 : 0;
  542. size_t aliasEnd = aFileName.find( ':', aliasStart );
  543. // ensure that the file separators suit the current platform
  544. #ifdef __WINDOWS__
  545. filename.Replace( wxT( "/" ), wxT( "\\" ) );
  546. // if we see the :\ pattern then it must be a drive designator
  547. if( aliasEnd != wxString::npos )
  548. {
  549. size_t pos1 = filename.find( wxT( ":\\" ) );
  550. if( pos1 != wxString::npos && ( pos1 != aliasEnd || pos1 != 1 ) )
  551. return false;
  552. // if we have a drive designator then we have no alias
  553. if( pos1 != wxString::npos )
  554. aliasEnd = wxString::npos;
  555. }
  556. #else
  557. filename.Replace( wxT( "\\" ), wxT( "/" ) );
  558. #endif
  559. // names may not end with ':'
  560. if( aliasEnd == aFileName.length() -1 )
  561. return false;
  562. if( aliasEnd != wxString::npos )
  563. {
  564. // ensure the alias component is not empty
  565. if( aliasEnd == aliasStart )
  566. return false;
  567. lpath = filename.substr( aliasStart, aliasEnd );
  568. // check the alias for restricted characters
  569. if( wxString::npos != lpath.find_first_of( wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" ) ) )
  570. return false;
  571. hasAlias = true;
  572. lpath = aFileName.substr( aliasEnd + 1 );
  573. }
  574. else
  575. {
  576. lpath = aFileName;
  577. // in the case of ${ENV_VAR}|$(ENV_VAR)/path, strip the
  578. // environment string before testing
  579. aliasEnd = wxString::npos;
  580. if( aFileName.StartsWith( "${" ) )
  581. aliasEnd = aFileName.find( '}' );
  582. else if( aFileName.StartsWith( "$(" ) )
  583. aliasEnd = aFileName.find( ')' );
  584. if( aliasEnd != wxString::npos )
  585. lpath = aFileName.substr( aliasEnd + 1 );
  586. }
  587. // Test for forbidden chars in filenames. Should be wxFileName::GetForbiddenChars()
  588. // On MSW, the list returned by wxFileName::GetForbiddenChars() contains separators
  589. // '\'and '/' used here because lpath can be a full path.
  590. // So remove separators
  591. wxString lpath_no_sep = lpath;
  592. #ifdef __WINDOWS__
  593. lpath_no_sep.Replace( "/", " " );
  594. lpath_no_sep.Replace( "\\", " " );
  595. // A disk identifier is allowed, and therefore remove its separator
  596. if( lpath_no_sep.Length() > 1 && lpath_no_sep[1] == ':' )
  597. lpath_no_sep[1] = ' ';
  598. #endif
  599. if( wxString::npos != lpath_no_sep.find_first_of( wxFileName::GetForbiddenChars() ) )
  600. return false;
  601. return true;
  602. }
  603. bool FILENAME_RESOLVER::GetKicadPaths( std::list< wxString >& paths ) const
  604. {
  605. paths.clear();
  606. if( !m_pgm )
  607. return false;
  608. bool hasKisys3D = false;
  609. // iterate over the list of internally defined ENV VARs
  610. // and add them to the paths list
  611. ENV_VAR_MAP_CITER mS = m_pgm->GetLocalEnvVariables().begin();
  612. ENV_VAR_MAP_CITER mE = m_pgm->GetLocalEnvVariables().end();
  613. while( mS != mE )
  614. {
  615. // filter out URLs, template directories, and known system paths
  616. if( mS->first == wxS( "KICAD_PTEMPLATES" )
  617. || mS->first == wxS( "KICAD6_FOOTPRINT_DIR" ) )
  618. {
  619. ++mS;
  620. continue;
  621. }
  622. if( wxString::npos != mS->second.GetValue().find( wxS( "://" ) ) )
  623. {
  624. ++mS;
  625. continue;
  626. }
  627. //also add the path without the ${} to act as legacy alias support for older files
  628. paths.push_back( mS->first );
  629. if( mS->first == wxS("KICAD6_3DMODEL_DIR") )
  630. hasKisys3D = true;
  631. ++mS;
  632. }
  633. if( !hasKisys3D )
  634. paths.emplace_back( wxS("KICAD6_3DMODEL_DIR") );
  635. return true;
  636. }