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.

823 lines
23 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  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-2023 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 <pgm_base.h>
  29. #include <trace_helpers.h>
  30. #include <common.h>
  31. #include <env_vars.h>
  32. #include <filename_resolver.h>
  33. #include <confirm.h>
  34. #include <wx_filename.h>
  35. // configuration file version
  36. #define CFGFILE_VERSION 1
  37. // flag bits used to track different one-off messages to users
  38. #define ERRFLG_ALIAS (1)
  39. #define ERRFLG_RELPATH (2)
  40. #define ERRFLG_ENVPATH (4)
  41. #define MASK_3D_RESOLVER "3D_RESOLVER"
  42. static std::mutex mutex_resolver;
  43. FILENAME_RESOLVER::FILENAME_RESOLVER() :
  44. m_pgm( nullptr ),
  45. m_project( nullptr )
  46. {
  47. m_errflags = 0;
  48. }
  49. bool FILENAME_RESOLVER::Set3DConfigDir( const wxString& aConfigDir )
  50. {
  51. if( aConfigDir.empty() )
  52. return false;
  53. wxFileName cfgdir( ExpandEnvVarSubstitutions( aConfigDir, m_project ), "" );
  54. cfgdir.Normalize( FN_NORMALIZE_FLAGS );
  55. if( !cfgdir.DirExists() )
  56. return false;
  57. m_configDir = cfgdir.GetPath();
  58. createPathList();
  59. return true;
  60. }
  61. bool FILENAME_RESOLVER::SetProject( PROJECT* aProject, bool* flgChanged )
  62. {
  63. m_project = aProject;
  64. if( !aProject )
  65. return false;
  66. wxFileName projdir( ExpandEnvVarSubstitutions( aProject->GetProjectPath(), aProject ), "" );
  67. projdir.Normalize( FN_NORMALIZE_FLAGS );
  68. if( !projdir.DirExists() )
  69. return false;
  70. m_curProjDir = projdir.GetPath();
  71. if( flgChanged )
  72. *flgChanged = false;
  73. if( m_paths.empty() )
  74. {
  75. SEARCH_PATH al;
  76. al.m_Alias = wxS( "${KIPRJMOD}" );
  77. al.m_Pathvar = wxS( "${KIPRJMOD}" );
  78. al.m_Pathexp = m_curProjDir;
  79. m_paths.push_back( al );
  80. if( flgChanged )
  81. *flgChanged = true;
  82. }
  83. else
  84. {
  85. if( m_paths.front().m_Pathexp != m_curProjDir )
  86. {
  87. m_paths.front().m_Pathexp = m_curProjDir;
  88. if( flgChanged )
  89. *flgChanged = true;
  90. }
  91. else
  92. {
  93. return true;
  94. }
  95. }
  96. #ifdef DEBUG
  97. {
  98. std::ostringstream ostr;
  99. ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
  100. ostr << " * [INFO] changed project dir to ";
  101. ostr << m_paths.front().m_Pathexp.ToUTF8();
  102. wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
  103. }
  104. #endif
  105. return true;
  106. }
  107. wxString FILENAME_RESOLVER::GetProjectDir() const
  108. {
  109. return m_curProjDir;
  110. }
  111. void FILENAME_RESOLVER::SetProgramBase( PGM_BASE* aBase )
  112. {
  113. m_pgm = aBase;
  114. if( !m_pgm || m_paths.empty() )
  115. return;
  116. // recreate the path list
  117. m_paths.clear();
  118. createPathList();
  119. }
  120. bool FILENAME_RESOLVER::createPathList()
  121. {
  122. if( !m_paths.empty() )
  123. return true;
  124. // add an entry for the default search path; at this point
  125. // we cannot set a sensible default so we use an empty string.
  126. // the user may change this later with a call to SetProjectDir()
  127. SEARCH_PATH lpath;
  128. lpath.m_Alias = wxS( "${KIPRJMOD}" );
  129. lpath.m_Pathvar = wxS( "${KIPRJMOD}" );
  130. lpath.m_Pathexp = m_curProjDir;
  131. m_paths.push_back( lpath );
  132. wxFileName fndummy;
  133. wxUniChar psep = fndummy.GetPathSeparator();
  134. std::list< wxString > epaths;
  135. if( GetKicadPaths( epaths ) )
  136. {
  137. for( const wxString& currPath : epaths )
  138. {
  139. wxString currPathVarFormat = currPath;
  140. currPathVarFormat.Prepend( wxS( "${" ) );
  141. currPathVarFormat.Append( wxS( "}" ) );
  142. wxString pathVal = ExpandEnvVarSubstitutions( currPathVarFormat, m_project );
  143. if( pathVal.empty() )
  144. {
  145. lpath.m_Pathexp.clear();
  146. }
  147. else
  148. {
  149. fndummy.Assign( pathVal, "" );
  150. fndummy.Normalize( FN_NORMALIZE_FLAGS );
  151. lpath.m_Pathexp = fndummy.GetFullPath();
  152. }
  153. lpath.m_Alias = currPath;
  154. lpath.m_Pathvar = currPath;
  155. if( !lpath.m_Pathexp.empty() && psep == *lpath.m_Pathexp.rbegin() )
  156. lpath.m_Pathexp.erase( --lpath.m_Pathexp.end() );
  157. // we add it first with the alias set to the non-variable format
  158. m_paths.push_back( lpath );
  159. // now add it with the "new variable format ${VAR}"
  160. lpath.m_Alias = currPathVarFormat;
  161. m_paths.push_back( lpath );
  162. }
  163. }
  164. if( m_paths.empty() )
  165. return false;
  166. #ifdef DEBUG
  167. wxLogTrace( MASK_3D_RESOLVER, wxS( " * [3D model] search paths:\n" ) );
  168. std::list< SEARCH_PATH >::const_iterator sPL = m_paths.begin();
  169. while( sPL != m_paths.end() )
  170. {
  171. wxLogTrace( MASK_3D_RESOLVER, wxS( " + %s : '%s'\n" ), (*sPL).m_Alias.GetData(),
  172. (*sPL).m_Pathexp.GetData() );
  173. ++sPL;
  174. }
  175. #endif
  176. return true;
  177. }
  178. bool FILENAME_RESOLVER::UpdatePathList( const std::vector< SEARCH_PATH >& aPathList )
  179. {
  180. wxUniChar envMarker( '$' );
  181. while( !m_paths.empty() && envMarker != *m_paths.back().m_Alias.rbegin() )
  182. m_paths.pop_back();
  183. for( const SEARCH_PATH& path : aPathList )
  184. addPath( path );
  185. return true;
  186. }
  187. wxString FILENAME_RESOLVER::ResolvePath( const wxString& aFileName, const wxString& aWorkingPath )
  188. {
  189. std::lock_guard<std::mutex> lock( mutex_resolver );
  190. if( aFileName.empty() )
  191. return wxEmptyString;
  192. if( m_paths.empty() )
  193. createPathList();
  194. // first attempt to use the name as specified:
  195. wxString tname = aFileName;
  196. // Note: variable expansion must preferably be performed via a threadsafe wrapper for the
  197. // getenv() system call. If we allow the wxFileName::Normalize() routine to perform expansion
  198. // then we will have a race condition since wxWidgets does not assure a threadsafe wrapper
  199. // for getenv().
  200. tname = ExpandEnvVarSubstitutions( tname, m_project );
  201. wxFileName tmpFN( tname );
  202. // this case covers full paths, leading expanded vars, and paths relative to the current
  203. // working directory (which is not necessarily the current project directory)
  204. if( tmpFN.FileExists() )
  205. {
  206. tmpFN.Normalize( FN_NORMALIZE_FLAGS );
  207. tname = tmpFN.GetFullPath();
  208. // special case: if a path begins with ${ENV_VAR} but is not in the resolver's path list
  209. // then add it.
  210. if( aFileName.StartsWith( wxS( "${" ) ) || aFileName.StartsWith( wxS( "$(" ) ) )
  211. checkEnvVarPath( aFileName );
  212. return tname;
  213. }
  214. // if a path begins with ${ENV_VAR}/$(ENV_VAR) and is not resolved then the file either does
  215. // not exist or the ENV_VAR is not defined
  216. if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
  217. {
  218. if( !( m_errflags & ERRFLG_ENVPATH ) )
  219. {
  220. m_errflags |= ERRFLG_ENVPATH;
  221. wxString errmsg = "[3D File Resolver] No such path; ensure the environment var is defined";
  222. errmsg.append( "\n" );
  223. errmsg.append( tname );
  224. errmsg.append( "\n" );
  225. wxLogTrace( tracePathsAndFiles, errmsg );
  226. }
  227. return wxEmptyString;
  228. }
  229. // at this point aFileName is:
  230. // a. an aliased shortened name or
  231. // b. cannot be determined
  232. // check the path relative to the current project directory;
  233. // NB: this is not necessarily the same as the current working directory, which has already
  234. // been checked. This case accounts for partial paths which do not contain ${KIPRJMOD}.
  235. // This check is performed before checking the path relative to ${KICAD7_3DMODEL_DIR} so that
  236. // users can potentially override a model within ${KICAD7_3DMODEL_DIR}.
  237. if( !m_paths.begin()->m_Pathexp.empty() && !tname.StartsWith( ":" ) )
  238. {
  239. tmpFN.Assign( m_paths.begin()->m_Pathexp, "" );
  240. wxString fullPath = tmpFN.GetPathWithSep() + tname;
  241. fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
  242. if( wxFileName::FileExists( fullPath ) )
  243. {
  244. tmpFN.Assign( fullPath );
  245. tmpFN.Normalize( FN_NORMALIZE_FLAGS );
  246. tname = tmpFN.GetFullPath();
  247. return tname;
  248. }
  249. }
  250. // check path relative to search path
  251. if( !aWorkingPath.IsEmpty() && !tname.StartsWith( ":" ) )
  252. {
  253. wxString tmp = aWorkingPath;
  254. tmp.Append( tmpFN.GetPathSeparator() );
  255. tmp.Append( tname );
  256. tmpFN.Assign( tmp );
  257. if( tmpFN.MakeAbsolute() && tmpFN.FileExists() )
  258. {
  259. tname = tmpFN.GetFullPath();
  260. return tname;
  261. }
  262. }
  263. // check the partial path relative to ${KICAD7_3DMODEL_DIR} (legacy behavior)
  264. if( !tname.StartsWith( wxS( ":" ) ) )
  265. {
  266. wxFileName fpath;
  267. wxString fullPath( wxString::Format( wxS( "${%s}" ),
  268. ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ) ) );
  269. fullPath.Append( fpath.GetPathSeparator() );
  270. fullPath.Append( tname );
  271. fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
  272. fpath.Assign( fullPath );
  273. if( fpath.Normalize( FN_NORMALIZE_FLAGS ) && fpath.FileExists() )
  274. {
  275. tname = fpath.GetFullPath();
  276. return tname;
  277. }
  278. }
  279. // at this point the filename must contain an alias or else it is invalid
  280. wxString alias; // the alias portion of the short filename
  281. wxString relpath; // the path relative to the alias
  282. if( !SplitAlias( tname, alias, relpath ) )
  283. {
  284. if( !( m_errflags & ERRFLG_RELPATH ) )
  285. {
  286. // this can happen if the file was intended to be relative to ${KICAD7_3DMODEL_DIR}
  287. // but ${KICAD7_3DMODEL_DIR} is not set or is incorrect.
  288. m_errflags |= ERRFLG_RELPATH;
  289. wxString errmsg = "[3D File Resolver] No such path";
  290. errmsg.append( wxS( "\n" ) );
  291. errmsg.append( tname );
  292. errmsg.append( wxS( "\n" ) );
  293. wxLogTrace( tracePathsAndFiles, errmsg );
  294. }
  295. return wxEmptyString;
  296. }
  297. for( const SEARCH_PATH& path : m_paths )
  298. {
  299. // ${ENV_VAR} paths have already been checked; skip them
  300. if( path.m_Alias.StartsWith( wxS( "${" ) ) || path.m_Alias.StartsWith( wxS( "$(" ) ) )
  301. continue;
  302. if( path.m_Alias == alias && !path.m_Pathexp.empty() )
  303. {
  304. wxFileName fpath( wxFileName::DirName( path.m_Pathexp ) );
  305. wxString fullPath = fpath.GetPathWithSep() + relpath;
  306. fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
  307. if( wxFileName::FileExists( fullPath ) )
  308. {
  309. tname = fullPath;
  310. wxFileName tmp( fullPath );
  311. if( tmp.Normalize( FN_NORMALIZE_FLAGS ) )
  312. tname = tmp.GetFullPath();
  313. return tname;
  314. }
  315. }
  316. }
  317. if( !( m_errflags & ERRFLG_ALIAS ) )
  318. {
  319. m_errflags |= ERRFLG_ALIAS;
  320. wxString errmsg = "[3D File Resolver] No such path; ensure the path alias is defined";
  321. errmsg.append( "\n" );
  322. errmsg.append( tname.substr( 1 ) );
  323. errmsg.append( "\n" );
  324. wxLogTrace( tracePathsAndFiles, errmsg );
  325. }
  326. return wxEmptyString;
  327. }
  328. bool FILENAME_RESOLVER::addPath( const SEARCH_PATH& aPath )
  329. {
  330. if( aPath.m_Alias.empty() || aPath.m_Pathvar.empty() )
  331. return false;
  332. std::lock_guard<std::mutex> lock( mutex_resolver );
  333. SEARCH_PATH tpath = aPath;
  334. #ifdef _WIN32
  335. while( tpath.m_Pathvar.EndsWith( wxT( "\\" ) ) )
  336. tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
  337. #else
  338. while( tpath.m_Pathvar.EndsWith( wxT( "/" ) ) && tpath.m_Pathvar.length() > 1 )
  339. tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
  340. #endif
  341. wxFileName path( ExpandEnvVarSubstitutions( tpath.m_Pathvar, m_project ), "" );
  342. path.Normalize( FN_NORMALIZE_FLAGS );
  343. if( !path.DirExists() )
  344. {
  345. wxString versionedPath = wxString::Format( wxS( "${%s}" ),
  346. ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ) );
  347. if( aPath.m_Pathvar == versionedPath
  348. || aPath.m_Pathvar == wxS( "${KIPRJMOD}" ) || aPath.m_Pathvar == wxS( "$(KIPRJMOD)" )
  349. || aPath.m_Pathvar == wxS( "${KISYS3DMOD}" ) || aPath.m_Pathvar == wxS( "$(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. DisplayErrorMessage( nullptr, msg );
  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. DisplayErrorMessage( nullptr, _( "Bad alias (duplicate name)" ), msg );
  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( wxS( "$(" ) ) )
  399. useParen = true;
  400. else if( !aPath.StartsWith( wxS( "${" ) ) )
  401. return;
  402. size_t pEnd;
  403. if( useParen )
  404. pEnd = aPath.find( wxS( ")" ) );
  405. else
  406. pEnd = aPath.find( wxS( "}" ) );
  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( wxS( "${" ) ) )
  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( wxS( "${" ) ) || sL->m_Alias.StartsWith( wxS( "$(" ) ) )
  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( wxS( "${" ) ) || sL->m_Alias.StartsWith( wxS( "$(" ) ) )
  481. {
  482. // old style ENV_VAR
  483. tname = sL->m_Alias;
  484. tname.Append( wxS( "/" ) );
  485. tname.append( fname );
  486. }
  487. else
  488. {
  489. // new style alias
  490. tname = "${";
  491. tname.append( sL->m_Alias );
  492. tname.append( wxS( "}/" ) );
  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( wxS( "${" ) ) )
  581. aliasEnd = aFileName.find( '}' );
  582. else if( aFileName.StartsWith( wxS( "$(" ) ) )
  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.Matches( wxS( "KICAD*_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.Matches( wxS("KICAD*_3DMODEL_DIR") ) )
  630. hasKisys3D = true;
  631. ++mS;
  632. }
  633. if( !hasKisys3D )
  634. paths.emplace_back( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ) );
  635. return true;
  636. }