コード例 #1
0
        /// <summary>
        /// Tests the sync logic with custom <see cref="AppList"/>s.
        /// </summary>
        /// <param name="resetMode">The <see cref="SyncResetMode"/> to pass to <see cref="SyncIntegrationManager.Sync"/>.</param>
        /// <param name="appListLocal">The current local <see cref="AppList"/>.</param>
        /// <param name="appListLast">The state of the <see cref="AppList"/> after the last successful sync.</param>
        /// <param name="appListServer">The current server-side <see cref="AppList"/>.</param>
        private void TestSync(SyncResetMode resetMode, AppList appListLocal, AppList appListLast, AppList appListServer)
        {
            appListLocal.SaveXml(_appListPath);
            if (appListLast != null)
            {
                appListLast.SaveXml(_appListPath + SyncIntegrationManager.AppListLastSyncSuffix);
            }

            using (var stream = File.Create(_appListPath + ".zip"))
                appListServer.SaveXmlZip(stream);

            using (var appListServerFile = File.OpenRead(_appListPath + ".zip"))
                using (var syncServer = new MicroServer("app-list", appListServerFile))
                {
                    using (var integrationManager = new SyncIntegrationManager(_appListPath, new SyncServer {
                        Uri = syncServer.ServerUri
                    }, interfaceId => new Feed(), new SilentTaskHandler()))
                        integrationManager.Sync(resetMode);

                    appListServer = AppList.LoadXmlZip(syncServer.FileContent);
                }

            appListLocal = XmlStorage.LoadXml <AppList>(_appListPath);
            appListLast  = XmlStorage.LoadXml <AppList>(_appListPath + SyncIntegrationManager.AppListLastSyncSuffix);
            Assert.AreEqual(appListLocal, appListServer, "Server and local data should be equal after sync");
            Assert.AreEqual(appListLocal, appListLast, "Last sync snapshot and local data should be equal after sync");
        }
コード例 #2
0
        public static void LoadCurrent()
        {
            try
            {
                string settingsPath = Locations.GetSaveConfigPath(GeneralSettings.AppName, true, "Settings.xml");
                if (File.Exists(settingsPath))
                {
                    Current = XmlStorage.LoadXml <Settings>(settingsPath);
                    Log.Info("Loaded settings from " + settingsPath);
                    return;
                }
            }
            #region Error handling
            catch (IOException ex)
            {
                Log.Warn("Failed to load settings: " + ex.Message + "\nReverting to defaults");
            }
            catch (UnauthorizedAccessException ex)
            {
                Log.Warn("Insufficient rights to load settings: " + ex.Message + "\nReverting to defaults");
            }
            catch (InvalidDataException ex)
            {
                Log.Warn("Settings file damaged: " + ex.Message + "\nReverting to defaults");
            }
            #endregion

            Log.Info("Loaded default settings");
            Current = new Settings();
        }
コード例 #3
0
        /// <summary>
        /// Detects attacks such as feed substitution or replay attacks.
        /// </summary>
        /// <param name="data">The content of the feed file as a byte array.</param>
        /// <param name="uri">The URI the feed originally came from.</param>
        /// <param name="signature">The first trusted signature for the feed.</param>
        /// <exception cref="ReplayAttackException">A replay attack was detected.</exception>
        /// <exception cref="UriFormatException"><see cref="Feed.Uri"/> is missing or does not match <paramref name="uri"/>.</exception>
        private void DetectAttacks(byte[] data, FeedUri uri, ValidSignature signature)
        {
            // Detect feed substitution
            var feed = XmlStorage.LoadXml <Feed>(new MemoryStream(data));

            if (feed.Uri == null)
            {
                throw new UriFormatException(string.Format(Resources.FeedUriMissing, uri));
            }
            if (feed.Uri != uri)
            {
                throw new UriFormatException(string.Format(Resources.FeedUriMismatch, feed.Uri, uri));
            }

            // Detect replay attacks
            try
            {
                var oldSignature = _feedCache.GetSignatures(uri).OfType <ValidSignature>().FirstOrDefault();
                if (oldSignature != null && signature.Timestamp < oldSignature.Timestamp)
                {
                    throw new ReplayAttackException(uri, oldSignature.Timestamp, signature.Timestamp);
                }
            }
            catch (KeyNotFoundException)
            {
                // No existing feed to be replaced
            }
        }
