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.

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