@@ -264,83 +264,122 @@ public class FirefoxBookmarkLoader : FirefoxBookmarkLoaderBase
264264 /// </summary>
265265 public override List < Bookmark > GetBookmarks ( )
266266 {
267- return GetBookmarksFromPath ( PlacesPath ) ;
267+ var bookmarks = new List < Bookmark > ( ) ;
268+ bookmarks . AddRange ( GetBookmarksFromPath ( PlacesPath ) ) ;
269+ bookmarks . AddRange ( GetBookmarksFromPath ( MsixPlacesPath ) ) ;
270+ return bookmarks ;
268271 }
269272
270273 /// <summary>
271- /// Path to places.sqlite
274+ /// Path to places.sqlite of Msi installer
275+ /// E.g. C:\Users\{UserName}\AppData\Roaming\Mozilla\Firefox
276+ /// <see href="https://support.mozilla.org/en-US/kb/profiles-where-firefox-stores-user-data#w_finding-your-profile-without-opening-firefox"/>
272277 /// </summary>
273- /// <remarks></remarks>
274278 private static string PlacesPath
275279 {
276280 get
277281 {
278282 var profileFolderPath = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) , @"Mozilla\Firefox" ) ;
279- var profileIni = Path . Combine ( profileFolderPath , @"profiles.ini" ) ;
283+ return GetProfileIniPath ( profileFolderPath ) ;
284+ }
285+ }
280286
281- if ( ! File . Exists ( profileIni ) )
282- return string . Empty ;
287+ /// <summary>
288+ /// Path to places.sqlite of MSIX installer
289+ /// E.g. C:\Users\{UserName}\AppData\Local\Packages\Mozilla.Firefox_n80bbvh6b1yt2\LocalCache\Roaming\Mozilla\Firefox
290+ /// <see href="https://support.mozilla.org/en-US/kb/profiles-where-firefox-stores-user-data#w_finding-your-profile-without-opening-firefox"/>
291+ /// </summary>
292+ public static string MsixPlacesPath
293+ {
294+ get
295+ {
296+ var platformPath = Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) ;
297+ var packagesPath = Path . Combine ( platformPath , "Packages" ) ;
298+ try
299+ {
300+ // Search for folder with Mozilla.Firefox prefix
301+ var firefoxPackageFolder = Directory . EnumerateDirectories ( packagesPath , "Mozilla.Firefox*" ,
302+ SearchOption . TopDirectoryOnly ) . FirstOrDefault ( ) ;
303+
304+ // Msix FireFox not installed
305+ if ( firefoxPackageFolder == null ) return string . Empty ;
283306
284- // get firefox default profile directory from profiles.ini
285- using var sReader = new StreamReader ( profileIni ) ;
286- var ini = sReader . ReadToEnd ( ) ;
307+ var profileFolderPath = Path . Combine ( firefoxPackageFolder , @"LocalCache\Roaming\Mozilla\Firefox" ) ;
308+ return GetProfileIniPath ( profileFolderPath ) ;
309+ }
310+ catch
311+ {
312+ return string . Empty ;
313+ }
314+ }
315+ }
287316
288- var lines = ini . Split ( "\r \n " ) . ToList ( ) ;
317+ private static string GetProfileIniPath ( string profileFolderPath )
318+ {
319+ var profileIni = Path . Combine ( profileFolderPath , @"profiles.ini" ) ;
320+ if ( ! File . Exists ( profileIni ) )
321+ return string . Empty ;
289322
290- var defaultProfileFolderNameRaw = lines . FirstOrDefault ( x => x . Contains ( "Default=" ) && x != "Default=1" ) ?? string . Empty ;
323+ // get firefox default profile directory from profiles.ini
324+ using var sReader = new StreamReader ( profileIni ) ;
325+ var ini = sReader . ReadToEnd ( ) ;
291326
292- if ( string . IsNullOrEmpty ( defaultProfileFolderNameRaw ) )
293- return string . Empty ;
327+ var lines = ini . Split ( "\r \n " ) . ToList ( ) ;
294328
295- var defaultProfileFolderName = defaultProfileFolderNameRaw . Split ( '=' ) . Last ( ) ;
329+ var defaultProfileFolderNameRaw = lines . FirstOrDefault ( x => x . Contains ( "Default=" ) && x != "Default=1" ) ?? string . Empty ;
296330
297- var indexOfDefaultProfileAttributePath = lines . IndexOf ( "Path=" + defaultProfileFolderName ) ;
331+ if ( string . IsNullOrEmpty ( defaultProfileFolderNameRaw ) )
332+ return string . Empty ;
298333
299- /*
300- Current profiles.ini structure example as of Firefox version 69.0.1
334+ var defaultProfileFolderName = defaultProfileFolderNameRaw . Split ( '=' ) . Last ( ) ;
301335
302- [Install736426B0AF4A39CB]
303- Default=Profiles/7789f565.default-release <== this is the default profile this plugin will get the bookmarks from. When opened Firefox will load the default profile
304- Locked=1
336+ var indexOfDefaultProfileAttributePath = lines . IndexOf ( "Path=" + defaultProfileFolderName ) ;
305337
306- [Profile2]
307- Name=newblahprofile
308- IsRelative=0
309- Path=C:\t6h2yuq8.newblahprofile <== Note this is a custom location path for the profile user can set, we need to cater for this in code.
338+ /*
339+ Current profiles.ini structure example as of Firefox version 69.0.1
310340
311- [Profile1]
312- Name=default
313- IsRelative=1
314- Path=Profiles/cydum7q4.default
315- Default=1
341+ [Install736426B0AF4A39CB]
342+ Default=Profiles/7789f565.default-release <== this is the default profile this plugin will get the bookmarks from. When opened Firefox will load the default profile
343+ Locked=1
316344
317- [Profile0 ]
318- Name=default-release
319- IsRelative=1
320- Path=Profiles/7789f565.default-release
345+ [Profile2 ]
346+ Name=dummyprofile
347+ IsRelative=0
348+ Path=C:\t6h2yuq8.dummyprofile <== Note this is a custom location path for the profile user can set, we need to cater for this in code.
321349
322- [General ]
323- StartWithLastProfile=1
324- Version=2
325- */
326- // Seen in the example above, the IsRelative attribute is always above the Path attribute
350+ [Profile1 ]
351+ Name=default
352+ IsRelative=1
353+ Path=Profiles/cydum7q4.default
354+ Default=1
327355
328- var relativePath = Path . Combine ( defaultProfileFolderName , "places.sqlite" ) ;
329- var absoluePath = Path . Combine ( profileFolderPath , relativePath ) ;
356+ [Profile0]
357+ Name=default-release
358+ IsRelative=1
359+ Path=Profiles/7789f565.default-release
330360
331- // If the index is out of range, it means that the default profile is in a custom location or the file is malformed
332- // If the profile is in a custom location, we need to check
333- if ( indexOfDefaultProfileAttributePath - 1 < 0 ||
334- indexOfDefaultProfileAttributePath - 1 >= lines . Count )
335- {
336- return Directory . Exists ( absoluePath ) ? absoluePath : relativePath ;
337- }
361+ [General]
362+ StartWithLastProfile=1
363+ Version=2
364+ */
365+ // Seen in the example above, the IsRelative attribute is always above the Path attribute
338366
339- var relativeAttribute = lines [ indexOfDefaultProfileAttributePath - 1 ] ;
367+ var relativePath = Path . Combine ( defaultProfileFolderName , "places.sqlite" ) ;
368+ var absolutePath = Path . Combine ( profileFolderPath , relativePath ) ;
340369
341- return relativeAttribute == "0" // See above, the profile is located in a custom location, path is not relative, so IsRelative=0
342- ? relativePath : absoluePath ;
370+ // If the index is out of range, it means that the default profile is in a custom location or the file is malformed
371+ // If the profile is in a custom location, we need to check
372+ if ( indexOfDefaultProfileAttributePath - 1 < 0 ||
373+ indexOfDefaultProfileAttributePath - 1 >= lines . Count )
374+ {
375+ return Directory . Exists ( absolutePath ) ? absolutePath : relativePath ;
343376 }
377+
378+ var relativeAttribute = lines [ indexOfDefaultProfileAttributePath - 1 ] ;
379+
380+ // See above, the profile is located in a custom location, path is not relative, so IsRelative=0
381+ return ( relativeAttribute == "0" || relativeAttribute == "IsRelative=0" )
382+ ? relativePath : absolutePath ;
344383 }
345384}
346385
0 commit comments