コード例 #4
0
    /// <summary>
    /// Tests the sync logic with custom <see cref="AppList"/>s.
    /// </summary>
    /// <param name="resetMode">The <see cref="SyncResetMode"/> to pass to <see cref="SyncIntegrationManager.Sync"/>.</param>
    /// <param name="appListLocal">The current local <see cref="AppList"/>.</param>
    /// <param name="appListLast">The state of the <see cref="AppList"/> after the last successful sync.</param>
    /// <param name="appListServer">The current server-side <see cref="AppList"/>.</param>
    private static void TestSync(SyncResetMode resetMode, AppList appListLocal, AppList?appListLast, AppList appListServer)
    {
        string appListLocalPath = AppList.GetDefaultPath();

        appListLocal.SaveXml(appListLocalPath);
        appListLast?.SaveXml(appListLocalPath + SyncIntegrationManager.AppListLastSyncSuffix);

        using var appListServerPath = new TemporaryFile("0install-test-applist");
        {
            using (var stream = File.Create(appListServerPath))
                appListServer.SaveXmlZip(stream, CryptoKey);

            using (var appListServerFile = File.OpenRead(appListServerPath))
            {
                using var syncServer = new MicroServer("app-list", appListServerFile);
                var config = new Config
                {
                    SyncServer         = new(syncServer.ServerUri),
                    SyncServerUsername = "******",
                    SyncServerPassword = "******",
                    SyncCryptoKey      = CryptoKey
                };
                using (var integrationManager = new SyncIntegrationManager(config, _ => new Feed(), new SilentTaskHandler()))
                    integrationManager.Sync(resetMode);

                appListServer = AppList.LoadXmlZip(syncServer.FileContent, CryptoKey);
            }
        }

        appListLocal = XmlStorage.LoadXml <AppList>(appListLocalPath);
        appListLast  = XmlStorage.LoadXml <AppList>(appListLocalPath + SyncIntegrationManager.AppListLastSyncSuffix);
        appListServer.Should().Be(appListLocal, because: "Server and local data should be equal after sync");
        appListLast.Should().Be(appListLocal, because: "Last sync snapshot and local data should be equal after sync");
    }
コード例 #5
0
 private AppList LoadAppListSafe()
 {
     try
     {
         return(XmlStorage.LoadXml <AppList>(AppList.GetDefaultPath(_machineWide)));
     }
     #region Error handling
     catch (FileNotFoundException)
     {
         return(new AppList());
     }
     catch (IOException ex)
     {
         Log.Warn(Resources.UnableToLoadAppList);
         Log.Warn(ex);
         return(new AppList());
     }
     catch (UnauthorizedAccessException ex)
     {
         Log.Warn(Resources.UnableToLoadAppList);
         Log.Warn(ex);
         return(new AppList());
     }
     catch (InvalidDataException ex)
     {
         Log.Warn(Resources.UnableToLoadAppList);
         Log.Warn(ex);
         return(new AppList());
     }
     #endregion
 }
コード例 #6
0
        /// <inheritdoc/>
        public void Add(FeedUri feedUri, byte[] data)
        {
            #region Sanity checks
            if (feedUri == null)
            {
                throw new ArgumentNullException("feedUri");
            }
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }
            #endregion

            // Add to underlying cache
            _backingCache.Add(feedUri, data);

            // Add to memory cache (replacing existing old versions)
            var feed = XmlStorage.LoadXml <Feed>(new MemoryStream(data));
            feed.Normalize(feedUri);

            string key = feedUri.Escape();
            lock (_feedDictionary)
            {
                _feedDictionary.Remove(key);
                _feedDictionary.Add(key, feed);
            }
        }
コード例 #7
0
        public static AppList LoadXmlZip([NotNull] Stream stream, [CanBeNull] string password = null)
        {
            #region Sanity checks
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }
            #endregion

            using (var zipFile = new ZipFile(stream)
            {
                IsStreamOwner = false, Password = password
            })
            {
                var zipEntry = zipFile.Cast <ZipEntry>().First(x => StringUtils.EqualsIgnoreCase(x.Name, "data.xml"));

                try
                {
                    return(XmlStorage.LoadXml <AppList>(zipFile.GetInputStream(zipEntry)));
                }
                catch (InvalidOperationException)
                {
                    throw new InvalidDataException(Resources.SyncServerDataDamaged);
                }
            }
        }
コード例 #8
0
        /// <summary>
        /// Creates a new sync manager for a custom <see cref="AppList"/> file. Used for testing.
        /// </summary>
        /// <param name="appListPath">The storage location of the <see cref="AppList"/> file.</param>
        /// <param name="server">Access information for the sync server.</param>
        /// <param name="feedRetriever">Callback method used to retrieve additional <see cref="Feed"/>s on demand.</param>
        /// <param name="handler">A callback object used when the the user is to be informed about the progress of long-running operations such as downloads.</param>
        /// <param name="machineWide">Apply operations machine-wide instead of just for the current user.</param>
        /// <exception cref="IOException">A problem occurs while accessing the <see cref="AppList"/> file.</exception>
        /// <exception cref="UnauthorizedAccessException">Read or write access to the <see cref="AppList"/> file is not permitted or if another desktop integration class is currently active.</exception>
        /// <exception cref="InvalidDataException">A problem occurs while deserializing the XML data.</exception>
        public SyncIntegrationManager([NotNull] string appListPath, SyncServer server, [NotNull] Converter <FeedUri, Feed> feedRetriever, [NotNull] ITaskHandler handler, bool machineWide = false)
            : base(appListPath, handler, machineWide)
        {
            #region Sanity checks
            if (server.Uri == null)
            {
                throw new ArgumentNullException("server");
            }
            if (feedRetriever == null)
            {
                throw new ArgumentNullException("feedRetriever");
            }
            #endregion

            _server        = server;
            _feedRetriever = feedRetriever;

            if (File.Exists(AppListPath + AppListLastSyncSuffix))
            {
                _appListLastSync = XmlStorage.LoadXml <AppList>(AppListPath + AppListLastSyncSuffix);
            }
            else
            {
                _appListLastSync = new AppList();
                _appListLastSync.SaveXml(AppListPath + AppListLastSyncSuffix);
            }
        }
コード例 #9
0
        /// <summary>
        /// Creates a new integration manager using a custom <see cref="DesktopIntegration.AppList"/>. Do not use directly except for testing purposes!
        /// </summary>
        /// <param name="appListPath">The storage location of the <see cref="AppList"/> file.</param>
        /// <param name="handler">A callback object used when the the user is to be informed about the progress of long-running operations such as downloads.</param>
        /// <param name="machineWide">Apply operations machine-wide instead of just for the current user.</param>
        /// <exception cref="IOException">A problem occurs while accessing the <see cref="AppList"/> file.</exception>
        /// <exception cref="UnauthorizedAccessException">Read or write access to the <see cref="AppList"/> file is not permitted.</exception>
        /// <exception cref="InvalidDataException">A problem occurs while deserializing the XML data.</exception>
        public IntegrationManager([NotNull] string appListPath, [NotNull] ITaskHandler handler, bool machineWide = false) : base(handler)
        {
            #region Sanity checks
            if (appListPath == null)
            {
                throw new ArgumentNullException("appListPath");
            }
            if (handler == null)
            {
                throw new ArgumentNullException("handler");
            }
            #endregion

            MachineWide = machineWide;
            AppListPath = appListPath;

            if (File.Exists(AppListPath))
            {
                Log.Debug("Loading AppList from: " + AppListPath);
                AppList = XmlStorage.LoadXml <AppList>(AppListPath);
            }
            else
            {
                Log.Debug("Creating new AppList: " + AppListPath);
                AppList = new AppList();
                AppList.SaveXml(AppListPath);
            }
        }
コード例 #10
0
 public static AppList LoadSafe(bool machineWide = false)
 {
     try
     {
         return(XmlStorage.LoadXml <AppList>(GetDefaultPath(machineWide)));
     }
     #region Error handling
     catch (FileNotFoundException)
     {
         return(new AppList());
     }
     catch (IOException ex)
     {
         Log.Warn(ex);
         return(new AppList());
     }
     catch (UnauthorizedAccessException ex)
     {
         Log.Warn(ex);
         return(new AppList());
     }
     catch (InvalidDataException ex)
     {
         Log.Warn(ex);
         return(new AppList());
     }
     #endregion
 }
コード例 #11
0
        public void GetInstalledPackages([CanBeNull] string name)
        {
            var appList = XmlStorage.LoadXml <AppList>(AppList.GetDefaultPath(MachineWide));

            foreach (var entry in appList.Search(name))
            {
                Yield(entry.EffectiveRequirements);
            }
        }
コード例 #12
0
        /// <summary>
        /// Import a feed from a local file, as if it had been downloaded from the network.
        /// </summary>
        /// <param name="path">The local path of the feed file to import.</param>
        /// <exception cref="InvalidDataException">The feed specifies no <see cref="Feed.Uri"/>.</exception>
        private void ImportFile([NotNull] string path)
        {
            var feed = XmlStorage.LoadXml <Feed>(path);

            if (feed.Uri == null)
            {
                throw new InvalidDataException(Resources.ImportNoSource);
            }
            FeedManager.ImportFeed(path, feed.Uri, mirrorUrl: new FeedUri(path));
        }
コード例 #13
0
        public static SignedCatalog Load([NotNull] string path)
        {
            #region Sanity checks
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentNullException("path");
            }
            #endregion

            return(new SignedCatalog(XmlStorage.LoadXml <Catalog>(path), FeedUtils.GetKey(path, OpenPgpFactory.CreateDefault())));
        }
コード例 #14
0
ファイル: Selection.cs プロジェクト: isabella232/0install-win
 /// <summary>
 /// Trys to parse <see cref="Store.Model.Requirements.InterfaceUri"/> as a pre-computed <see cref="Store.Model.Selection.Selections"/> document.
 /// </summary>
 /// <seealso cref="SelectionsDocument"/>
 private void TryParseSelectionsDocument()
 {
     try
     { // Try to parse as selections document
         Selections = XmlStorage.LoadXml <Selections>(Requirements.InterfaceUri.LocalPath);
         Requirements.InterfaceUri = Selections.InterfaceUri;
         SelectionsDocument        = true;
     }
     catch (InvalidDataException)
     { // If that fails assume it is an interface
     }
 }
コード例 #15
0
ファイル: TrustDB.cs プロジェクト: isabella232/0install-win
        public static TrustDB Load()
        {
            string path = Locations.GetSaveConfigPath("0install.net", true, "injector", "trustdb.xml");

            if (!File.Exists(path))
            {
                return(new TrustDB());
            }

            Log.Debug("Loading trust database from: " + path);
            return(XmlStorage.LoadXml <TrustDB>(path));
        }
コード例 #16
0
        /// <summary>
        /// Loads a <see cref="Catalog"/> from an XML file and identifies the signature (if any).
        /// </summary>
        /// <param name="path">The file to load from.</param>
        /// <returns>The loaded <see cref="SignedCatalog"/>.</returns>
        /// <exception cref="IOException">A problem occurred while reading the file.</exception>
        /// <exception cref="UnauthorizedAccessException">Read access to the file is not permitted.</exception>
        /// <exception cref="InvalidDataException">A problem occurred while deserializing the XML data.</exception>
        public static SignedCatalog Load(string path)
        {
            #region Sanity checks
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentNullException(nameof(path));
            }
            #endregion

            var openPgp = OpenPgp.Signing();
            return(new SignedCatalog(XmlStorage.LoadXml <Catalog>(path), FeedUtils.GetKey(path, openPgp), openPgp));
        }
コード例 #17
0
        public void TestNormalizeHash()
        {
            var feed = CreateTestFeed();

            using var tempFile = new TemporaryFile("0install-unit-tests");
            feed.SaveXml(tempFile);
            var feedReload = XmlStorage.LoadXml <Feed>(tempFile);

            feed.Normalize(new FeedUri(tempFile));
            feedReload.Normalize(new FeedUri(tempFile));
            feedReload.GetHashCode().Should().Be(feed.GetHashCode());
        }
コード例 #18
0
 public Catalog?GetCached()
 {
     try
     {
         using (new MutexLock(CacheMutexName))
             return(XmlStorage.LoadXml <Catalog>(_cacheFilePath));
     }
     catch (FileNotFoundException)
     {
         return(null);
     }
 }
コード例 #19
0
ファイル: TrustDB.cs プロジェクト: archit9169/0install-dotnet
        /// <summary>
        /// Loads the <see cref="TrustDB"/> from a file.
        /// </summary>
        /// <param name="path">The file to load from.</param>
        /// <returns>The loaded <see cref="TrustDB"/>.</returns>
        /// <exception cref="IOException">A problem occured while reading the file.</exception>
        /// <exception cref="UnauthorizedAccessException">Read access to the file is not permitted.</exception>
        /// <exception cref="InvalidDataException">A problem occured while deserializing the XML data.</exception>
        public static TrustDB Load(string path)
        {
            #region Sanity checks
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentNullException(nameof(path));
            }
            #endregion

            Log.Debug("Loading trust database from: " + path);
            var trustDB = XmlStorage.LoadXml <TrustDB>(path);
            trustDB._filePath = path;
            return(trustDB);
        }
コード例 #20
0
        private static void CheckFeed(byte[] data, FeedUri feedUri)
        {
            // Detect feed substitution
            var feed = XmlStorage.LoadXml <Feed>(new MemoryStream(data));

            if (feed.Uri == null)
            {
                throw new InvalidDataException(string.Format(Resources.FeedUriMissing, feedUri));
            }
            if (feed.Uri != feedUri)
            {
                throw new InvalidDataException(string.Format(Resources.FeedUriMismatch, feed.Uri, feedUri));
            }
        }
コード例 #21
0
        /// <inheritdoc/>
        public Feed GetFeed(FeedUri feedUri)
        {
            #region Sanity checks
            if (feedUri == null)
            {
                throw new ArgumentNullException(nameof(feedUri));
            }
            #endregion

            string path = GetPath(feedUri);
            Log.Debug("Loading feed " + feedUri.ToStringRfc() + " from disk cache: " + path);

            return(XmlStorage.LoadXml <Feed>(path));
        }
コード例 #22
0
        private Catalog DownloadCatalog([NotNull] FeedUri source)
        {
            if (source.IsFile)
            {
                return(XmlStorage.LoadXml <Catalog>(source.LocalPath));
            }

            Log.Info("Downloading catalog: " + source.ToStringRfc());
            byte[] data;
            using (var webClient = new WebClientTimeout())
                data = webClient.DownloadData(source);
            _trustManager.CheckTrust(data, source);
            return(XmlStorage.LoadXml <Catalog>(new MemoryStream(data)));
        }
コード例 #23
0
    /// <summary>
    /// Creates a new sync manager. Performs Mutex-based locking!
    /// </summary>
    /// <param name="config">Configuration for communicating with a sync server.</param>
    /// <param name="feedRetriever">Callback method used to retrieve additional <see cref="Feed"/>s on demand.</param>
    /// <param name="handler">A callback object used when the the user is to be informed about the progress of long-running operations such as downloads.</param>
    /// <param name="machineWide">Apply operations machine-wide instead of just for the current user.</param>
    /// <exception cref="IOException">A problem occurred while accessing the <see cref="AppList"/> file.</exception>
    /// <exception cref="UnauthorizedAccessException">Read or write access to the <see cref="AppList"/> file is not permitted or another desktop integration class is currently active.</exception>
    /// <exception cref="InvalidDataException">A problem occurred while deserializing the XML data.</exception>
    public SyncIntegrationManager(Config config, Converter<FeedUri, Feed> feedRetriever, ITaskHandler handler, bool machineWide = false)
        : base(config, handler, machineWide)
    {
        if (!config.IsSyncConfigured) throw new InvalidDataException(Resources.PleaseConfigSync);
        _syncServer = config.SyncServer.EnsureTrailingSlash();

        _feedRetriever = feedRetriever ?? throw new ArgumentNullException(nameof(feedRetriever));

        if (File.Exists(AppListPath + AppListLastSyncSuffix)) _appListLastSync = XmlStorage.LoadXml<AppList>(AppListPath + AppListLastSyncSuffix);
        else
        {
            _appListLastSync = new AppList();
            _appListLastSync.SaveXml(AppListPath + AppListLastSyncSuffix);
        }
    }
コード例 #24
0
        public void TestFile()
        {
            TestData testData1 = new TestData {
                Data = "Hello"
            }, testData2;

            using (var tempFile = new TemporaryFile("unit-tests"))
            {
                // Write and read file
                testData1.SaveXml(tempFile);
                testData2 = XmlStorage.LoadXml <TestData>(tempFile);
            }

            // Ensure data stayed the same
            testData2.Data.Should().Be(testData1.Data);
        }
コード例 #25
0
        public void TestSaveLoad()
        {
            Feed feed1 = CreateTestFeed(), feed2;

            using (var tempFile = new TemporaryFile("0install-unit-tests"))
            {
                // Write and read file
                feed1.SaveXml(tempFile);
                feed2 = XmlStorage.LoadXml <Feed>(tempFile);
            }

            // Ensure data stayed the same
            feed2.Should().Be(feed1, because: "Serialized objects should be equal.");
            feed2.GetHashCode().Should().Be(feed1.GetHashCode(), because: "Serialized objects' hashes should be equal.");
            feed2.Should().NotBeSameAs(feed1, because: "Serialized objects should not return the same reference.");
        }
コード例 #26
0
        public void TestFileRelative()
        {
            TestData testData1 = new TestData {
                Data = "Hello"
            }, testData2;

            using (new TemporaryWorkingDirectory("unit-tests"))
            {
                // Write and read file
                testData1.SaveXml("file.xml");
                testData2 = XmlStorage.LoadXml <TestData>("file.xml");
            }

            // Ensure data stayed the same
            testData2.Data.Should().Be(testData1.Data);
        }
コード例 #27
0
    [Fact] // Ensures that the class is correctly serialized and deserialized.
    public void TestSaveLoad()
    {
        CapabilityList capabilityList1 = CreateTestCapabilityList(), capabilityList2;

        using (var tempFile = new TemporaryFile("0install-test-capabilities"))
        {
            // Write and read file
            capabilityList1.SaveXml(tempFile);
            capabilityList2 = XmlStorage.LoadXml <CapabilityList>(tempFile);
        }

        // Ensure data stayed the same
        capabilityList2.Should().Be(capabilityList1, because: "Serialized objects should be equal.");
        capabilityList2.GetHashCode().Should().Be(capabilityList1.GetHashCode(), because: "Serialized objects' hashes should be equal.");
        capabilityList2.Should().NotBeSameAs(capabilityList1, because: "Serialized objects should not return the same reference.");
    }
コード例 #28
0
        public void TestSaveLoad()
        {
            Selections selections1 = CreateTestSelections(), selections2;

            using (var tempFile = new TemporaryFile("0install-unit-tests"))
            {
                // Write and read file
                selections1.SaveXml(tempFile);
                selections2 = XmlStorage.LoadXml <Selections>(tempFile);
            }

            // Ensure data stayed the same
            selections2.Should().Be(selections1, because: "Serialized objects should be equal.");
            selections2.GetHashCode().Should().Be(selections1.GetHashCode(), because: "Serialized objects' hashes should be equal.");
            selections2.Should().NotBeSameAs(selections1, because: "Serialized objects should not return the same reference.");
        }
コード例 #29
0
        public void TestSaveLoad()
        {
            FeedPreferences preferences1 = CreateTestFeedPreferences(), preferences2;
            Assert.That(preferences1, Is.XmlSerializable);
            using (var tempFile = new TemporaryFile("0install-unit-tests"))
            {
                // Write and read file
                preferences1.SaveXml(tempFile);
                preferences2 = XmlStorage.LoadXml<FeedPreferences>(tempFile);
            }

            // Ensure data stayed the same
            preferences2.Should().Be(preferences1, because: "Serialized objects should be equal.");
            preferences2.GetHashCode().Should().Be(preferences1.GetHashCode(), because: "Serialized objects' hashes should be equal.");
            preferences2.Should().NotBeSameAs(preferences1, because: "Serialized objects should not return the same reference.");
        }
コード例 #30
0
        /// <inheritdoc/>
        public void ImportFeed(string path)
        {
            #region Sanity checks
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentNullException(nameof(path));
            }
            #endregion

            var feed = XmlStorage.LoadXml <Feed>(path);
            if (feed.Uri == null)
            {
                throw new InvalidDataException(Resources.ImportNoSource);
            }
            ImportFeed(File.ReadAllBytes(path), feed.Uri, path);
        }