aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations')
-rw-r--r--Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs3
-rw-r--r--Emby.Server.Implementations/Activity/ActivityRepository.cs9
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs1749
-rw-r--r--Emby.Server.Implementations/ApplicationPathHelper.cs51
-rw-r--r--Emby.Server.Implementations/Channels/ChannelImageProvider.cs8
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs39
-rw-r--r--Emby.Server.Implementations/Collections/CollectionImageProvider.cs6
-rw-r--r--Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs5
-rw-r--r--Emby.Server.Implementations/Cryptography/ASN1.cs340
-rw-r--r--Emby.Server.Implementations/Cryptography/ASN1Convert.cs207
-rw-r--r--Emby.Server.Implementations/Cryptography/BitConverterLE.cs239
-rw-r--r--Emby.Server.Implementations/Cryptography/CertificateGenerator.cs69
-rw-r--r--Emby.Server.Implementations/Cryptography/CryptoConvert.cs745
-rw-r--r--Emby.Server.Implementations/Cryptography/PKCS1.cs491
-rw-r--r--Emby.Server.Implementations/Cryptography/PKCS12.cs1934
-rw-r--r--Emby.Server.Implementations/Cryptography/PKCS7.cs1012
-rw-r--r--Emby.Server.Implementations/Cryptography/PKCS8.cs495
-rw-r--r--Emby.Server.Implementations/Cryptography/PfxGenerator.cs75
-rw-r--r--Emby.Server.Implementations/Cryptography/X501Name.cs393
-rw-r--r--Emby.Server.Implementations/Cryptography/X509Builder.cs153
-rw-r--r--Emby.Server.Implementations/Cryptography/X509Certificate.cs563
-rw-r--r--Emby.Server.Implementations/Cryptography/X509CertificateBuilder.cs245
-rw-r--r--Emby.Server.Implementations/Cryptography/X509CertificateCollection.cs201
-rw-r--r--Emby.Server.Implementations/Cryptography/X509Extension.cs208
-rw-r--r--Emby.Server.Implementations/Cryptography/X509Extensions.cs195
-rw-r--r--Emby.Server.Implementations/Cryptography/X520Attributes.cs346
-rw-r--r--Emby.Server.Implementations/Data/BaseSqliteRepository.cs25
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs108
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs18
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj84
-rw-r--r--Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs335
-rw-r--r--Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs3
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpListenerHost.cs17
-rw-r--r--Emby.Server.Implementations/HttpServer/LoggerUtils.cs3
-rw-r--r--Emby.Server.Implementations/HttpServer/SocketSharp/HttpUtility.cs3
-rw-r--r--Emby.Server.Implementations/HttpServerFactory.cs110
-rw-r--r--Emby.Server.Implementations/IO/LibraryMonitor.cs652
-rw-r--r--Emby.Server.Implementations/IO/MemoryStreamProvider.cs56
-rw-r--r--Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs38
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs39
-rw-r--r--Emby.Server.Implementations/Library/LocalTrailerPostScanTask.cs5
-rw-r--r--Emby.Server.Implementations/Library/MusicManager.cs22
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs7
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs12
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs2
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs9
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs12
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs5
-rw-r--r--Emby.Server.Implementations/Library/UserViewManager.cs5
-rw-r--r--Emby.Server.Implementations/Library/Validators/PeopleValidator.cs17
-rw-r--r--Emby.Server.Implementations/LiveTv/ChannelImageProvider.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs8
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs51
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs3
-rw-r--r--Emby.Server.Implementations/LiveTv/RecordingImageProvider.cs6
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs6
-rw-r--r--Emby.Server.Implementations/Localization/TextLocalizer.cs63
-rw-r--r--Emby.Server.Implementations/Logging/ConsoleLogger.cs13
-rw-r--r--Emby.Server.Implementations/MediaEncoder/EncodingManager.cs2
-rw-r--r--Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs13
-rw-r--r--Emby.Server.Implementations/Notifications/WebSocketNotifier.cs3
-rw-r--r--Emby.Server.Implementations/Photos/PhotoAlbumImageProvider.cs6
-rw-r--r--Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs17
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs3
-rw-r--r--Emby.Server.Implementations/Security/AuthenticationRepository.cs9
-rw-r--r--Emby.Server.Implementations/Services/ServiceController.cs8
-rw-r--r--Emby.Server.Implementations/Services/ServiceExec.cs3
-rw-r--r--Emby.Server.Implementations/Services/ServicePath.cs11
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs5
-rw-r--r--Emby.Server.Implementations/Social/SharingRepository.cs3
-rw-r--r--Emby.Server.Implementations/SystemEvents.cs50
-rw-r--r--Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs20
-rw-r--r--Emby.Server.Implementations/UserViews/DynamicImageProvider.cs16
-rw-r--r--Emby.Server.Implementations/packages.config5
75 files changed, 11379 insertions, 321 deletions
diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
index 567f139fd..702917832 100644
--- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
+++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
@@ -18,6 +18,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.Activity
{
@@ -436,7 +437,7 @@ namespace Emby.Server.Implementations.Activity
{
Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
Type = "ScheduledTaskFailed",
- Overview = string.Join(Environment.NewLine, vals.ToArray()),
+ Overview = string.Join(Environment.NewLine, vals.ToArray(vals.Count)),
ShortOverview = runningTime,
Severity = LogSeverity.Error
});
diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs
index e9b6f7a40..7720f8f2f 100644
--- a/Emby.Server.Implementations/Activity/ActivityRepository.cs
+++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs
@@ -10,6 +10,7 @@ using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using SQLitePCL.pretty;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.Activity
{
@@ -94,13 +95,13 @@ namespace Emby.Server.Implementations.Activity
var whereTextWithoutPaging = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count));
if (startIndex.HasValue && startIndex.Value > 0)
{
var pagingWhereText = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count));
whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLogEntries {0} ORDER BY DateCreated DESC LIMIT {1})",
pagingWhereText,
@@ -109,7 +110,7 @@ namespace Emby.Server.Implementations.Activity
var whereText = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count));
commandText += whereText;
@@ -154,7 +155,7 @@ namespace Emby.Server.Implementations.Activity
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
}
- result.Items = list.ToArray();
+ result.Items = list.ToArray(list.Count);
return result;
}, ReadTransactionMode);
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
new file mode 100644
index 000000000..f1f24660c
--- /dev/null
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -0,0 +1,1749 @@
+using Emby.Common.Implementations;
+using Emby.Common.Implementations.Archiving;
+using Emby.Common.Implementations.IO;
+using Emby.Common.Implementations.Reflection;
+using Emby.Common.Implementations.ScheduledTasks;
+using Emby.Common.Implementations.Serialization;
+using Emby.Common.Implementations.TextEncoding;
+using Emby.Common.Implementations.Xml;
+using Emby.Dlna;
+using Emby.Dlna.ConnectionManager;
+using Emby.Dlna.ContentDirectory;
+using Emby.Dlna.Main;
+using Emby.Dlna.MediaReceiverRegistrar;
+using Emby.Dlna.Ssdp;
+using Emby.Drawing;
+using Emby.Photos;
+using Emby.Server.Implementations.Activity;
+using Emby.Server.Implementations.Channels;
+using Emby.Server.Implementations.Collections;
+using Emby.Server.Implementations.Configuration;
+using Emby.Server.Implementations.Data;
+using Emby.Server.Implementations.Devices;
+using Emby.Server.Implementations.Dto;
+using Emby.Server.Implementations.FFMpeg;
+using Emby.Server.Implementations.HttpServer;
+using Emby.Server.Implementations.HttpServer.Security;
+using Emby.Server.Implementations.IO;
+using Emby.Server.Implementations.Library;
+using Emby.Server.Implementations.LiveTv;
+using Emby.Server.Implementations.Localization;
+using Emby.Server.Implementations.MediaEncoder;
+using Emby.Server.Implementations.Migrations;
+using Emby.Server.Implementations.Notifications;
+using Emby.Server.Implementations.Playlists;
+using Emby.Server.Implementations.Security;
+using Emby.Server.Implementations.Session;
+using Emby.Server.Implementations.Social;
+using Emby.Server.Implementations.TV;
+using Emby.Server.Implementations.Updates;
+using MediaBrowser.Api;
+using MediaBrowser.Common;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Events;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Common.Progress;
+using MediaBrowser.Common.Security;
+using MediaBrowser.Common.Updates;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Chapters;
+using MediaBrowser.Controller.Collections;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Connect;
+using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Notifications;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Playlists;
+using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Controller.Security;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Controller.Subtitles;
+using MediaBrowser.Controller.Sync;
+using MediaBrowser.Controller.TV;
+using MediaBrowser.LocalMetadata.Savers;
+using MediaBrowser.Model.Activity;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Diagnostics;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Net;
+using MediaBrowser.Model.News;
+using MediaBrowser.Model.Reflection;
+using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.Services;
+using MediaBrowser.Model.Social;
+using MediaBrowser.Model.System;
+using MediaBrowser.Model.Text;
+using MediaBrowser.Model.Updates;
+using MediaBrowser.Model.Xml;
+using MediaBrowser.Providers.Chapters;
+using MediaBrowser.Providers.Manager;
+using MediaBrowser.Providers.Subtitles;
+using MediaBrowser.WebDashboard.Api;
+using MediaBrowser.XbmcMetadata.Providers;
+using OpenSubtitlesHandler;
+using ServiceStack;
+using SocketHttpListener.Primitives;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+using Emby.Server.MediaEncoding.Subtitles;
+using MediaBrowser.MediaEncoding.BdInfo;
+using StringExtensions = MediaBrowser.Controller.Extensions.StringExtensions;
+
+namespace Emby.Server.Implementations
+{
+ /// <summary>
+ /// Class CompositionRoot
+ /// </summary>
+ public abstract class ApplicationHost : BaseApplicationHost<ServerApplicationPaths>, IServerApplicationHost, IDependencyContainer
+ {
+ /// <summary>
+ /// Gets the server configuration manager.
+ /// </summary>
+ /// <value>The server configuration manager.</value>
+ public IServerConfigurationManager ServerConfigurationManager
+ {
+ get { return (IServerConfigurationManager)ConfigurationManager; }
+ }
+
+ /// <summary>
+ /// Gets the configuration manager.
+ /// </summary>
+ /// <returns>IConfigurationManager.</returns>
+ protected override IConfigurationManager GetConfigurationManager()
+ {
+ return new ServerConfigurationManager(ApplicationPaths, LogManager, XmlSerializer, FileSystemManager);
+ }
+
+ /// <summary>
+ /// Gets or sets the server manager.
+ /// </summary>
+ /// <value>The server manager.</value>
+ private IServerManager ServerManager { get; set; }
+ /// <summary>
+ /// Gets or sets the user manager.
+ /// </summary>
+ /// <value>The user manager.</value>
+ public IUserManager UserManager { get; set; }
+ /// <summary>
+ /// Gets or sets the library manager.
+ /// </summary>
+ /// <value>The library manager.</value>
+ internal ILibraryManager LibraryManager { get; set; }
+ /// <summary>
+ /// Gets or sets the directory watchers.
+ /// </summary>
+ /// <value>The directory watchers.</value>
+ private ILibraryMonitor LibraryMonitor { get; set; }
+ /// <summary>
+ /// Gets or sets the provider manager.
+ /// </summary>
+ /// <value>The provider manager.</value>
+ private IProviderManager ProviderManager { get; set; }
+ /// <summary>
+ /// Gets or sets the HTTP server.
+ /// </summary>
+ /// <value>The HTTP server.</value>
+ private IHttpServer HttpServer { get; set; }
+ private IDtoService DtoService { get; set; }
+ public IImageProcessor ImageProcessor { get; set; }
+
+ /// <summary>
+ /// Gets or sets the media encoder.
+ /// </summary>
+ /// <value>The media encoder.</value>
+ private IMediaEncoder MediaEncoder { get; set; }
+ private ISubtitleEncoder SubtitleEncoder { get; set; }
+
+ private IConnectManager ConnectManager { get; set; }
+ private ISessionManager SessionManager { get; set; }
+
+ private ILiveTvManager LiveTvManager { get; set; }
+
+ public ILocalizationManager LocalizationManager { get; set; }
+
+ private IEncodingManager EncodingManager { get; set; }
+ private IChannelManager ChannelManager { get; set; }
+ private ISyncManager SyncManager { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user data repository.
+ /// </summary>
+ /// <value>The user data repository.</value>
+ private IUserDataManager UserDataManager { get; set; }
+ private IUserRepository UserRepository { get; set; }
+ internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; }
+ internal IItemRepository ItemRepository { get; set; }
+ private INotificationsRepository NotificationsRepository { get; set; }
+
+ private INotificationManager NotificationManager { get; set; }
+ private ISubtitleManager SubtitleManager { get; set; }
+ private IChapterManager ChapterManager { get; set; }
+ private IDeviceManager DeviceManager { get; set; }
+
+ internal IUserViewManager UserViewManager { get; set; }
+
+ private IAuthenticationRepository AuthenticationRepository { get; set; }
+ private ISyncRepository SyncRepository { get; set; }
+ private ITVSeriesManager TVSeriesManager { get; set; }
+ private ICollectionManager CollectionManager { get; set; }
+ private IMediaSourceManager MediaSourceManager { get; set; }
+ private IPlaylistManager PlaylistManager { get; set; }
+
+ /// <summary>
+ /// Gets or sets the installation manager.
+ /// </summary>
+ /// <value>The installation manager.</value>
+ protected IInstallationManager InstallationManager { get; private set; }
+ /// <summary>
+ /// Gets the security manager.
+ /// </summary>
+ /// <value>The security manager.</value>
+ protected ISecurityManager SecurityManager { get; private set; }
+
+ /// <summary>
+ /// Gets or sets the zip client.
+ /// </summary>
+ /// <value>The zip client.</value>
+ protected IZipClient ZipClient { get; private set; }
+
+ protected IAuthService AuthService { get; private set; }
+
+ protected readonly StartupOptions StartupOptions;
+ private readonly string _releaseAssetFilename;
+
+ internal IPowerManagement PowerManagement { get; private set; }
+ internal IImageEncoder ImageEncoder { get; private set; }
+
+ private readonly Action<string, string, string> _certificateGenerator;
+ private readonly Func<string> _defaultUserNameFactory;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ApplicationHost" /> class.
+ /// </summary>
+ public ApplicationHost(ServerApplicationPaths applicationPaths,
+ ILogManager logManager,
+ StartupOptions options,
+ IFileSystem fileSystem,
+ IPowerManagement powerManagement,
+ string releaseAssetFilename,
+ IEnvironmentInfo environmentInfo,
+ IImageEncoder imageEncoder,
+ ISystemEvents systemEvents,
+ IMemoryStreamFactory memoryStreamFactory,
+ INetworkManager networkManager,
+ Action<string, string, string> certificateGenerator,
+ Func<string> defaultUsernameFactory)
+ : base(applicationPaths,
+ logManager,
+ fileSystem,
+ environmentInfo,
+ systemEvents,
+ memoryStreamFactory,
+ networkManager)
+ {
+ StartupOptions = options;
+ _certificateGenerator = certificateGenerator;
+ _releaseAssetFilename = releaseAssetFilename;
+ _defaultUserNameFactory = defaultUsernameFactory;
+ PowerManagement = powerManagement;
+
+ ImageEncoder = imageEncoder;
+
+ SetBaseExceptionMessage();
+
+ if (environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows)
+ {
+ fileSystem.AddShortcutHandler(new LnkShortcutHandler());
+ }
+
+ fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
+ }
+
+ private Version _version;
+ /// <summary>
+ /// Gets the current application version
+ /// </summary>
+ /// <value>The application version.</value>
+ public override Version ApplicationVersion
+ {
+ get
+ {
+ return _version ?? (_version = GetAssembly(GetType()).GetName().Version);
+ }
+ }
+
+ public virtual bool SupportsRunningAsService
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Gets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public override string Name
+ {
+ get
+ {
+ return "Emby Server";
+ }
+ }
+
+ public virtual bool IsRunningAsService
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ private Assembly GetAssembly(Type type)
+ {
+ return type.GetTypeInfo().Assembly;
+ }
+
+ public virtual bool SupportsAutoRunAtStartup
+ {
+ get
+ {
+ return EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows;
+ }
+ }
+
+ private void SetBaseExceptionMessage()
+ {
+ var builder = GetBaseExceptionMessage(ApplicationPaths);
+
+ builder.Insert(0, string.Format("Version: {0}{1}", ApplicationVersion, Environment.NewLine));
+ builder.Insert(0, "*** Error Report ***" + Environment.NewLine);
+
+ LogManager.ExceptionMessagePrefix = builder.ToString();
+ }
+
+ /// <summary>
+ /// Runs the startup tasks.
+ /// </summary>
+ public override async Task RunStartupTasks()
+ {
+ await PerformPreInitMigrations().ConfigureAwait(false);
+
+ await base.RunStartupTasks().ConfigureAwait(false);
+
+ await MediaEncoder.Init().ConfigureAwait(false);
+
+ if (string.IsNullOrWhiteSpace(MediaEncoder.EncoderPath))
+ {
+ if (ServerConfigurationManager.Configuration.IsStartupWizardCompleted)
+ {
+ ServerConfigurationManager.Configuration.IsStartupWizardCompleted = false;
+ ServerConfigurationManager.SaveConfiguration();
+ }
+ }
+
+ Logger.Info("ServerId: {0}", SystemId);
+ Logger.Info("Core startup complete");
+ HttpServer.GlobalResponse = null;
+
+ PerformPostInitMigrations();
+ Logger.Info("Post-init migrations complete");
+
+ foreach (var entryPoint in GetExports<IServerEntryPoint>().ToList())
+ {
+ var name = entryPoint.GetType().FullName;
+ Logger.Info("Starting entry point {0}", name);
+ var now = DateTime.UtcNow;
+ try
+ {
+ entryPoint.Run();
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error in {0}", ex, name);
+ }
+ Logger.Info("Entry point completed: {0}. Duration: {1} seconds", name, (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture), "ImageInfos");
+ }
+ Logger.Info("All entry points have started");
+
+ LogManager.RemoveConsoleOutput();
+ }
+
+ protected override IJsonSerializer CreateJsonSerializer()
+ {
+ try
+ {
+ // https://github.com/ServiceStack/ServiceStack/blob/master/tests/ServiceStack.WebHost.IntegrationTests/Web.config#L4
+ Licensing.RegisterLicense("1001-e1JlZjoxMDAxLE5hbWU6VGVzdCBCdXNpbmVzcyxUeXBlOkJ1c2luZXNzLEhhc2g6UHVNTVRPclhvT2ZIbjQ5MG5LZE1mUTd5RUMzQnBucTFEbTE3TDczVEF4QUNMT1FhNXJMOWkzVjFGL2ZkVTE3Q2pDNENqTkQyUktRWmhvUVBhYTBiekJGUUZ3ZE5aZHFDYm9hL3lydGlwUHI5K1JsaTBYbzNsUC85cjVJNHE5QVhldDN6QkE4aTlvdldrdTgyTk1relY2eis2dFFqTThYN2lmc0JveHgycFdjPSxFeHBpcnk6MjAxMy0wMS0wMX0=");
+ }
+ catch
+ {
+ // Failing under mono
+ }
+
+ var result = new JsonSerializer(FileSystemManager, LogManager.GetLogger("JsonSerializer"));
+
+ ServiceStack.Text.JsConfig<LiveTvProgram>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<LiveTvChannel>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<LiveTvVideoRecording>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<LiveTvAudioRecording>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Series>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Audio>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<MusicAlbum>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<MusicArtist>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<MusicGenre>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<MusicVideo>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Movie>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Playlist>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<AudioPodcast>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<AudioBook>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Trailer>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<BoxSet>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Episode>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Season>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Book>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<CollectionFolder>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Folder>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Game>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<GameGenre>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<GameSystem>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Genre>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Person>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Photo>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<PhotoAlbum>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Studio>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<UserRootFolder>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<UserView>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Video>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Year>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<Channel>.ExcludePropertyNames = new[] { "ProviderIds" };
+ ServiceStack.Text.JsConfig<AggregateFolder>.ExcludePropertyNames = new[] { "ProviderIds" };
+
+ return result;
+ }
+
+ public override Task Init(IProgress<double> progress)
+ {
+ HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
+ HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
+
+ // Safeguard against invalid configuration
+ if (HttpPort == HttpsPort)
+ {
+ HttpPort = ServerConfiguration.DefaultHttpPort;
+ HttpsPort = ServerConfiguration.DefaultHttpsPort;
+ }
+
+ return base.Init(progress);
+ }
+
+ private async Task PerformPreInitMigrations()
+ {
+ var migrations = new List<IVersionMigration>
+ {
+ };
+
+ foreach (var task in migrations)
+ {
+ try
+ {
+ await task.Run().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error running migration", ex);
+ }
+ }
+ }
+
+ private void PerformPostInitMigrations()
+ {
+ var migrations = new List<IVersionMigration>
+ {
+ };
+
+ foreach (var task in migrations)
+ {
+ try
+ {
+ task.Run();
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error running migration", ex);
+ }
+ }
+ }
+
+ protected abstract IConnectManager CreateConnectManager();
+ protected abstract ISyncManager CreateSyncManager();
+
+ /// <summary>
+ /// Registers resources that classes will depend on
+ /// </summary>
+ protected override async Task RegisterResources(IProgress<double> progress)
+ {
+ await base.RegisterResources(progress).ConfigureAwait(false);
+
+ RegisterSingleInstance(PowerManagement);
+
+ SecurityManager = new PluginSecurityManager(this, HttpClient, JsonSerializer, ApplicationPaths, LogManager, FileSystemManager, CryptographyProvider);
+ RegisterSingleInstance(SecurityManager);
+
+ InstallationManager = new InstallationManager(LogManager.GetLogger("InstallationManager"), this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, ConfigurationManager, FileSystemManager, CryptographyProvider);
+ RegisterSingleInstance(InstallationManager);
+
+ ZipClient = new ZipClient(FileSystemManager);
+ RegisterSingleInstance(ZipClient);
+
+ RegisterSingleInstance<IHttpResultFactory>(new HttpResultFactory(LogManager, FileSystemManager, JsonSerializer, MemoryStreamFactory));
+
+ RegisterSingleInstance<IServerApplicationHost>(this);
+ RegisterSingleInstance<IServerApplicationPaths>(ApplicationPaths);
+
+ RegisterSingleInstance(ServerConfigurationManager);
+
+ IAssemblyInfo assemblyInfo = new AssemblyInfo();
+ RegisterSingleInstance<IAssemblyInfo>(assemblyInfo);
+
+ LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LogManager.GetLogger("LocalizationManager"), assemblyInfo, new TextLocalizer());
+ StringExtensions.LocalizationManager = LocalizationManager;
+ RegisterSingleInstance(LocalizationManager);
+
+ ITextEncoding textEncoding = new TextEncoding(FileSystemManager, LogManager.GetLogger("TextEncoding"), JsonSerializer);
+ RegisterSingleInstance(textEncoding);
+ Utilities.EncodingHelper = textEncoding;
+ RegisterSingleInstance<IBlurayExaminer>(() => new BdInfoExaminer(FileSystemManager, textEncoding));
+
+ RegisterSingleInstance<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory());
+
+ UserDataManager = new UserDataManager(LogManager, ServerConfigurationManager);
+ RegisterSingleInstance(UserDataManager);
+
+ UserRepository = GetUserRepository();
+ // This is only needed for disposal purposes. If removing this, make sure to have the manager handle disposing it
+ RegisterSingleInstance(UserRepository);
+
+ var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LogManager.GetLogger("SqliteDisplayPreferencesRepository"), JsonSerializer, ApplicationPaths, MemoryStreamFactory);
+ DisplayPreferencesRepository = displayPreferencesRepo;
+ RegisterSingleInstance(DisplayPreferencesRepository);
+
+ var itemRepo = new SqliteItemRepository(ServerConfigurationManager, JsonSerializer, LogManager.GetLogger("SqliteItemRepository"), MemoryStreamFactory, assemblyInfo, FileSystemManager, EnvironmentInfo, TimerFactory);
+ ItemRepository = itemRepo;
+ RegisterSingleInstance(ItemRepository);
+
+ AuthenticationRepository = GetAuthenticationRepository();
+ RegisterSingleInstance(AuthenticationRepository);
+
+ UserManager = new UserManager(LogManager.GetLogger("UserManager"), ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, () => ConnectManager, this, JsonSerializer, FileSystemManager, CryptographyProvider, _defaultUserNameFactory());
+ RegisterSingleInstance(UserManager);
+
+ LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager);
+ RegisterSingleInstance(LibraryManager);
+
+ var musicManager = new MusicManager(LibraryManager);
+ RegisterSingleInstance<IMusicManager>(new MusicManager(LibraryManager));
+
+ LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, TimerFactory, SystemEvents, EnvironmentInfo);
+ RegisterSingleInstance(LibraryMonitor);
+
+ ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer, MemoryStreamFactory);
+ RegisterSingleInstance(ProviderManager);
+
+ RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LogManager, LibraryManager, UserManager));
+
+ CertificateInfo = GetCertificateInfo(true);
+ Certificate = GetCertificate(CertificateInfo);
+
+ HttpServer = HttpServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamFactory, "Emby", "web/index.html", textEncoding, SocketFactory, CryptographyProvider, JsonSerializer, XmlSerializer, EnvironmentInfo, Certificate, FileSystemManager, SupportsDualModeSockets);
+ HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
+ RegisterSingleInstance(HttpServer, false);
+ progress.Report(10);
+
+ ServerManager = new ServerManager.ServerManager(this, JsonSerializer, LogManager.GetLogger("ServerManager"), ServerConfigurationManager, MemoryStreamFactory, textEncoding);
+ RegisterSingleInstance(ServerManager);
+
+ var innerProgress = new ActionableProgress<double>();
+ innerProgress.RegisterAction(p => progress.Report((.75 * p) + 15));
+
+ ImageProcessor = GetImageProcessor();
+ RegisterSingleInstance(ImageProcessor);
+
+ TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager);
+ RegisterSingleInstance(TVSeriesManager);
+
+ SyncManager = CreateSyncManager();
+ RegisterSingleInstance(SyncManager);
+
+ DtoService = new DtoService(LogManager.GetLogger("DtoService"), LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, SyncManager, this, () => DeviceManager, () => MediaSourceManager, () => LiveTvManager);
+ RegisterSingleInstance(DtoService);
+
+ var encryptionManager = new EncryptionManager();
+ RegisterSingleInstance<IEncryptionManager>(encryptionManager);
+
+ ConnectManager = CreateConnectManager();
+ RegisterSingleInstance(ConnectManager);
+
+ DeviceManager = new DeviceManager(new DeviceRepository(ApplicationPaths, JsonSerializer, LogManager.GetLogger("DeviceManager"), FileSystemManager), UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LogManager.GetLogger("DeviceManager"), NetworkManager);
+ RegisterSingleInstance(DeviceManager);
+
+ var newsService = new Emby.Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer);
+ RegisterSingleInstance<INewsService>(newsService);
+
+ progress.Report(15);
+
+ ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LogManager.GetLogger("ChannelManager"), ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager);
+ RegisterSingleInstance(ChannelManager);
+
+ MediaSourceManager = new MediaSourceManager(ItemRepository, UserManager, LibraryManager, LogManager.GetLogger("MediaSourceManager"), JsonSerializer, FileSystemManager, UserDataManager, TimerFactory);
+ RegisterSingleInstance(MediaSourceManager);
+
+ SessionManager = new SessionManager(UserDataManager, LogManager.GetLogger("SessionManager"), LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager, TimerFactory);
+ RegisterSingleInstance(SessionManager);
+
+ var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("Dlna"), JsonSerializer, this, assemblyInfo);
+ RegisterSingleInstance<IDlnaManager>(dlnaManager);
+
+ var connectionManager = new ConnectionManager(dlnaManager, ServerConfigurationManager, LogManager.GetLogger("UpnpConnectionManager"), HttpClient, new XmlReaderSettingsFactory());
+ RegisterSingleInstance<IConnectionManager>(connectionManager);
+
+ CollectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("CollectionManager"), ProviderManager);
+ RegisterSingleInstance(CollectionManager);
+
+ PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("PlaylistManager"), UserManager, ProviderManager);
+ RegisterSingleInstance<IPlaylistManager>(PlaylistManager);
+
+ LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, ProviderManager, FileSystemManager, SecurityManager);
+ RegisterSingleInstance(LiveTvManager);
+
+ UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
+ RegisterSingleInstance(UserViewManager);
+
+ var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, LibraryManager, ServerConfigurationManager, UserManager, LogManager.GetLogger("UpnpContentDirectory"), HttpClient, LocalizationManager, ChannelManager, MediaSourceManager, UserViewManager, () => MediaEncoder, new XmlReaderSettingsFactory(), TVSeriesManager);
+ RegisterSingleInstance<IContentDirectory>(contentDirectory);
+
+ var mediaRegistrar = new MediaReceiverRegistrar(LogManager.GetLogger("MediaReceiverRegistrar"), HttpClient, ServerConfigurationManager, new XmlReaderSettingsFactory());
+ RegisterSingleInstance<IMediaReceiverRegistrar>(mediaRegistrar);
+
+ NotificationManager = new NotificationManager(LogManager, UserManager, ServerConfigurationManager);
+ RegisterSingleInstance(NotificationManager);
+
+ SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, MediaSourceManager);
+ RegisterSingleInstance(SubtitleManager);
+
+ RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager, SocketFactory, TimerFactory));
+
+ ChapterManager = new ChapterManager(LibraryManager, LogManager.GetLogger("ChapterManager"), ServerConfigurationManager, ItemRepository);
+ RegisterSingleInstance(ChapterManager);
+
+ await RegisterMediaEncoder(innerProgress).ConfigureAwait(false);
+ progress.Report(90);
+
+ EncodingManager = new EncodingManager(FileSystemManager, Logger, MediaEncoder, ChapterManager, LibraryManager);
+ RegisterSingleInstance(EncodingManager);
+
+ var sharingRepo = new SharingRepository(LogManager.GetLogger("SharingRepository"), ApplicationPaths);
+ sharingRepo.Initialize();
+ // This is only needed for disposal purposes. If removing this, make sure to have the manager handle disposing it
+ RegisterSingleInstance<ISharingRepository>(sharingRepo);
+ RegisterSingleInstance<ISharingManager>(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this));
+
+ var activityLogRepo = GetActivityLogRepository();
+ RegisterSingleInstance(activityLogRepo);
+ RegisterSingleInstance<IActivityManager>(new ActivityManager(LogManager.GetLogger("ActivityManager"), activityLogRepo, UserManager));
+
+ var authContext = new AuthorizationContext(AuthenticationRepository, ConnectManager);
+ RegisterSingleInstance<IAuthorizationContext>(authContext);
+ RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
+
+ AuthService = new AuthService(UserManager, authContext, ServerConfigurationManager, ConnectManager, SessionManager, DeviceManager);
+ RegisterSingleInstance<IAuthService>(AuthService);
+
+ SubtitleEncoder = new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, MemoryStreamFactory, ProcessFactory, textEncoding);
+ RegisterSingleInstance(SubtitleEncoder);
+
+ displayPreferencesRepo.Initialize();
+
+ var userDataRepo = new SqliteUserDataRepository(LogManager.GetLogger("SqliteUserDataRepository"), ApplicationPaths, FileSystemManager);
+
+ ((UserDataManager)UserDataManager).Repository = userDataRepo;
+ itemRepo.Initialize(userDataRepo);
+ ((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
+ ConfigureNotificationsRepository();
+ progress.Report(100);
+
+ SetStaticProperties();
+
+ await ((UserManager)UserManager).Initialize().ConfigureAwait(false);
+ }
+
+ protected virtual bool SupportsDualModeSockets
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ private ICertificate GetCertificate(CertificateInfo info)
+ {
+ var certificateLocation = info == null ? null : info.Path;
+
+ if (string.IsNullOrWhiteSpace(certificateLocation))
+ {
+ return null;
+ }
+
+ try
+ {
+ if (!FileSystemManager.FileExists(certificateLocation))
+ {
+ return null;
+ }
+
+ // Don't use an empty string password
+ var password = string.IsNullOrWhiteSpace(info.Password) ? null : info.Password;
+
+ X509Certificate2 localCert = new X509Certificate2(certificateLocation, password);
+ //localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA;
+ if (!localCert.HasPrivateKey)
+ {
+ //throw new FileNotFoundException("Secure requested, no private key included", certificateLocation);
+ return null;
+ }
+
+ return new Certificate(localCert);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error loading cert from {0}", ex, certificateLocation);
+ return null;
+ }
+ }
+
+ private IImageProcessor GetImageProcessor()
+ {
+ return new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, ImageEncoder, () => LibraryManager, TimerFactory);
+ }
+
+ protected virtual FFMpegInstallInfo GetFfmpegInstallInfo()
+ {
+ var info = new FFMpegInstallInfo();
+
+ // Windows builds: http://ffmpeg.zeranoe.com/builds/
+ // Linux builds: http://johnvansickle.com/ffmpeg/
+ // OS X builds: http://ffmpegmac.net/
+ // OS X x64: http://www.evermeet.cx/ffmpeg/
+
+ if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Linux)
+ {
+ info.FFMpegFilename = "ffmpeg";
+ info.FFProbeFilename = "ffprobe";
+ info.ArchiveType = "7z";
+ info.Version = "20170308";
+ info.DownloadUrls = GetLinuxDownloadUrls();
+ }
+ else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows)
+ {
+ info.FFMpegFilename = "ffmpeg.exe";
+ info.FFProbeFilename = "ffprobe.exe";
+ info.Version = "20170308";
+ info.ArchiveType = "7z";
+ info.DownloadUrls = GetWindowsDownloadUrls();
+ }
+ else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX)
+ {
+ info.FFMpegFilename = "ffmpeg";
+ info.FFProbeFilename = "ffprobe";
+ info.ArchiveType = "7z";
+ info.Version = "20170308";
+ info.DownloadUrls = GetMacDownloadUrls();
+ }
+ else
+ {
+ // No version available - user requirement
+ info.DownloadUrls = new string[] { };
+ }
+
+ return info;
+ }
+
+ private string[] GetMacDownloadUrls()
+ {
+ switch (EnvironmentInfo.SystemArchitecture)
+ {
+ case Architecture.X64:
+ return new[]
+ {
+ "https://embydata.com/downloads/ffmpeg/osx/ffmpeg-x64-20170308.7z"
+ };
+ }
+
+ return new string[] { };
+ }
+
+ private string[] GetWindowsDownloadUrls()
+ {
+ switch (EnvironmentInfo.SystemArchitecture)
+ {
+ case Architecture.X64:
+ return new[]
+ {
+ "https://embydata.com/downloads/ffmpeg/windows/ffmpeg-20170308-win64.7z"
+ };
+ case Architecture.X86:
+ return new[]
+ {
+ "https://embydata.com/downloads/ffmpeg/windows/ffmpeg-20170308-win32.7z"
+ };
+ }
+
+ return new string[] { };
+ }
+
+ private string[] GetLinuxDownloadUrls()
+ {
+ switch (EnvironmentInfo.SystemArchitecture)
+ {
+ case Architecture.X64:
+ return new[]
+ {
+ "https://embydata.com/downloads/ffmpeg/linux/ffmpeg-git-20170301-64bit-static.7z"
+ };
+ case Architecture.X86:
+ return new[]
+ {
+ "https://embydata.com/downloads/ffmpeg/linux/ffmpeg-git-20170301-32bit-static.7z"
+ };
+ }
+
+ return new string[] { };
+ }
+
+ /// <summary>
+ /// Registers the media encoder.
+ /// </summary>
+ /// <returns>Task.</returns>
+ private async Task RegisterMediaEncoder(IProgress<double> progress)
+ {
+ string encoderPath = null;
+ string probePath = null;
+
+ var info = await new FFMpegLoader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager, GetFfmpegInstallInfo())
+ .GetFFMpegInfo(StartupOptions, progress).ConfigureAwait(false);
+
+ encoderPath = info.EncoderPath;
+ probePath = info.ProbePath;
+ var hasExternalEncoder = string.Equals(info.Version, "external", StringComparison.OrdinalIgnoreCase);
+
+ var mediaEncoder = new MediaEncoding.Encoder.MediaEncoder(LogManager.GetLogger("MediaEncoder"),
+ JsonSerializer,
+ encoderPath,
+ probePath,
+ hasExternalEncoder,
+ ServerConfigurationManager,
+ FileSystemManager,
+ LiveTvManager,
+ IsoManager,
+ LibraryManager,
+ ChannelManager,
+ SessionManager,
+ () => SubtitleEncoder,
+ () => MediaSourceManager,
+ HttpClient,
+ ZipClient,
+ MemoryStreamFactory,
+ ProcessFactory,
+ (Environment.ProcessorCount > 2 ? 14000 : 40000),
+ EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows,
+ EnvironmentInfo);
+
+ MediaEncoder = mediaEncoder;
+ RegisterSingleInstance(MediaEncoder);
+ }
+
+ /// <summary>
+ /// Gets the user repository.
+ /// </summary>
+ /// <returns>Task{IUserRepository}.</returns>
+ private IUserRepository GetUserRepository()
+ {
+ var repo = new SqliteUserRepository(LogManager.GetLogger("SqliteUserRepository"), ApplicationPaths, JsonSerializer, MemoryStreamFactory);
+
+ repo.Initialize();
+
+ return repo;
+ }
+
+ private IAuthenticationRepository GetAuthenticationRepository()
+ {
+ var repo = new AuthenticationRepository(LogManager.GetLogger("AuthenticationRepository"), ServerConfigurationManager.ApplicationPaths);
+
+ repo.Initialize();
+
+ return repo;
+ }
+
+ private IActivityRepository GetActivityLogRepository()
+ {
+ var repo = new ActivityRepository(LogManager.GetLogger("ActivityRepository"), ServerConfigurationManager.ApplicationPaths);
+
+ repo.Initialize();
+
+ return repo;
+ }
+
+ /// <summary>
+ /// Configures the repositories.
+ /// </summary>
+ private void ConfigureNotificationsRepository()
+ {
+ var repo = new SqliteNotificationsRepository(LogManager.GetLogger("SqliteNotificationsRepository"), ServerConfigurationManager.ApplicationPaths, FileSystemManager);
+
+ repo.Initialize();
+
+ NotificationsRepository = repo;
+
+ RegisterSingleInstance(NotificationsRepository);
+ }
+
+ /// <summary>
+ /// Dirty hacks
+ /// </summary>
+ private void SetStaticProperties()
+ {
+ // For now there's no real way to inject these properly
+ BaseItem.Logger = LogManager.GetLogger("BaseItem");
+ BaseItem.ConfigurationManager = ServerConfigurationManager;
+ BaseItem.LibraryManager = LibraryManager;
+ BaseItem.ProviderManager = ProviderManager;
+ BaseItem.LocalizationManager = LocalizationManager;
+ BaseItem.ItemRepository = ItemRepository;
+ User.XmlSerializer = XmlSerializer;
+ User.UserManager = UserManager;
+ Folder.UserManager = UserManager;
+ BaseItem.FileSystem = FileSystemManager;
+ BaseItem.UserDataManager = UserDataManager;
+ BaseItem.ChannelManager = ChannelManager;
+ BaseItem.LiveTvManager = LiveTvManager;
+ Folder.UserViewManager = UserViewManager;
+ UserView.TVSeriesManager = TVSeriesManager;
+ UserView.PlaylistManager = PlaylistManager;
+ BaseItem.CollectionManager = CollectionManager;
+ BaseItem.MediaSourceManager = MediaSourceManager;
+ CollectionFolder.XmlSerializer = XmlSerializer;
+ Utilities.CryptographyProvider = CryptographyProvider;
+ AuthenticatedAttribute.AuthService = AuthService;
+ }
+
+ /// <summary>
+ /// Finds the parts.
+ /// </summary>
+ protected override void FindParts()
+ {
+ if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
+ {
+ RegisterServerWithAdministratorAccess();
+ ServerConfigurationManager.Configuration.IsPortAuthorized = true;
+ ConfigurationManager.SaveConfiguration();
+ }
+
+ RegisterModules();
+
+ base.FindParts();
+
+ HttpServer.Init(GetExports<IService>(false));
+
+ ServerManager.AddWebSocketListeners(GetExports<IWebSocketListener>(false));
+
+ StartServer();
+
+ LibraryManager.AddParts(GetExports<IResolverIgnoreRule>(),
+ GetExports<IVirtualFolderCreator>(),
+ GetExports<IItemResolver>(),
+ GetExports<IIntroProvider>(),
+ GetExports<IBaseItemComparer>(),
+ GetExports<ILibraryPostScanTask>());
+
+ ProviderManager.AddParts(GetExports<IImageProvider>(),
+ GetExports<IMetadataService>(),
+ GetExports<IMetadataProvider>(),
+ GetExports<IMetadataSaver>(),
+ GetExports<IExternalId>());
+
+ ImageProcessor.AddParts(GetExports<IImageEnhancer>());
+
+ LiveTvManager.AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
+
+ SubtitleManager.AddParts(GetExports<ISubtitleProvider>());
+
+ SessionManager.AddParts(GetExports<ISessionControllerFactory>());
+
+ ChannelManager.AddParts(GetExports<IChannel>());
+
+ MediaSourceManager.AddParts(GetExports<IMediaSourceProvider>());
+
+ NotificationManager.AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
+ SyncManager.AddParts(GetExports<ISyncProvider>());
+ }
+
+ private CertificateInfo CertificateInfo { get; set; }
+ private ICertificate Certificate { get; set; }
+
+ private IEnumerable<string> GetUrlPrefixes()
+ {
+ var hosts = new List<string>();
+
+ hosts.Add("+");
+
+ return hosts.SelectMany(i =>
+ {
+ var prefixes = new List<string>
+ {
+ "http://"+i+":" + HttpPort + "/"
+ };
+
+ if (CertificateInfo != null)
+ {
+ prefixes.Add("https://" + i + ":" + HttpsPort + "/");
+ }
+
+ return prefixes;
+ });
+ }
+
+ /// <summary>
+ /// Starts the server.
+ /// </summary>
+ private void StartServer()
+ {
+ try
+ {
+ ServerManager.Start(GetUrlPrefixes());
+ return;
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error starting http server", ex);
+
+ if (HttpPort == ServerConfiguration.DefaultHttpPort)
+ {
+ throw;
+ }
+ }
+
+ HttpPort = ServerConfiguration.DefaultHttpPort;
+
+ try
+ {
+ ServerManager.Start(GetUrlPrefixes());
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error starting http server", ex);
+
+ throw;
+ }
+ }
+
+ private CertificateInfo GetCertificateInfo(bool generateCertificate)
+ {
+ if (!string.IsNullOrWhiteSpace(ServerConfigurationManager.Configuration.CertificatePath))
+ {
+ // Custom cert
+ return new CertificateInfo
+ {
+ Path = ServerConfigurationManager.Configuration.CertificatePath,
+ Password = ServerConfigurationManager.Configuration.CertificatePassword
+ };
+ }
+
+ // Generate self-signed cert
+ var certHost = GetHostnameFromExternalDns(ServerConfigurationManager.Configuration.WanDdns);
+ var certPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.ProgramDataPath, "ssl", "cert_" + (certHost + "2").GetMD5().ToString("N") + ".pfx");
+ var password = "embycert";
+
+ if (generateCertificate)
+ {
+ if (!FileSystemManager.FileExists(certPath))
+ {
+ FileSystemManager.CreateDirectory(FileSystemManager.GetDirectoryName(certPath));
+
+ try
+ {
+ _certificateGenerator(certPath, certHost, password);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error creating ssl cert", ex);
+ return null;
+ }
+ }
+ }
+
+ return new CertificateInfo
+ {
+ Path = certPath,
+ Password = password
+ };
+ }
+
+ /// <summary>
+ /// Called when [configuration updated].
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
+ protected override void OnConfigurationUpdated(object sender, EventArgs e)
+ {
+ base.OnConfigurationUpdated(sender, e);
+
+ var requiresRestart = false;
+
+ // Don't do anything if these haven't been set yet
+ if (HttpPort != 0 && HttpsPort != 0)
+ {
+ // Need to restart if ports have changed
+ if (ServerConfigurationManager.Configuration.HttpServerPortNumber != HttpPort ||
+ ServerConfigurationManager.Configuration.HttpsPortNumber != HttpsPort)
+ {
+ if (ServerConfigurationManager.Configuration.IsPortAuthorized)
+ {
+ ServerConfigurationManager.Configuration.IsPortAuthorized = false;
+ ServerConfigurationManager.SaveConfiguration();
+
+ requiresRestart = true;
+ }
+ }
+ }
+
+ if (!HttpServer.UrlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase))
+ {
+ requiresRestart = true;
+ }
+
+ var currentCertPath = CertificateInfo == null ? null : CertificateInfo.Path;
+ var newCertInfo = GetCertificateInfo(false);
+ var newCertPath = newCertInfo == null ? null : newCertInfo.Path;
+
+ if (!string.Equals(currentCertPath, newCertPath, StringComparison.OrdinalIgnoreCase))
+ {
+ requiresRestart = true;
+ }
+
+ if (requiresRestart)
+ {
+ Logger.Info("App needs to be restarted due to configuration change.");
+
+ NotifyPendingRestart();
+ }
+ }
+
+ /// <summary>
+ /// Restarts this instance.
+ /// </summary>
+ public override async Task Restart()
+ {
+ if (!CanSelfRestart)
+ {
+ throw new PlatformNotSupportedException("The server is unable to self-restart. Please restart manually.");
+ }
+
+ try
+ {
+ await SessionManager.SendServerRestartNotification(CancellationToken.None).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error sending server restart notification", ex);
+ }
+
+ Logger.Info("Calling RestartInternal");
+
+ RestartInternal();
+ }
+
+ protected abstract void RestartInternal();
+
+ /// <summary>
+ /// Gets the composable part assemblies.
+ /// </summary>
+ /// <returns>IEnumerable{Assembly}.</returns>
+ protected override IEnumerable<Assembly> GetComposablePartAssemblies()
+ {
+ var list = GetPluginAssemblies()
+ .ToList();
+
+ // Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that
+ // This will prevent the .dll file from getting locked, and allow us to replace it when needed
+
+ // Include composable parts in the Api assembly
+ list.Add(GetAssembly(typeof(ApiEntryPoint)));
+
+ // Include composable parts in the Dashboard assembly
+ list.Add(GetAssembly(typeof(DashboardService)));
+
+ // Include composable parts in the Model assembly
+ list.Add(GetAssembly(typeof(SystemInfo)));
+
+ // Include composable parts in the Common assembly
+ list.Add(GetAssembly(typeof(IApplicationHost)));
+
+ // Include composable parts in the Controller assembly
+ list.Add(GetAssembly(typeof(IServerApplicationHost)));
+
+ // Include composable parts in the Providers assembly
+ list.Add(GetAssembly(typeof(ProviderUtils)));
+
+ // Include composable parts in the Photos assembly
+ list.Add(GetAssembly(typeof(PhotoProvider)));
+
+ // Common implementations
+ list.Add(GetAssembly(typeof(TaskManager)));
+
+ // Emby.Server implementations
+ list.Add(GetAssembly(typeof(InstallationManager)));
+
+ // MediaEncoding
+ list.Add(GetAssembly(typeof(MediaEncoding.Encoder.MediaEncoder)));
+
+ // Dlna
+ list.Add(GetAssembly(typeof(DlnaEntryPoint)));
+
+ // Local metadata
+ list.Add(GetAssembly(typeof(BoxSetXmlSaver)));
+
+ // Xbmc
+ list.Add(GetAssembly(typeof(ArtistNfoProvider)));
+
+ list.AddRange(GetAssembliesWithPartsInternal());
+
+ return list.ToList();
+ }
+
+ protected abstract List<Assembly> GetAssembliesWithPartsInternal();
+
+ /// <summary>
+ /// Gets the plugin assemblies.
+ /// </summary>
+ /// <returns>IEnumerable{Assembly}.</returns>
+ private IEnumerable<Assembly> GetPluginAssemblies()
+ {
+ try
+ {
+ return Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly)
+ .Where(EnablePlugin)
+ .Select(LoadAssembly)
+ .Where(a => a != null)
+ .ToList();
+ }
+ catch (DirectoryNotFoundException)
+ {
+ return new List<Assembly>();
+ }
+ }
+
+ private bool EnablePlugin(string path)
+ {
+ var filename = Path.GetFileName(path);
+
+ var exclude = new[]
+ {
+ "mbplus.dll",
+ "mbintros.dll",
+ "embytv.dll"
+ };
+
+ return !exclude.Contains(filename ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+ }
+
+ /// <summary>
+ /// Gets the system status.
+ /// </summary>
+ /// <returns>SystemInfo.</returns>
+ public async Task<SystemInfo> GetSystemInfo()
+ {
+ var localAddress = await GetLocalApiUrl().ConfigureAwait(false);
+
+ return new SystemInfo
+ {
+ HasPendingRestart = HasPendingRestart,
+ Version = ApplicationVersion.ToString(),
+ WebSocketPortNumber = HttpPort,
+ FailedPluginAssemblies = FailedAssemblies.ToList(),
+ InProgressInstallations = InstallationManager.CurrentInstallations.Select(i => i.Item1).ToList(),
+ CompletedInstallations = InstallationManager.CompletedInstallations.ToList(),
+ Id = SystemId,
+ ProgramDataPath = ApplicationPaths.ProgramDataPath,
+ LogPath = ApplicationPaths.LogDirectoryPath,
+ ItemsByNamePath = ApplicationPaths.ItemsByNamePath,
+ InternalMetadataPath = ApplicationPaths.InternalMetadataPath,
+ CachePath = ApplicationPaths.CachePath,
+ MacAddress = GetMacAddress(),
+ HttpServerPortNumber = HttpPort,
+ SupportsHttps = SupportsHttps,
+ HttpsPortNumber = HttpsPort,
+ OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(),
+ OperatingSystemDisplayName = OperatingSystemDisplayName,
+ CanSelfRestart = CanSelfRestart,
+ CanSelfUpdate = CanSelfUpdate,
+ WanAddress = ConnectManager.WanApiAddress,
+ HasUpdateAvailable = HasUpdateAvailable,
+ SupportsAutoRunAtStartup = SupportsAutoRunAtStartup,
+ TranscodingTempPath = ApplicationPaths.TranscodingTempPath,
+ SupportsRunningAsService = SupportsRunningAsService,
+ ServerName = FriendlyName,
+ LocalAddress = localAddress,
+ SupportsLibraryMonitor = true,
+ EncoderLocationType = MediaEncoder.EncoderLocationType,
+ SystemArchitecture = EnvironmentInfo.SystemArchitecture,
+ SystemUpdateLevel = SystemUpdateLevel,
+ PackageName = StartupOptions.GetOption("-package")
+ };
+ }
+
+ public bool EnableHttps
+ {
+ get
+ {
+ return SupportsHttps && ServerConfigurationManager.Configuration.EnableHttps;
+ }
+ }
+
+ public bool SupportsHttps
+ {
+ get { return Certificate != null; }
+ }
+
+ public async Task<string> GetLocalApiUrl()
+ {
+ try
+ {
+ // Return the first matched address, if found, or the first known local address
+ var address = (await GetLocalIpAddresses().ConfigureAwait(false)).FirstOrDefault(i => !i.Equals(IpAddressInfo.Loopback) && !i.Equals(IpAddressInfo.IPv6Loopback));
+
+ if (address != null)
+ {
+ return GetLocalApiUrl(address);
+ }
+
+ return null;
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error getting local Ip address information", ex);
+ }
+
+ return null;
+ }
+
+ public string GetLocalApiUrl(IpAddressInfo ipAddress)
+ {
+ if (ipAddress.AddressFamily == IpAddressFamily.InterNetworkV6)
+ {
+ return GetLocalApiUrl("[" + ipAddress.Address + "]");
+ }
+
+ return GetLocalApiUrl(ipAddress.Address);
+ }
+
+ public string GetLocalApiUrl(string host)
+ {
+ return string.Format("http://{0}:{1}",
+ host,
+ HttpPort.ToString(CultureInfo.InvariantCulture));
+ }
+
+ public async Task<List<IpAddressInfo>> GetLocalIpAddresses()
+ {
+ var addresses = ServerConfigurationManager
+ .Configuration
+ .LocalNetworkAddresses
+ .Select(NormalizeConfiguredLocalAddress)
+ .Where(i => i != null)
+ .ToList();
+
+ if (addresses.Count == 0)
+ {
+ addresses.AddRange(NetworkManager.GetLocalIpAddresses());
+
+ var list = new List<IpAddressInfo>();
+
+ foreach (var address in addresses)
+ {
+ var valid = await IsIpAddressValidAsync(address).ConfigureAwait(false);
+ if (valid)
+ {
+ list.Add(address);
+ }
+ }
+
+ addresses = list;
+ }
+
+ return addresses;
+ }
+
+ private IpAddressInfo NormalizeConfiguredLocalAddress(string address)
+ {
+ var index = address.Trim('/').IndexOf('/');
+
+ if (index != -1)
+ {
+ address = address.Substring(index + 1);
+ }
+
+ IpAddressInfo result;
+ if (NetworkManager.TryParseIpAddress(address.Trim('/'), out result))
+ {
+ return result;
+ }
+ return null;
+ }
+
+ private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
+ private DateTime _lastAddressCacheClear;
+ private async Task<bool> IsIpAddressValidAsync(IpAddressInfo address)
+ {
+ if (address.Equals(IpAddressInfo.Loopback) ||
+ address.Equals(IpAddressInfo.IPv6Loopback))
+ {
+ return true;
+ }
+
+ var apiUrl = GetLocalApiUrl(address);
+ apiUrl += "/system/ping";
+
+ if ((DateTime.UtcNow - _lastAddressCacheClear).TotalMinutes >= 15)
+ {
+ _lastAddressCacheClear = DateTime.UtcNow;
+ _validAddressResults.Clear();
+ }
+
+ bool cachedResult;
+ if (_validAddressResults.TryGetValue(apiUrl, out cachedResult))
+ {
+ return cachedResult;
+ }
+
+ try
+ {
+ using (var response = await HttpClient.SendAsync(new HttpRequestOptions
+ {
+ Url = apiUrl,
+ LogErrorResponseBody = false,
+ LogErrors = false,
+ LogRequest = false,
+ TimeoutMs = 30000,
+ BufferContent = false
+
+ }, "POST").ConfigureAwait(false))
+ {
+ using (var reader = new StreamReader(response.Content))
+ {
+ var result = reader.ReadToEnd();
+ var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase);
+
+ _validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid);
+ //Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, valid);
+ return valid;
+ }
+ }
+ }
+ catch
+ {
+ //Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, false);
+
+ _validAddressResults.AddOrUpdate(apiUrl, false, (k, v) => false);
+ return false;
+ }
+ }
+
+ public string FriendlyName
+ {
+ get
+ {
+ return string.IsNullOrWhiteSpace(ServerConfigurationManager.Configuration.ServerName)
+ ? Environment.MachineName
+ : ServerConfigurationManager.Configuration.ServerName;
+ }
+ }
+
+ public int HttpPort { get; private set; }
+
+ public int HttpsPort { get; private set; }
+
+ /// <summary>
+ /// Gets the mac address.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ private string GetMacAddress()
+ {
+ try
+ {
+ return NetworkManager.GetMacAddress();
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error getting mac address", ex);
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Shuts down.
+ /// </summary>
+ public override async Task Shutdown()
+ {
+ try
+ {
+ await SessionManager.SendServerShutdownNotification(CancellationToken.None).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error sending server shutdown notification", ex);
+ }
+
+ ShutdownInternal();
+ }
+
+ protected abstract void ShutdownInternal();
+
+ /// <summary>
+ /// Registers the server with administrator access.
+ /// </summary>
+ private void RegisterServerWithAdministratorAccess()
+ {
+ Logger.Info("Requesting administrative access to authorize http server");
+
+ try
+ {
+ AuthorizeServer();
+ }
+ catch (NotImplementedException)
+ {
+
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error authorizing server", ex);
+ }
+ }
+
+ protected virtual void AuthorizeServer()
+ {
+ throw new NotImplementedException();
+ }
+
+ public event EventHandler HasUpdateAvailableChanged;
+
+ private bool _hasUpdateAvailable;
+ public bool HasUpdateAvailable
+ {
+ get { return _hasUpdateAvailable; }
+ set
+ {
+ var fireEvent = value && !_hasUpdateAvailable;
+
+ _hasUpdateAvailable = value;
+
+ if (fireEvent)
+ {
+ EventHelper.FireEventIfNotNull(HasUpdateAvailableChanged, this, EventArgs.Empty, Logger);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Checks for update.
+ /// </summary>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <param name="progress">The progress.</param>
+ /// <returns>Task{CheckForUpdateResult}.</returns>
+ public override async Task<CheckForUpdateResult> CheckForApplicationUpdate(CancellationToken cancellationToken, IProgress<double> progress)
+ {
+ var cacheLength = TimeSpan.FromHours(3);
+ var updateLevel = SystemUpdateLevel;
+
+ if (updateLevel == PackageVersionClass.Beta)
+ {
+ cacheLength = TimeSpan.FromHours(1);
+ }
+ else if (updateLevel == PackageVersionClass.Dev)
+ {
+ cacheLength = TimeSpan.FromMinutes(5);
+ }
+
+ var result = await new GithubUpdater(HttpClient, JsonSerializer).CheckForUpdateResult("MediaBrowser", "Emby", ApplicationVersion, updateLevel, _releaseAssetFilename,
+ "MBServer", "Mbserver.zip", cacheLength, cancellationToken).ConfigureAwait(false);
+
+ HasUpdateAvailable = result.IsUpdateAvailable;
+
+ return result;
+ }
+
+ /// <summary>
+ /// Updates the application.
+ /// </summary>
+ /// <param name="package">The package that contains the update</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <param name="progress">The progress.</param>
+ public override async Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress<double> progress)
+ {
+ await InstallationManager.InstallPackage(package, false, progress, cancellationToken).ConfigureAwait(false);
+
+ HasUpdateAvailable = false;
+
+ OnApplicationUpdated(package);
+ }
+
+ /// <summary>
+ /// Configures the automatic run at startup.
+ /// </summary>
+ /// <param name="autorun">if set to <c>true</c> [autorun].</param>
+ protected override void ConfigureAutoRunAtStartup(bool autorun)
+ {
+ if (SupportsAutoRunAtStartup)
+ {
+ ConfigureAutoRunInternal(autorun);
+ }
+ }
+
+ protected virtual void ConfigureAutoRunInternal(bool autorun)
+ {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// This returns localhost in the case of no external dns, and the hostname if the
+ /// dns is prefixed with a valid Uri prefix.
+ /// </summary>
+ /// <param name="externalDns">The external dns prefix to get the hostname of.</param>
+ /// <returns>The hostname in <paramref name="externalDns"/></returns>
+ private static string GetHostnameFromExternalDns(string externalDns)
+ {
+ if (string.IsNullOrWhiteSpace(externalDns))
+ {
+ return "localhost";
+ }
+
+ try
+ {
+ return new Uri(externalDns).Host;
+ }
+ catch
+ {
+ return externalDns;
+ }
+ }
+
+ public void LaunchUrl(string url)
+ {
+ if (EnvironmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows &&
+ EnvironmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.OSX)
+ {
+ throw new NotImplementedException();
+ }
+
+ var process = ProcessFactory.Create(new ProcessOptions
+ {
+ FileName = url,
+ //EnableRaisingEvents = true,
+ UseShellExecute = true,
+ ErrorDialog = false
+ });
+
+ process.Exited += ProcessExited;
+
+ try
+ {
+ process.Start();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Error launching url: {0}", url);
+ Logger.ErrorException("Error launching url: {0}", ex, url);
+
+ throw;
+ }
+ }
+
+ private static void ProcessExited(object sender, EventArgs e)
+ {
+ ((IProcess)sender).Dispose();
+ }
+
+ public virtual void EnableLoopback(string appName)
+ {
+ }
+
+ private void RegisterModules()
+ {
+ var moduleTypes = GetExportTypes<IDependencyModule>();
+
+ foreach (var type in moduleTypes)
+ {
+ try
+ {
+ var instance = Activator.CreateInstance(type) as IDependencyModule;
+ if (instance != null)
+ instance.BindDependencies(this);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error setting up dependency bindings for " + type.Name, ex);
+ }
+ }
+ }
+
+ void IDependencyContainer.RegisterSingleInstance<T>(T obj, bool manageLifetime)
+ {
+ RegisterSingleInstance(obj, manageLifetime);
+ }
+
+ void IDependencyContainer.RegisterSingleInstance<T>(Func<T> func)
+ {
+ RegisterSingleInstance(func);
+ }
+
+ void IDependencyContainer.Register(Type typeInterface, Type typeImplementation)
+ {
+ Container.Register(typeInterface, typeImplementation);
+ }
+ }
+
+ internal class CertificateInfo
+ {
+ public string Path { get; set; }
+ public string Password { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/ApplicationPathHelper.cs b/Emby.Server.Implementations/ApplicationPathHelper.cs
new file mode 100644
index 000000000..262cc526e
--- /dev/null
+++ b/Emby.Server.Implementations/ApplicationPathHelper.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Configuration;
+using System.IO;
+
+namespace Emby.Server.Implementations
+{
+ public static class ApplicationPathHelper
+ {
+ /// <summary>
+ /// Gets the path to the application's ProgramDataFolder
+ /// </summary>
+ /// <returns>System.String.</returns>
+ public static string GetProgramDataPath(string applicationPath)
+ {
+ var useDebugPath = false;
+
+#if DEBUG
+ useDebugPath = true;
+#endif
+
+ var programDataPath = useDebugPath ?
+ ConfigurationManager.AppSettings["DebugProgramDataPath"] :
+ ConfigurationManager.AppSettings["ReleaseProgramDataPath"];
+
+ programDataPath = programDataPath.Replace("%ApplicationData%", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData));
+
+ programDataPath = programDataPath
+ .Replace('/', Path.DirectorySeparatorChar)
+ .Replace('\\', Path.DirectorySeparatorChar);
+
+ // If it's a relative path, e.g. "..\"
+ if (!Path.IsPathRooted(programDataPath))
+ {
+ var path = Path.GetDirectoryName(applicationPath);
+
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ApplicationException("Unable to determine running assembly location");
+ }
+
+ programDataPath = Path.Combine(path, programDataPath);
+
+ programDataPath = Path.GetFullPath(programDataPath);
+ }
+
+ Directory.CreateDirectory(programDataPath);
+
+ return programDataPath;
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
index f892b1e62..0c363c585 100644
--- a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
+++ b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
@@ -18,12 +18,12 @@ namespace Emby.Server.Implementations.Channels
_channelManager = channelManager;
}
- public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
+ public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item)
{
return GetChannel(item).GetSupportedChannelImages();
}
- public Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken)
+ public Task<DynamicImageResponse> GetImage(IHasMetadata item, ImageType type, CancellationToken cancellationToken)
{
var channel = GetChannel(item);
@@ -35,12 +35,12 @@ namespace Emby.Server.Implementations.Channels
get { return "Channel Image Provider"; }
}
- public bool Supports(IHasImages item)
+ public bool Supports(IHasMetadata item)
{
return item is Channel;
}
- private IChannel GetChannel(IHasImages item)
+ private IChannel GetChannel(IHasMetadata item)
{
var channel = (Channel)item;
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 2adf6a37c..5e97cd5f5 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -159,7 +159,7 @@ namespace Emby.Server.Implementations.Channels
all = all.Take(query.Limit.Value).ToList();
}
- var returnItems = all.ToArray();
+ var returnItems = all.ToArray(all.Count);
var result = new QueryResult<Channel>
{
@@ -182,8 +182,10 @@ namespace Emby.Server.Implementations.Channels
{
};
- var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user).ConfigureAwait(false))
- .ToArray();
+ var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user)
+ .ConfigureAwait(false));
+ var returnItems = returnList
+ .ToArray(returnList.Count);
var result = new QueryResult<BaseItemDto>
{
@@ -567,8 +569,9 @@ namespace Emby.Server.Implementations.Channels
Fields = query.Fields.ToList()
};
- var returnItems = (await _dtoService.GetBaseItemDtos(items, dtoOptions, user).ConfigureAwait(false))
- .ToArray();
+ var returnList = (await _dtoService.GetBaseItemDtos(items, dtoOptions, user).ConfigureAwait(false));
+ var returnItems = returnList
+ .ToArray(returnList.Count);
var result = new QueryResult<BaseItemDto>
{
@@ -676,12 +679,10 @@ namespace Emby.Server.Implementations.Channels
internalItems = internalItems.Take(query.Limit.Value).ToArray();
}
- var returnItemArray = internalItems.ToArray();
-
return new QueryResult<BaseItem>
{
TotalRecordCount = totalCount,
- Items = returnItemArray
+ Items = internalItems
};
}
@@ -813,12 +814,10 @@ namespace Emby.Server.Implementations.Channels
var internalItems = await Task.WhenAll(itemTasks).ConfigureAwait(false);
- var returnItemArray = internalItems.ToArray();
-
return new QueryResult<BaseItem>
{
TotalRecordCount = totalCount,
- Items = returnItemArray
+ Items = internalItems
};
}
@@ -837,8 +836,10 @@ namespace Emby.Server.Implementations.Channels
Fields = query.Fields.ToList()
};
- var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user).ConfigureAwait(false))
- .ToArray();
+ var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user)
+ .ConfigureAwait(false));
+ var returnItems = returnList
+ .ToArray(returnList.Count);
var result = new QueryResult<BaseItemDto>
{
@@ -989,8 +990,10 @@ namespace Emby.Server.Implementations.Channels
Fields = query.Fields.ToList()
};
- var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user).ConfigureAwait(false))
- .ToArray();
+ var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user)
+ .ConfigureAwait(false));
+ var returnItems = returnList
+ .ToArray(returnList.Count);
var result = new QueryResult<BaseItemDto>
{
@@ -1191,7 +1194,7 @@ namespace Emby.Server.Implementations.Channels
}
}
- var returnItemArray = all.ToArray();
+ var returnItemArray = all.ToArray(all.Count);
RefreshIfNeeded(returnItemArray);
return new QueryResult<BaseItem>
@@ -1309,7 +1312,7 @@ namespace Emby.Server.Implementations.Channels
{
item.Name = info.Name;
item.Genres = info.Genres;
- item.Studios = info.Studios;
+ item.Studios = info.Studios.ToArray(info.Studios.Count);
item.CommunityRating = info.CommunityRating;
item.Overview = info.Overview;
item.IndexNumber = info.IndexNumber;
@@ -1319,7 +1322,7 @@ namespace Emby.Server.Implementations.Channels
item.ProviderIds = info.ProviderIds;
item.OfficialRating = info.OfficialRating;
item.DateCreated = info.DateCreated ?? DateTime.UtcNow;
- item.Tags = info.Tags;
+ item.Tags = info.Tags.ToArray(info.Tags.Count);
item.HomePageUrl = info.HomePageUrl;
}
else if (info.Type == ChannelItemType.Folder && info.FolderType == ChannelFolderType.Container)
diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
index 463d276e5..c7378956d 100644
--- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
+++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
@@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Collections
{
}
- protected override bool Supports(IHasImages item)
+ protected override bool Supports(IHasMetadata item)
{
// Right now this is the only way to prevent this image from getting created ahead of internet image providers
if (!item.IsLocked)
@@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Collections
return base.Supports(item);
}
- protected override List<BaseItem> GetItemsWithImages(IHasImages item)
+ protected override List<BaseItem> GetItemsWithImages(IHasMetadata item)
{
var playlist = (BoxSet)item;
@@ -76,7 +76,7 @@ namespace Emby.Server.Implementations.Collections
return GetFinalItems(items, 2);
}
- protected override string CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+ protected override string CreateImage(IHasMetadata item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
{
return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
}
diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
index 2241e9377..4d9bf0624 100644
--- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
+++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
@@ -16,6 +16,7 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.Configuration
{
@@ -216,7 +217,7 @@ namespace Emby.Server.Implementations.Configuration
list.Add(service);
- options.DisabledMetadataSavers = list.ToArray();
+ options.DisabledMetadataSavers = list.ToArray(list.Count);
}
}
@@ -236,7 +237,7 @@ namespace Emby.Server.Implementations.Configuration
list.Add(options);
- config.MetadataOptions = list.ToArray();
+ config.MetadataOptions = list.ToArray(list.Count);
}
return options;
diff --git a/Emby.Server.Implementations/Cryptography/ASN1.cs b/Emby.Server.Implementations/Cryptography/ASN1.cs
new file mode 100644
index 000000000..f5c826436
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/ASN1.cs
@@ -0,0 +1,340 @@
+//
+// ASN1.cs: Abstract Syntax Notation 1 - micro-parser and generator
+//
+// Authors:
+// Sebastien Pouliot <sebastien@ximian.com>
+// Jesper Pedersen <jep@itplus.dk>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+// (C) 2004 IT+ A/S (http://www.itplus.dk)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.IO;
+using System.Text;
+
+namespace Emby.Server.Core.Cryptography
+{
+
+ // References:
+ // a. ITU ASN.1 standards (free download)
+ // http://www.itu.int/ITU-T/studygroups/com17/languages/
+
+ public class ASN1 {
+
+ private byte m_nTag;
+ private byte[] m_aValue;
+ private ArrayList elist;
+
+ public ASN1 () : this (0x00, null) {}
+
+ public ASN1 (byte tag) : this (tag, null) {}
+
+ public ASN1 (byte tag, byte[] data)
+ {
+ m_nTag = tag;
+ m_aValue = data;
+ }
+
+ public ASN1 (byte[] data)
+ {
+ m_nTag = data [0];
+
+ int nLenLength = 0;
+ int nLength = data [1];
+
+ if (nLength > 0x80) {
+ // composed length
+ nLenLength = nLength - 0x80;
+ nLength = 0;
+ for (int i = 0; i < nLenLength; i++) {
+ nLength *= 256;
+ nLength += data [i + 2];
+ }
+ }
+ else if (nLength == 0x80) {
+ // undefined length encoding
+ throw new NotSupportedException ("Undefined length encoding.");
+ }
+
+ m_aValue = new byte [nLength];
+ Buffer.BlockCopy (data, (2 + nLenLength), m_aValue, 0, nLength);
+
+ if ((m_nTag & 0x20) == 0x20) {
+ int nStart = (2 + nLenLength);
+ Decode (data, ref nStart, data.Length);
+ }
+ }
+
+ public int Count {
+ get {
+ if (elist == null)
+ return 0;
+ return elist.Count;
+ }
+ }
+
+ public byte Tag {
+ get { return m_nTag; }
+ }
+
+ public int Length {
+ get {
+ if (m_aValue != null)
+ return m_aValue.Length;
+ else
+ return 0;
+ }
+ }
+
+ public byte[] Value {
+ get {
+ if (m_aValue == null)
+ GetBytes ();
+ return (byte[]) m_aValue.Clone ();
+ }
+ set {
+ if (value != null)
+ m_aValue = (byte[]) value.Clone ();
+ }
+ }
+
+ private bool CompareArray (byte[] array1, byte[] array2)
+ {
+ bool bResult = (array1.Length == array2.Length);
+ if (bResult) {
+ for (int i = 0; i < array1.Length; i++) {
+ if (array1[i] != array2[i])
+ return false;
+ }
+ }
+ return bResult;
+ }
+
+ public bool Equals (byte[] asn1)
+ {
+ return CompareArray (this.GetBytes (), asn1);
+ }
+
+ public bool CompareValue (byte[] value)
+ {
+ return CompareArray (m_aValue, value);
+ }
+
+ public ASN1 Add (ASN1 asn1)
+ {
+ if (asn1 != null) {
+ if (elist == null)
+ elist = new ArrayList ();
+ elist.Add (asn1);
+ }
+ return asn1;
+ }
+
+ public virtual byte[] GetBytes ()
+ {
+ byte[] val = null;
+
+ if (Count > 0) {
+ int esize = 0;
+ ArrayList al = new ArrayList ();
+ foreach (ASN1 a in elist) {
+ byte[] item = a.GetBytes ();
+ al.Add (item);
+ esize += item.Length;
+ }
+ val = new byte [esize];
+ int pos = 0;
+ for (int i=0; i < elist.Count; i++) {
+ byte[] item = (byte[]) al[i];
+ Buffer.BlockCopy (item, 0, val, pos, item.Length);
+ pos += item.Length;
+ }
+ } else if (m_aValue != null) {
+ val = m_aValue;
+ }
+
+ byte[] der;
+ int nLengthLen = 0;
+
+ if (val != null) {
+ int nLength = val.Length;
+ // special for length > 127
+ if (nLength > 127) {
+ if (nLength <= Byte.MaxValue) {
+ der = new byte [3 + nLength];
+ Buffer.BlockCopy (val, 0, der, 3, nLength);
+ nLengthLen = 0x81;
+ der[2] = (byte)(nLength);
+ }
+ else if (nLength <= UInt16.MaxValue) {
+ der = new byte [4 + nLength];
+ Buffer.BlockCopy (val, 0, der, 4, nLength);
+ nLengthLen = 0x82;
+ der[2] = (byte)(nLength >> 8);
+ der[3] = (byte)(nLength);
+ }
+ else if (nLength <= 0xFFFFFF) {
+ // 24 bits
+ der = new byte [5 + nLength];
+ Buffer.BlockCopy (val, 0, der, 5, nLength);
+ nLengthLen = 0x83;
+ der [2] = (byte)(nLength >> 16);
+ der [3] = (byte)(nLength >> 8);
+ der [4] = (byte)(nLength);
+ }
+ else {
+ // max (Length is an integer) 32 bits
+ der = new byte [6 + nLength];
+ Buffer.BlockCopy (val, 0, der, 6, nLength);
+ nLengthLen = 0x84;
+ der [2] = (byte)(nLength >> 24);
+ der [3] = (byte)(nLength >> 16);
+ der [4] = (byte)(nLength >> 8);
+ der [5] = (byte)(nLength);
+ }
+ }
+ else {
+ // basic case (no encoding)
+ der = new byte [2 + nLength];
+ Buffer.BlockCopy (val, 0, der, 2, nLength);
+ nLengthLen = nLength;
+ }
+ if (m_aValue == null)
+ m_aValue = val;
+ }
+ else
+ der = new byte[2];
+
+ der[0] = m_nTag;
+ der[1] = (byte)nLengthLen;
+
+ return der;
+ }
+
+ // Note: Recursive
+ protected void Decode (byte[] asn1, ref int anPos, int anLength)
+ {
+ byte nTag;
+ int nLength;
+ byte[] aValue;
+
+ // minimum is 2 bytes (tag + length of 0)
+ while (anPos < anLength - 1) {
+ DecodeTLV (asn1, ref anPos, out nTag, out nLength, out aValue);
+ // sometimes we get trailing 0
+ if (nTag == 0)
+ continue;
+
+ ASN1 elm = Add (new ASN1 (nTag, aValue));
+
+ if ((nTag & 0x20) == 0x20) {
+ int nConstructedPos = anPos;
+ elm.Decode (asn1, ref nConstructedPos, nConstructedPos + nLength);
+ }
+ anPos += nLength; // value length
+ }
+ }
+
+ // TLV : Tag - Length - Value
+ protected void DecodeTLV (byte[] asn1, ref int pos, out byte tag, out int length, out byte[] content)
+ {
+ tag = asn1 [pos++];
+ length = asn1 [pos++];
+
+ // special case where L contains the Length of the Length + 0x80
+ if ((length & 0x80) == 0x80) {
+ int nLengthLen = length & 0x7F;
+ length = 0;
+ for (int i = 0; i < nLengthLen; i++)
+ length = length * 256 + asn1 [pos++];
+ }
+
+ content = new byte [length];
+ Buffer.BlockCopy (asn1, pos, content, 0, length);
+ }
+
+ public ASN1 this [int index] {
+ get {
+ try {
+ if ((elist == null) || (index >= elist.Count))
+ return null;
+ return (ASN1) elist [index];
+ }
+ catch (ArgumentOutOfRangeException) {
+ return null;
+ }
+ }
+ }
+
+ public ASN1 Element (int index, byte anTag)
+ {
+ try {
+ if ((elist == null) || (index >= elist.Count))
+ return null;
+
+ ASN1 elm = (ASN1) elist [index];
+ if (elm.Tag == anTag)
+ return elm;
+ else
+ return null;
+ }
+ catch (ArgumentOutOfRangeException) {
+ return null;
+ }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder hexLine = new StringBuilder ();
+
+ // Add tag
+ hexLine.AppendFormat ("Tag: {0} {1}", m_nTag.ToString ("X2"), Environment.NewLine);
+
+ // Add length
+ hexLine.AppendFormat ("Length: {0} {1}", Value.Length, Environment.NewLine);
+
+ // Add value
+ hexLine.Append ("Value: ");
+ hexLine.Append (Environment.NewLine);
+ for (int i = 0; i < Value.Length; i++) {
+ hexLine.AppendFormat ("{0} ", Value [i].ToString ("X2"));
+ if ((i+1) % 16 == 0)
+ hexLine.AppendFormat (Environment.NewLine);
+ }
+ return hexLine.ToString ();
+ }
+
+ public void SaveToFile (string filename)
+ {
+ if (filename == null)
+ throw new ArgumentNullException ("filename");
+
+ using (FileStream fs = File.Create (filename)) {
+ byte[] data = GetBytes ();
+ fs.Write (data, 0, data.Length);
+ }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/ASN1Convert.cs b/Emby.Server.Implementations/Cryptography/ASN1Convert.cs
new file mode 100644
index 000000000..851d36dc7
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/ASN1Convert.cs
@@ -0,0 +1,207 @@
+//
+// ASN1Convert.cs: Abstract Syntax Notation 1 convertion routines
+//
+// Authors:
+// Sebastien Pouliot <sebastien@ximian.com>
+// Jesper Pedersen <jep@itplus.dk>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// (C) 2004 IT+ A/S (http://www.itplus.dk)
+// Copyright (C) 2004-2007 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Globalization;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Emby.Server.Core.Cryptography
+{
+
+ // References:
+ // a. ITU ASN.1 standards (free download)
+ // http://www.itu.int/ITU-T/studygroups/com17/languages/
+
+ public static class ASN1Convert {
+ // RFC3280, section 4.2.1.5
+ // CAs conforming to this profile MUST always encode certificate
+ // validity dates through the year 2049 as UTCTime; certificate validity
+ // dates in 2050 or later MUST be encoded as GeneralizedTime.
+
+ // Under 1.x this API requires a Local datetime to be provided
+ // Under 2.0 it will also accept a Utc datetime
+ static public ASN1 FromDateTime (DateTime dt)
+ {
+ if (dt.Year < 2050) {
+ // UTCTIME
+ return new ASN1 (0x17, Encoding.ASCII.GetBytes (
+ dt.ToUniversalTime ().ToString ("yyMMddHHmmss",
+ CultureInfo.InvariantCulture) + "Z"));
+ }
+ else {
+ // GENERALIZEDTIME
+ return new ASN1 (0x18, Encoding.ASCII.GetBytes (
+ dt.ToUniversalTime ().ToString ("yyyyMMddHHmmss",
+ CultureInfo.InvariantCulture) + "Z"));
+ }
+ }
+
+ static public ASN1 FromInt32 (Int32 value)
+ {
+ byte[] integer = BitConverterLE.GetBytes (value);
+ Array.Reverse (integer);
+ int x = 0;
+ while ((x < integer.Length) && (integer [x] == 0x00))
+ x++;
+ ASN1 asn1 = new ASN1 (0x02);
+ switch (x) {
+ case 0:
+ asn1.Value = integer;
+ break;
+ case 4:
+ asn1.Value = new byte [1];
+ break;
+ default:
+ byte[] smallerInt = new byte [4 - x];
+ Buffer.BlockCopy (integer, x, smallerInt, 0, smallerInt.Length);
+ asn1.Value = smallerInt;
+ break;
+ }
+ return asn1;
+ }
+
+ static public ASN1 FromOid (string oid)
+ {
+ if (oid == null)
+ throw new ArgumentNullException ("oid");
+
+ return new ASN1 (CryptoConfig.EncodeOID (oid));
+ }
+
+ static public ASN1 FromUnsignedBigInteger (byte[] big)
+ {
+ if (big == null)
+ throw new ArgumentNullException ("big");
+
+ // check for numbers that could be interpreted as negative (first bit)
+ if (big [0] >= 0x80) {
+ // in thie cas we add a new, empty, byte (position 0) so we're
+ // sure this will always be interpreted an unsigned integer.
+ // However we can't feed it into RSAParameters or DSAParameters
+ int length = big.Length + 1;
+ byte[] uinteger = new byte [length];
+ Buffer.BlockCopy (big, 0, uinteger, 1, length - 1);
+ big = uinteger;
+ }
+ return new ASN1 (0x02, big);
+ }
+
+ static public int ToInt32 (ASN1 asn1)
+ {
+ if (asn1 == null)
+ throw new ArgumentNullException ("asn1");
+ if (asn1.Tag != 0x02)
+ throw new FormatException ("Only integer can be converted");
+
+ int x = 0;
+ for (int i=0; i < asn1.Value.Length; i++)
+ x = (x << 8) + asn1.Value [i];
+ return x;
+ }
+
+ // Convert a binary encoded OID to human readable string representation of
+ // an OID (IETF style). Based on DUMPASN1.C from Peter Gutmann.
+ static public string ToOid (ASN1 asn1)
+ {
+ if (asn1 == null)
+ throw new ArgumentNullException ("asn1");
+
+ byte[] aOID = asn1.Value;
+ StringBuilder sb = new StringBuilder ();
+ // Pick apart the OID
+ byte x = (byte) (aOID[0] / 40);
+ byte y = (byte) (aOID[0] % 40);
+ if (x > 2) {
+ // Handle special case for large y if x = 2
+ y += (byte) ((x - 2) * 40);
+ x = 2;
+ }
+ sb.Append (x.ToString (CultureInfo.InvariantCulture));
+ sb.Append (".");
+ sb.Append (y.ToString (CultureInfo.InvariantCulture));
+ ulong val = 0;
+ for (x = 1; x < aOID.Length; x++) {
+ val = ((val << 7) | ((byte) (aOID [x] & 0x7F)));
+ if ( !((aOID [x] & 0x80) == 0x80)) {
+ sb.Append (".");
+ sb.Append (val.ToString (CultureInfo.InvariantCulture));
+ val = 0;
+ }
+ }
+ return sb.ToString ();
+ }
+
+ static public DateTime ToDateTime (ASN1 time)
+ {
+ if (time == null)
+ throw new ArgumentNullException ("time");
+
+ string t = Encoding.ASCII.GetString (time.Value);
+ // to support both UTCTime and GeneralizedTime (and not so common format)
+ string mask = null;
+ int year;
+ switch (t.Length) {
+ case 11:
+ // illegal format, still it's supported for compatibility
+ mask = "yyMMddHHmmZ";
+ break;
+ case 13:
+ // RFC3280: 4.1.2.5.1 UTCTime
+ year = Convert.ToInt16 (t.Substring (0, 2), CultureInfo.InvariantCulture);
+ // Where YY is greater than or equal to 50, the
+ // year SHALL be interpreted as 19YY; and
+ // Where YY is less than 50, the year SHALL be
+ // interpreted as 20YY.
+ if (year >= 50)
+ t = "19" + t;
+ else
+ t = "20" + t;
+ mask = "yyyyMMddHHmmssZ";
+ break;
+ case 15:
+ mask = "yyyyMMddHHmmssZ"; // GeneralizedTime
+ break;
+ case 17:
+ // another illegal format (990630000000+1000), again supported for compatibility
+ year = Convert.ToInt16 (t.Substring (0, 2), CultureInfo.InvariantCulture);
+ string century = (year >= 50) ? "19" : "20";
+ // ASN.1 (see ITU X.680 section 43.3) deals with offset differently than .NET
+ char sign = (t[12] == '+') ? '-' : '+';
+ t = String.Format ("{0}{1}{2}{3}{4}:{5}{6}", century, t.Substring (0, 12), sign,
+ t[13], t[14], t[15], t[16]);
+ mask = "yyyyMMddHHmmsszzz";
+ break;
+ }
+ return DateTime.ParseExact (t, mask, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/BitConverterLE.cs b/Emby.Server.Implementations/Cryptography/BitConverterLE.cs
new file mode 100644
index 000000000..34e6bf6dc
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/BitConverterLE.cs
@@ -0,0 +1,239 @@
+//
+// Mono.Security.BitConverterLE.cs
+// Like System.BitConverter but always little endian
+//
+// Author:
+// Bernie Solomon
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Emby.Server.Core.Cryptography
+{
+ internal sealed class BitConverterLE
+ {
+ private BitConverterLE ()
+ {
+ }
+
+ unsafe private static byte[] GetUShortBytes (byte *bytes)
+ {
+ if (BitConverter.IsLittleEndian)
+ return new byte [] { bytes [0], bytes [1] };
+ else
+ return new byte [] { bytes [1], bytes [0] };
+ }
+
+ unsafe private static byte[] GetUIntBytes (byte *bytes)
+ {
+ if (BitConverter.IsLittleEndian)
+ return new byte [] { bytes [0], bytes [1], bytes [2], bytes [3] };
+ else
+ return new byte [] { bytes [3], bytes [2], bytes [1], bytes [0] };
+ }
+
+ unsafe private static byte[] GetULongBytes (byte *bytes)
+ {
+ if (BitConverter.IsLittleEndian)
+ return new byte [] { bytes [0], bytes [1], bytes [2], bytes [3],
+ bytes [4], bytes [5], bytes [6], bytes [7] };
+ else
+ return new byte [] { bytes [7], bytes [6], bytes [5], bytes [4],
+ bytes [3], bytes [2], bytes [1], bytes [0] };
+ }
+
+ unsafe internal static byte[] GetBytes (bool value)
+ {
+ return new byte [] { value ? (byte)1 : (byte)0 };
+ }
+
+ unsafe internal static byte[] GetBytes (char value)
+ {
+ return GetUShortBytes ((byte *) &value);
+ }
+
+ unsafe internal static byte[] GetBytes (short value)
+ {
+ return GetUShortBytes ((byte *) &value);
+ }
+
+ unsafe internal static byte[] GetBytes (int value)
+ {
+ return GetUIntBytes ((byte *) &value);
+ }
+
+ unsafe internal static byte[] GetBytes (long value)
+ {
+ return GetULongBytes ((byte *) &value);
+ }
+
+ unsafe internal static byte[] GetBytes (ushort value)
+ {
+ return GetUShortBytes ((byte *) &value);
+ }
+
+ unsafe internal static byte[] GetBytes (uint value)
+ {
+ return GetUIntBytes ((byte *) &value);
+ }
+
+ unsafe internal static byte[] GetBytes (ulong value)
+ {
+ return GetULongBytes ((byte *) &value);
+ }
+
+ unsafe internal static byte[] GetBytes (float value)
+ {
+ return GetUIntBytes ((byte *) &value);
+ }
+
+ unsafe internal static byte[] GetBytes (double value)
+ {
+ return GetULongBytes ((byte *) &value);
+ }
+
+ unsafe private static void UShortFromBytes (byte *dst, byte[] src, int startIndex)
+ {
+ if (BitConverter.IsLittleEndian) {
+ dst [0] = src [startIndex];
+ dst [1] = src [startIndex + 1];
+ } else {
+ dst [0] = src [startIndex + 1];
+ dst [1] = src [startIndex];
+ }
+ }
+
+ unsafe private static void UIntFromBytes (byte *dst, byte[] src, int startIndex)
+ {
+ if (BitConverter.IsLittleEndian) {
+ dst [0] = src [startIndex];
+ dst [1] = src [startIndex + 1];
+ dst [2] = src [startIndex + 2];
+ dst [3] = src [startIndex + 3];
+ } else {
+ dst [0] = src [startIndex + 3];
+ dst [1] = src [startIndex + 2];
+ dst [2] = src [startIndex + 1];
+ dst [3] = src [startIndex];
+ }
+ }
+
+ unsafe private static void ULongFromBytes (byte *dst, byte[] src, int startIndex)
+ {
+ if (BitConverter.IsLittleEndian) {
+ for (int i = 0; i < 8; ++i)
+ dst [i] = src [startIndex + i];
+ } else {
+ for (int i = 0; i < 8; ++i)
+ dst [i] = src [startIndex + (7 - i)];
+ }
+ }
+
+ unsafe internal static bool ToBoolean (byte[] value, int startIndex)
+ {
+ return value [startIndex] != 0;
+ }
+
+ unsafe internal static char ToChar (byte[] value, int startIndex)
+ {
+ char ret;
+
+ UShortFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+
+ unsafe internal static short ToInt16 (byte[] value, int startIndex)
+ {
+ short ret;
+
+ UShortFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+
+ unsafe internal static int ToInt32 (byte[] value, int startIndex)
+ {
+ int ret;
+
+ UIntFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+
+ unsafe internal static long ToInt64 (byte[] value, int startIndex)
+ {
+ long ret;
+
+ ULongFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+
+ unsafe internal static ushort ToUInt16 (byte[] value, int startIndex)
+ {
+ ushort ret;
+
+ UShortFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+
+ unsafe internal static uint ToUInt32 (byte[] value, int startIndex)
+ {
+ uint ret;
+
+ UIntFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+
+ unsafe internal static ulong ToUInt64 (byte[] value, int startIndex)
+ {
+ ulong ret;
+
+ ULongFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+
+ unsafe internal static float ToSingle (byte[] value, int startIndex)
+ {
+ float ret;
+
+ UIntFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+
+ unsafe internal static double ToDouble (byte[] value, int startIndex)
+ {
+ double ret;
+
+ ULongFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/CertificateGenerator.cs b/Emby.Server.Implementations/Cryptography/CertificateGenerator.cs
new file mode 100644
index 000000000..2600d7470
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/CertificateGenerator.cs
@@ -0,0 +1,69 @@
+using MediaBrowser.Model.Logging;
+using System;
+using System.Collections;
+using System.Security.Cryptography;
+
+namespace Emby.Server.Core.Cryptography
+{
+ public class CertificateGenerator
+ {
+ private const string MonoTestRootAgency = "<RSAKeyValue><Modulus>v/4nALBxCE+9JgEC0LnDUvKh6e96PwTpN4Rj+vWnqKT7IAp1iK/JjuqvAg6DQ2vTfv0dTlqffmHH51OyioprcT5nzxcSTsZb/9jcHScG0s3/FRIWnXeLk/fgm7mSYhjUaHNI0m1/NTTktipicjKxo71hGIg9qucCWnDum+Krh/k=</Modulus><Exponent>AQAB</Exponent><P>9jbKxMXEruW2CfZrzhxtull4O8P47+mNsEL+9gf9QsRO1jJ77C+jmzfU6zbzjf8+ViK+q62tCMdC1ZzulwdpXQ==</P><Q>x5+p198l1PkK0Ga2mRh0SIYSykENpY2aLXoyZD/iUpKYAvATm0/wvKNrE4dKJyPCA+y3hfTdgVag+SP9avvDTQ==</Q><DP>ISSjCvXsUfbOGG05eddN1gXxL2pj+jegQRfjpk7RAsnWKvNExzhqd5x+ZuNQyc6QH5wxun54inP4RTUI0P/IaQ==</DP><DQ>R815VQmR3RIbPqzDXzv5j6CSH6fYlcTiQRtkBsUnzhWmkd/y3XmamO+a8zJFjOCCx9CcjpVuGziivBqi65lVPQ==</DQ><InverseQ>iYiu0KwMWI/dyqN3RJYUzuuLj02/oTD1pYpwo2rvNCXU1Q5VscOeu2DpNg1gWqI+1RrRCsEoaTNzXB1xtKNlSw==</InverseQ><D>nIfh1LYF8fjRBgMdAH/zt9UKHWiaCnc+jXzq5tkR8HVSKTVdzitD8bl1JgAfFQD8VjSXiCJqluexy/B5SGrCXQ49c78NIQj0hD+J13Y8/E0fUbW1QYbhj6Ff7oHyhaYe1WOQfkp2t/h+llHOdt1HRf7bt7dUknYp7m8bQKGxoYE=</D></RSAKeyValue>";
+
+ public static void CreateSelfSignCertificatePfx(
+ string fileName,
+ string hostname,
+ string password,
+ ILogger logger)
+ {
+ if (string.IsNullOrWhiteSpace(fileName))
+ {
+ throw new ArgumentNullException("fileName");
+ }
+
+ byte[] sn = Guid.NewGuid().ToByteArray();
+ string subject = string.Format("CN={0}", hostname);
+ string issuer = subject;
+ DateTime notBefore = DateTime.Now.AddDays(-2);
+ DateTime notAfter = DateTime.Now.AddYears(10);
+
+ RSA issuerKey = RSA.Create();
+ issuerKey.FromXmlString(MonoTestRootAgency);
+ RSA subjectKey = RSA.Create();
+
+ // serial number MUST be positive
+ if ((sn[0] & 0x80) == 0x80)
+ sn[0] -= 0x80;
+
+ issuer = subject;
+ issuerKey = subjectKey;
+
+ X509CertificateBuilder cb = new X509CertificateBuilder(3);
+ cb.SerialNumber = sn;
+ cb.IssuerName = issuer;
+ cb.NotBefore = notBefore;
+ cb.NotAfter = notAfter;
+ cb.SubjectName = subject;
+ cb.SubjectPublicKey = subjectKey;
+
+ // signature
+ cb.Hash = "SHA256";
+ byte[] rawcert = cb.Sign(issuerKey);
+
+ PKCS12 p12 = new PKCS12();
+
+
+ ArrayList list = new ArrayList();
+ // we use a fixed array to avoid endianess issues
+ // (in case some tools requires the ID to be 1).
+ list.Add(new byte[4] { 1, 0, 0, 0 });
+ Hashtable attributes = new Hashtable(1);
+ attributes.Add(PKCS9.localKeyId, list);
+
+ p12.AddCertificate(new X509Certificate(rawcert), attributes);
+ p12.Password = password;
+
+ p12.AddPkcs8ShroudedKeyBag(subjectKey, attributes);
+ p12.SaveToFile(fileName);
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/CryptoConvert.cs b/Emby.Server.Implementations/Cryptography/CryptoConvert.cs
new file mode 100644
index 000000000..70a91bfff
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/CryptoConvert.cs
@@ -0,0 +1,745 @@
+//
+// CryptoConvert.cs - Crypto Convertion Routines
+//
+// Author:
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2006 Novell Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Globalization;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Emby.Server.Core.Cryptography
+{
+
+ public sealed class CryptoConvert {
+
+ private CryptoConvert ()
+ {
+ }
+
+ static private int ToInt32LE (byte [] bytes, int offset)
+ {
+ return (bytes [offset+3] << 24) | (bytes [offset+2] << 16) | (bytes [offset+1] << 8) | bytes [offset];
+ }
+
+ static private uint ToUInt32LE (byte [] bytes, int offset)
+ {
+ return (uint)((bytes [offset+3] << 24) | (bytes [offset+2] << 16) | (bytes [offset+1] << 8) | bytes [offset]);
+ }
+
+ static private byte [] GetBytesLE (int val)
+ {
+ return new byte [] {
+ (byte) (val & 0xff),
+ (byte) ((val >> 8) & 0xff),
+ (byte) ((val >> 16) & 0xff),
+ (byte) ((val >> 24) & 0xff)
+ };
+ }
+
+ static private byte[] Trim (byte[] array)
+ {
+ for (int i=0; i < array.Length; i++) {
+ if (array [i] != 0x00) {
+ byte[] result = new byte [array.Length - i];
+ Buffer.BlockCopy (array, i, result, 0, result.Length);
+ return result;
+ }
+ }
+ return null;
+ }
+
+ // convert the key from PRIVATEKEYBLOB to RSA
+ // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/security/Security/private_key_blobs.asp
+ // e.g. SNK files, PVK files
+ static public RSA FromCapiPrivateKeyBlob (byte[] blob)
+ {
+ return FromCapiPrivateKeyBlob (blob, 0);
+ }
+
+ static public RSA FromCapiPrivateKeyBlob (byte[] blob, int offset)
+ {
+ if (blob == null)
+ throw new ArgumentNullException ("blob");
+ if (offset >= blob.Length)
+ throw new ArgumentException ("blob is too small.");
+
+ RSAParameters rsap = new RSAParameters ();
+ try {
+ if ((blob [offset] != 0x07) || // PRIVATEKEYBLOB (0x07)
+ (blob [offset+1] != 0x02) || // Version (0x02)
+ (blob [offset+2] != 0x00) || // Reserved (word)
+ (blob [offset+3] != 0x00) ||
+ (ToUInt32LE (blob, offset+8) != 0x32415352)) // DWORD magic = RSA2
+ throw new CryptographicException ("Invalid blob header");
+
+ // ALGID (CALG_RSA_SIGN, CALG_RSA_KEYX, ...)
+ // int algId = ToInt32LE (blob, offset+4);
+
+ // DWORD bitlen
+ int bitLen = ToInt32LE (blob, offset+12);
+
+ // DWORD public exponent
+ byte[] exp = new byte [4];
+ Buffer.BlockCopy (blob, offset+16, exp, 0, 4);
+ Array.Reverse (exp);
+ rsap.Exponent = Trim (exp);
+
+ int pos = offset+20;
+ // BYTE modulus[rsapubkey.bitlen/8];
+ int byteLen = (bitLen >> 3);
+ rsap.Modulus = new byte [byteLen];
+ Buffer.BlockCopy (blob, pos, rsap.Modulus, 0, byteLen);
+ Array.Reverse (rsap.Modulus);
+ pos += byteLen;
+
+ // BYTE prime1[rsapubkey.bitlen/16];
+ int byteHalfLen = (byteLen >> 1);
+ rsap.P = new byte [byteHalfLen];
+ Buffer.BlockCopy (blob, pos, rsap.P, 0, byteHalfLen);
+ Array.Reverse (rsap.P);
+ pos += byteHalfLen;
+
+ // BYTE prime2[rsapubkey.bitlen/16];
+ rsap.Q = new byte [byteHalfLen];
+ Buffer.BlockCopy (blob, pos, rsap.Q, 0, byteHalfLen);
+ Array.Reverse (rsap.Q);
+ pos += byteHalfLen;
+
+ // BYTE exponent1[rsapubkey.bitlen/16];
+ rsap.DP = new byte [byteHalfLen];
+ Buffer.BlockCopy (blob, pos, rsap.DP, 0, byteHalfLen);
+ Array.Reverse (rsap.DP);
+ pos += byteHalfLen;
+
+ // BYTE exponent2[rsapubkey.bitlen/16];
+ rsap.DQ = new byte [byteHalfLen];
+ Buffer.BlockCopy (blob, pos, rsap.DQ, 0, byteHalfLen);
+ Array.Reverse (rsap.DQ);
+ pos += byteHalfLen;
+
+ // BYTE coefficient[rsapubkey.bitlen/16];
+ rsap.InverseQ = new byte [byteHalfLen];
+ Buffer.BlockCopy (blob, pos, rsap.InverseQ, 0, byteHalfLen);
+ Array.Reverse (rsap.InverseQ);
+ pos += byteHalfLen;
+
+ // ok, this is hackish but CryptoAPI support it so...
+ // note: only works because CRT is used by default
+ // http://bugzilla.ximian.com/show_bug.cgi?id=57941
+ rsap.D = new byte [byteLen]; // must be allocated
+ if (pos + byteLen + offset <= blob.Length) {
+ // BYTE privateExponent[rsapubkey.bitlen/8];
+ Buffer.BlockCopy (blob, pos, rsap.D, 0, byteLen);
+ Array.Reverse (rsap.D);
+ }
+ }
+ catch (Exception e) {
+ throw new CryptographicException ("Invalid blob.", e);
+ }
+
+ RSA rsa = null;
+ try
+ {
+ rsa = RSA.Create();
+ rsa.ImportParameters(rsap);
+ }
+ catch (CryptographicException ce)
+ {
+ // this may cause problem when this code is run under
+ // the SYSTEM identity on Windows (e.g. ASP.NET). See
+ // http://bugzilla.ximian.com/show_bug.cgi?id=77559
+ try
+ {
+ CspParameters csp = new CspParameters();
+ csp.Flags = CspProviderFlags.UseMachineKeyStore;
+ rsa = new RSACryptoServiceProvider(csp);
+ rsa.ImportParameters(rsap);
+ }
+ catch
+ {
+ // rethrow original, not the later, exception if this fails
+ throw ce;
+ }
+ }
+ return rsa;
+ }
+
+ static public DSA FromCapiPrivateKeyBlobDSA (byte[] blob)
+ {
+ return FromCapiPrivateKeyBlobDSA (blob, 0);
+ }
+
+ static public DSA FromCapiPrivateKeyBlobDSA (byte[] blob, int offset)
+ {
+ if (blob == null)
+ throw new ArgumentNullException ("blob");
+ if (offset >= blob.Length)
+ throw new ArgumentException ("blob is too small.");
+
+ DSAParameters dsap = new DSAParameters ();
+ try {
+ if ((blob [offset] != 0x07) || // PRIVATEKEYBLOB (0x07)
+ (blob [offset + 1] != 0x02) || // Version (0x02)
+ (blob [offset + 2] != 0x00) || // Reserved (word)
+ (blob [offset + 3] != 0x00) ||
+ (ToUInt32LE (blob, offset + 8) != 0x32535344)) // DWORD magic
+ throw new CryptographicException ("Invalid blob header");
+
+ int bitlen = ToInt32LE (blob, offset + 12);
+ int bytelen = bitlen >> 3;
+ int pos = offset + 16;
+
+ dsap.P = new byte [bytelen];
+ Buffer.BlockCopy (blob, pos, dsap.P, 0, bytelen);
+ Array.Reverse (dsap.P);
+ pos += bytelen;
+
+ dsap.Q = new byte [20];
+ Buffer.BlockCopy (blob, pos, dsap.Q, 0, 20);
+ Array.Reverse (dsap.Q);
+ pos += 20;
+
+ dsap.G = new byte [bytelen];
+ Buffer.BlockCopy (blob, pos, dsap.G, 0, bytelen);
+ Array.Reverse (dsap.G);
+ pos += bytelen;
+
+ dsap.X = new byte [20];
+ Buffer.BlockCopy (blob, pos, dsap.X, 0, 20);
+ Array.Reverse (dsap.X);
+ pos += 20;
+
+ dsap.Counter = ToInt32LE (blob, pos);
+ pos += 4;
+
+ dsap.Seed = new byte [20];
+ Buffer.BlockCopy (blob, pos, dsap.Seed, 0, 20);
+ Array.Reverse (dsap.Seed);
+ pos += 20;
+ }
+ catch (Exception e) {
+ throw new CryptographicException ("Invalid blob.", e);
+ }
+
+ DSA dsa = null;
+ try
+ {
+ dsa = (DSA)DSA.Create();
+ dsa.ImportParameters(dsap);
+ }
+ catch (CryptographicException ce)
+ {
+ // this may cause problem when this code is run under
+ // the SYSTEM identity on Windows (e.g. ASP.NET). See
+ // http://bugzilla.ximian.com/show_bug.cgi?id=77559
+ try
+ {
+ CspParameters csp = new CspParameters();
+ csp.Flags = CspProviderFlags.UseMachineKeyStore;
+ dsa = new DSACryptoServiceProvider(csp);
+ dsa.ImportParameters(dsap);
+ }
+ catch
+ {
+ // rethrow original, not the later, exception if this fails
+ throw ce;
+ }
+ }
+ return dsa;
+ }
+
+ static public byte[] ToCapiPrivateKeyBlob (RSA rsa)
+ {
+ RSAParameters p = rsa.ExportParameters (true);
+ int keyLength = p.Modulus.Length; // in bytes
+ byte[] blob = new byte [20 + (keyLength << 2) + (keyLength >> 1)];
+
+ blob [0] = 0x07; // Type - PRIVATEKEYBLOB (0x07)
+ blob [1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
+ // [2], [3] // RESERVED - Always 0
+ blob [5] = 0x24; // ALGID - Always 00 24 00 00 (for CALG_RSA_SIGN)
+ blob [8] = 0x52; // Magic - RSA2 (ASCII in hex)
+ blob [9] = 0x53;
+ blob [10] = 0x41;
+ blob [11] = 0x32;
+
+ byte[] bitlen = GetBytesLE (keyLength << 3);
+ blob [12] = bitlen [0]; // bitlen
+ blob [13] = bitlen [1];
+ blob [14] = bitlen [2];
+ blob [15] = bitlen [3];
+
+ // public exponent (DWORD)
+ int pos = 16;
+ int n = p.Exponent.Length;
+ while (n > 0)
+ blob [pos++] = p.Exponent [--n];
+ // modulus
+ pos = 20;
+ byte[] part = p.Modulus;
+ int len = part.Length;
+ Array.Reverse (part, 0, len);
+ Buffer.BlockCopy (part, 0, blob, pos, len);
+ pos += len;
+ // private key
+ part = p.P;
+ len = part.Length;
+ Array.Reverse (part, 0, len);
+ Buffer.BlockCopy (part, 0, blob, pos, len);
+ pos += len;
+
+ part = p.Q;
+ len = part.Length;
+ Array.Reverse (part, 0, len);
+ Buffer.BlockCopy (part, 0, blob, pos, len);
+ pos += len;
+
+ part = p.DP;
+ len = part.Length;
+ Array.Reverse (part, 0, len);
+ Buffer.BlockCopy (part, 0, blob, pos, len);
+ pos += len;
+
+ part = p.DQ;
+ len = part.Length;
+ Array.Reverse (part, 0, len);
+ Buffer.BlockCopy (part, 0, blob, pos, len);
+ pos += len;
+
+ part = p.InverseQ;
+ len = part.Length;
+ Array.Reverse (part, 0, len);
+ Buffer.BlockCopy (part, 0, blob, pos, len);
+ pos += len;
+
+ part = p.D;
+ len = part.Length;
+ Array.Reverse (part, 0, len);
+ Buffer.BlockCopy (part, 0, blob, pos, len);
+
+ return blob;
+ }
+
+ static public byte[] ToCapiPrivateKeyBlob (DSA dsa)
+ {
+ DSAParameters p = dsa.ExportParameters (true);
+ int keyLength = p.P.Length; // in bytes
+
+ // header + P + Q + G + X + count + seed
+ byte[] blob = new byte [16 + keyLength + 20 + keyLength + 20 + 4 + 20];
+
+ blob [0] = 0x07; // Type - PRIVATEKEYBLOB (0x07)
+ blob [1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
+ // [2], [3] // RESERVED - Always 0
+ blob [5] = 0x22; // ALGID
+ blob [8] = 0x44; // Magic
+ blob [9] = 0x53;
+ blob [10] = 0x53;
+ blob [11] = 0x32;
+
+ byte[] bitlen = GetBytesLE (keyLength << 3);
+ blob [12] = bitlen [0];
+ blob [13] = bitlen [1];
+ blob [14] = bitlen [2];
+ blob [15] = bitlen [3];
+
+ int pos = 16;
+ byte[] part = p.P;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, keyLength);
+ pos += keyLength;
+
+ part = p.Q;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, 20);
+ pos += 20;
+
+ part = p.G;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, keyLength);
+ pos += keyLength;
+
+ part = p.X;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, 20);
+ pos += 20;
+
+ Buffer.BlockCopy (GetBytesLE (p.Counter), 0, blob, pos, 4);
+ pos += 4;
+
+ part = p.Seed;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, 20);
+
+ return blob;
+ }
+
+ static public RSA FromCapiPublicKeyBlob (byte[] blob)
+ {
+ return FromCapiPublicKeyBlob (blob, 0);
+ }
+
+ static public RSA FromCapiPublicKeyBlob (byte[] blob, int offset)
+ {
+ if (blob == null)
+ throw new ArgumentNullException ("blob");
+ if (offset >= blob.Length)
+ throw new ArgumentException ("blob is too small.");
+
+ try {
+ if ((blob [offset] != 0x06) || // PUBLICKEYBLOB (0x06)
+ (blob [offset+1] != 0x02) || // Version (0x02)
+ (blob [offset+2] != 0x00) || // Reserved (word)
+ (blob [offset+3] != 0x00) ||
+ (ToUInt32LE (blob, offset+8) != 0x31415352)) // DWORD magic = RSA1
+ throw new CryptographicException ("Invalid blob header");
+
+ // ALGID (CALG_RSA_SIGN, CALG_RSA_KEYX, ...)
+ // int algId = ToInt32LE (blob, offset+4);
+
+ // DWORD bitlen
+ int bitLen = ToInt32LE (blob, offset+12);
+
+ // DWORD public exponent
+ RSAParameters rsap = new RSAParameters ();
+ rsap.Exponent = new byte [3];
+ rsap.Exponent [0] = blob [offset+18];
+ rsap.Exponent [1] = blob [offset+17];
+ rsap.Exponent [2] = blob [offset+16];
+
+ int pos = offset+20;
+ // BYTE modulus[rsapubkey.bitlen/8];
+ int byteLen = (bitLen >> 3);
+ rsap.Modulus = new byte [byteLen];
+ Buffer.BlockCopy (blob, pos, rsap.Modulus, 0, byteLen);
+ Array.Reverse (rsap.Modulus);
+ RSA rsa = null;
+ try
+ {
+ rsa = RSA.Create();
+ rsa.ImportParameters(rsap);
+ }
+ catch (CryptographicException)
+ {
+ // this may cause problem when this code is run under
+ // the SYSTEM identity on Windows (e.g. ASP.NET). See
+ // http://bugzilla.ximian.com/show_bug.cgi?id=77559
+ CspParameters csp = new CspParameters();
+ csp.Flags = CspProviderFlags.UseMachineKeyStore;
+ rsa = new RSACryptoServiceProvider(csp);
+ rsa.ImportParameters(rsap);
+ }
+ return rsa;
+ }
+ catch (Exception e) {
+ throw new CryptographicException ("Invalid blob.", e);
+ }
+ }
+
+ static public DSA FromCapiPublicKeyBlobDSA (byte[] blob)
+ {
+ return FromCapiPublicKeyBlobDSA (blob, 0);
+ }
+
+ static public DSA FromCapiPublicKeyBlobDSA (byte[] blob, int offset)
+ {
+ if (blob == null)
+ throw new ArgumentNullException ("blob");
+ if (offset >= blob.Length)
+ throw new ArgumentException ("blob is too small.");
+
+ try {
+ if ((blob [offset] != 0x06) || // PUBLICKEYBLOB (0x06)
+ (blob [offset + 1] != 0x02) || // Version (0x02)
+ (blob [offset + 2] != 0x00) || // Reserved (word)
+ (blob [offset + 3] != 0x00) ||
+ (ToUInt32LE (blob, offset + 8) != 0x31535344)) // DWORD magic
+ throw new CryptographicException ("Invalid blob header");
+
+ int bitlen = ToInt32LE (blob, offset + 12);
+ DSAParameters dsap = new DSAParameters ();
+ int bytelen = bitlen >> 3;
+ int pos = offset + 16;
+
+ dsap.P = new byte [bytelen];
+ Buffer.BlockCopy (blob, pos, dsap.P, 0, bytelen);
+ Array.Reverse (dsap.P);
+ pos += bytelen;
+
+ dsap.Q = new byte [20];
+ Buffer.BlockCopy (blob, pos, dsap.Q, 0, 20);
+ Array.Reverse (dsap.Q);
+ pos += 20;
+
+ dsap.G = new byte [bytelen];
+ Buffer.BlockCopy (blob, pos, dsap.G, 0, bytelen);
+ Array.Reverse (dsap.G);
+ pos += bytelen;
+
+ dsap.Y = new byte [bytelen];
+ Buffer.BlockCopy (blob, pos, dsap.Y, 0, bytelen);
+ Array.Reverse (dsap.Y);
+ pos += bytelen;
+
+ dsap.Counter = ToInt32LE (blob, pos);
+ pos += 4;
+
+ dsap.Seed = new byte [20];
+ Buffer.BlockCopy (blob, pos, dsap.Seed, 0, 20);
+ Array.Reverse (dsap.Seed);
+ pos += 20;
+
+ DSA dsa = (DSA)DSA.Create ();
+ dsa.ImportParameters (dsap);
+ return dsa;
+ }
+ catch (Exception e) {
+ throw new CryptographicException ("Invalid blob.", e);
+ }
+ }
+
+ static public byte[] ToCapiPublicKeyBlob (RSA rsa)
+ {
+ RSAParameters p = rsa.ExportParameters (false);
+ int keyLength = p.Modulus.Length; // in bytes
+ byte[] blob = new byte [20 + keyLength];
+
+ blob [0] = 0x06; // Type - PUBLICKEYBLOB (0x06)
+ blob [1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
+ // [2], [3] // RESERVED - Always 0
+ blob [5] = 0x24; // ALGID - Always 00 24 00 00 (for CALG_RSA_SIGN)
+ blob [8] = 0x52; // Magic - RSA1 (ASCII in hex)
+ blob [9] = 0x53;
+ blob [10] = 0x41;
+ blob [11] = 0x31;
+
+ byte[] bitlen = GetBytesLE (keyLength << 3);
+ blob [12] = bitlen [0]; // bitlen
+ blob [13] = bitlen [1];
+ blob [14] = bitlen [2];
+ blob [15] = bitlen [3];
+
+ // public exponent (DWORD)
+ int pos = 16;
+ int n = p.Exponent.Length;
+ while (n > 0)
+ blob [pos++] = p.Exponent [--n];
+ // modulus
+ pos = 20;
+ byte[] part = p.Modulus;
+ int len = part.Length;
+ Array.Reverse (part, 0, len);
+ Buffer.BlockCopy (part, 0, blob, pos, len);
+ pos += len;
+ return blob;
+ }
+
+ static public byte[] ToCapiPublicKeyBlob (DSA dsa)
+ {
+ DSAParameters p = dsa.ExportParameters (false);
+ int keyLength = p.P.Length; // in bytes
+
+ // header + P + Q + G + Y + count + seed
+ byte[] blob = new byte [16 + keyLength + 20 + keyLength + keyLength + 4 + 20];
+
+ blob [0] = 0x06; // Type - PUBLICKEYBLOB (0x06)
+ blob [1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
+ // [2], [3] // RESERVED - Always 0
+ blob [5] = 0x22; // ALGID
+ blob [8] = 0x44; // Magic
+ blob [9] = 0x53;
+ blob [10] = 0x53;
+ blob [11] = 0x31;
+
+ byte[] bitlen = GetBytesLE (keyLength << 3);
+ blob [12] = bitlen [0];
+ blob [13] = bitlen [1];
+ blob [14] = bitlen [2];
+ blob [15] = bitlen [3];
+
+ int pos = 16;
+ byte[] part;
+
+ part = p.P;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, keyLength);
+ pos += keyLength;
+
+ part = p.Q;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, 20);
+ pos += 20;
+
+ part = p.G;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, keyLength);
+ pos += keyLength;
+
+ part = p.Y;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, keyLength);
+ pos += keyLength;
+
+ Buffer.BlockCopy (GetBytesLE (p.Counter), 0, blob, pos, 4);
+ pos += 4;
+
+ part = p.Seed;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, 20);
+
+ return blob;
+ }
+
+ // PRIVATEKEYBLOB
+ // PUBLICKEYBLOB
+ static public RSA FromCapiKeyBlob (byte[] blob)
+ {
+ return FromCapiKeyBlob (blob, 0);
+ }
+
+ static public RSA FromCapiKeyBlob (byte[] blob, int offset)
+ {
+ if (blob == null)
+ throw new ArgumentNullException ("blob");
+ if (offset >= blob.Length)
+ throw new ArgumentException ("blob is too small.");
+
+ switch (blob [offset]) {
+ case 0x00:
+ // this could be a public key inside an header
+ // like "sn -e" would produce
+ if (blob [offset + 12] == 0x06) {
+ return FromCapiPublicKeyBlob (blob, offset + 12);
+ }
+ break;
+ case 0x06:
+ return FromCapiPublicKeyBlob (blob, offset);
+ case 0x07:
+ return FromCapiPrivateKeyBlob (blob, offset);
+ }
+ throw new CryptographicException ("Unknown blob format.");
+ }
+
+ static public DSA FromCapiKeyBlobDSA (byte[] blob)
+ {
+ return FromCapiKeyBlobDSA (blob, 0);
+ }
+
+ static public DSA FromCapiKeyBlobDSA (byte[] blob, int offset)
+ {
+ if (blob == null)
+ throw new ArgumentNullException ("blob");
+ if (offset >= blob.Length)
+ throw new ArgumentException ("blob is too small.");
+
+ switch (blob [offset]) {
+ case 0x06:
+ return FromCapiPublicKeyBlobDSA (blob, offset);
+ case 0x07:
+ return FromCapiPrivateKeyBlobDSA (blob, offset);
+ }
+ throw new CryptographicException ("Unknown blob format.");
+ }
+
+ static public byte[] ToCapiKeyBlob (AsymmetricAlgorithm keypair, bool includePrivateKey)
+ {
+ if (keypair == null)
+ throw new ArgumentNullException ("keypair");
+
+ // check between RSA and DSA (and potentially others like DH)
+ if (keypair is RSA)
+ return ToCapiKeyBlob ((RSA)keypair, includePrivateKey);
+ else if (keypair is DSA)
+ return ToCapiKeyBlob ((DSA)keypair, includePrivateKey);
+ else
+ return null; // TODO
+ }
+
+ static public byte[] ToCapiKeyBlob (RSA rsa, bool includePrivateKey)
+ {
+ if (rsa == null)
+ throw new ArgumentNullException ("rsa");
+
+ if (includePrivateKey)
+ return ToCapiPrivateKeyBlob (rsa);
+ else
+ return ToCapiPublicKeyBlob (rsa);
+ }
+
+ static public byte[] ToCapiKeyBlob (DSA dsa, bool includePrivateKey)
+ {
+ if (dsa == null)
+ throw new ArgumentNullException ("dsa");
+
+ if (includePrivateKey)
+ return ToCapiPrivateKeyBlob (dsa);
+ else
+ return ToCapiPublicKeyBlob (dsa);
+ }
+
+ static public string ToHex (byte[] input)
+ {
+ if (input == null)
+ return null;
+
+ StringBuilder sb = new StringBuilder (input.Length * 2);
+ foreach (byte b in input) {
+ sb.Append (b.ToString ("X2", CultureInfo.InvariantCulture));
+ }
+ return sb.ToString ();
+ }
+
+ static private byte FromHexChar (char c)
+ {
+ if ((c >= 'a') && (c <= 'f'))
+ return (byte) (c - 'a' + 10);
+ if ((c >= 'A') && (c <= 'F'))
+ return (byte) (c - 'A' + 10);
+ if ((c >= '0') && (c <= '9'))
+ return (byte) (c - '0');
+ throw new ArgumentException ("invalid hex char");
+ }
+
+ static public byte[] FromHex (string hex)
+ {
+ if (hex == null)
+ return null;
+ if ((hex.Length & 0x1) == 0x1)
+ throw new ArgumentException ("Length must be a multiple of 2");
+
+ byte[] result = new byte [hex.Length >> 1];
+ int n = 0;
+ int i = 0;
+ while (n < result.Length) {
+ result [n] = (byte) (FromHexChar (hex [i++]) << 4);
+ result [n++] += FromHexChar (hex [i++]);
+ }
+ return result;
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/PKCS1.cs b/Emby.Server.Implementations/Cryptography/PKCS1.cs
new file mode 100644
index 000000000..24c0708c5
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/PKCS1.cs
@@ -0,0 +1,491 @@
+//
+// PKCS1.cs - Implements PKCS#1 primitives.
+//
+// Author:
+// Sebastien Pouliot <sebastien@xamarin.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+// Copyright 2013 Xamarin Inc. (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Security.Cryptography;
+
+namespace Emby.Server.Core.Cryptography
+{
+
+ // References:
+ // a. PKCS#1: RSA Cryptography Standard
+ // http://www.rsasecurity.com/rsalabs/pkcs/pkcs-1/index.html
+
+ public sealed class PKCS1 {
+
+ private PKCS1 ()
+ {
+ }
+
+ private static bool Compare (byte[] array1, byte[] array2)
+ {
+ bool result = (array1.Length == array2.Length);
+ if (result) {
+ for (int i=0; i < array1.Length; i++)
+ if (array1[i] != array2[i])
+ return false;
+ }
+ return result;
+ }
+
+ private static byte[] xor (byte[] array1, byte[] array2)
+ {
+ byte[] result = new byte [array1.Length];
+ for (int i=0; i < result.Length; i++)
+ result[i] = (byte) (array1[i] ^ array2[i]);
+ return result;
+ }
+
+ private static byte[] emptySHA1 = { 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09 };
+ private static byte[] emptySHA256 = { 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 };
+ private static byte[] emptySHA384 = { 0x38, 0xb0, 0x60, 0xa7, 0x51, 0xac, 0x96, 0x38, 0x4c, 0xd9, 0x32, 0x7e, 0xb1, 0xb1, 0xe3, 0x6a, 0x21, 0xfd, 0xb7, 0x11, 0x14, 0xbe, 0x07, 0x43, 0x4c, 0x0c, 0xc7, 0xbf, 0x63, 0xf6, 0xe1, 0xda, 0x27, 0x4e, 0xde, 0xbf, 0xe7, 0x6f, 0x65, 0xfb, 0xd5, 0x1a, 0xd2, 0xf1, 0x48, 0x98, 0xb9, 0x5b };
+ private static byte[] emptySHA512 = { 0xcf, 0x83, 0xe1, 0x35, 0x7e, 0xef, 0xb8, 0xbd, 0xf1, 0x54, 0x28, 0x50, 0xd6, 0x6d, 0x80, 0x07, 0xd6, 0x20, 0xe4, 0x05, 0x0b, 0x57, 0x15, 0xdc, 0x83, 0xf4, 0xa9, 0x21, 0xd3, 0x6c, 0xe9, 0xce, 0x47, 0xd0, 0xd1, 0x3c, 0x5d, 0x85, 0xf2, 0xb0, 0xff, 0x83, 0x18, 0xd2, 0x87, 0x7e, 0xec, 0x2f, 0x63, 0xb9, 0x31, 0xbd, 0x47, 0x41, 0x7a, 0x81, 0xa5, 0x38, 0x32, 0x7a, 0xf9, 0x27, 0xda, 0x3e };
+
+ private static byte[] GetEmptyHash (HashAlgorithm hash)
+ {
+ if (hash is SHA1)
+ return emptySHA1;
+ else if (hash is SHA256)
+ return emptySHA256;
+ else if (hash is SHA384)
+ return emptySHA384;
+ else if (hash is SHA512)
+ return emptySHA512;
+ else
+ return hash.ComputeHash ((byte[])null);
+ }
+
+ // PKCS #1 v.2.1, Section 4.1
+ // I2OSP converts a non-negative integer to an octet string of a specified length.
+ public static byte[] I2OSP (int x, int size)
+ {
+ byte[] array = BitConverterLE.GetBytes (x);
+ Array.Reverse (array, 0, array.Length);
+ return I2OSP (array, size);
+ }
+
+ public static byte[] I2OSP (byte[] x, int size)
+ {
+ byte[] result = new byte [size];
+ Buffer.BlockCopy (x, 0, result, (result.Length - x.Length), x.Length);
+ return result;
+ }
+
+ // PKCS #1 v.2.1, Section 4.2
+ // OS2IP converts an octet string to a nonnegative integer.
+ public static byte[] OS2IP (byte[] x)
+ {
+ int i = 0;
+ while ((x [i++] == 0x00) && (i < x.Length)) {
+ // confuse compiler into reporting a warning with {}
+ }
+ i--;
+ if (i > 0) {
+ byte[] result = new byte [x.Length - i];
+ Buffer.BlockCopy (x, i, result, 0, result.Length);
+ return result;
+ }
+ else
+ return x;
+ }
+
+ // PKCS #1 v.2.1, Section 5.1.1
+ public static byte[] RSAEP (RSA rsa, byte[] m)
+ {
+ // c = m^e mod n
+ return rsa.EncryptValue (m);
+ }
+
+ // PKCS #1 v.2.1, Section 5.1.2
+ public static byte[] RSADP (RSA rsa, byte[] c)
+ {
+ // m = c^d mod n
+ // Decrypt value may apply CRT optimizations
+ return rsa.DecryptValue (c);
+ }
+
+ // PKCS #1 v.2.1, Section 5.2.1
+ public static byte[] RSASP1 (RSA rsa, byte[] m)
+ {
+ // first form: s = m^d mod n
+ // Decrypt value may apply CRT optimizations
+ return rsa.DecryptValue (m);
+ }
+
+ // PKCS #1 v.2.1, Section 5.2.2
+ public static byte[] RSAVP1 (RSA rsa, byte[] s)
+ {
+ // m = s^e mod n
+ return rsa.EncryptValue (s);
+ }
+
+ // PKCS #1 v.2.1, Section 7.1.1
+ // RSAES-OAEP-ENCRYPT ((n, e), M, L)
+ public static byte[] Encrypt_OAEP (RSA rsa, HashAlgorithm hash, RandomNumberGenerator rng, byte[] M)
+ {
+ int size = rsa.KeySize / 8;
+ int hLen = hash.HashSize / 8;
+ if (M.Length > size - 2 * hLen - 2)
+ throw new CryptographicException ("message too long");
+ // empty label L SHA1 hash
+ byte[] lHash = GetEmptyHash (hash);
+ int PSLength = (size - M.Length - 2 * hLen - 2);
+ // DB = lHash || PS || 0x01 || M
+ byte[] DB = new byte [lHash.Length + PSLength + 1 + M.Length];
+ Buffer.BlockCopy (lHash, 0, DB, 0, lHash.Length);
+ DB [(lHash.Length + PSLength)] = 0x01;
+ Buffer.BlockCopy (M, 0, DB, (DB.Length - M.Length), M.Length);
+
+ byte[] seed = new byte [hLen];
+ rng.GetBytes (seed);
+
+ byte[] dbMask = MGF1 (hash, seed, size - hLen - 1);
+ byte[] maskedDB = xor (DB, dbMask);
+ byte[] seedMask = MGF1 (hash, maskedDB, hLen);
+ byte[] maskedSeed = xor (seed, seedMask);
+ // EM = 0x00 || maskedSeed || maskedDB
+ byte[] EM = new byte [maskedSeed.Length + maskedDB.Length + 1];
+ Buffer.BlockCopy (maskedSeed, 0, EM, 1, maskedSeed.Length);
+ Buffer.BlockCopy (maskedDB, 0, EM, maskedSeed.Length + 1, maskedDB.Length);
+
+ byte[] m = OS2IP (EM);
+ byte[] c = RSAEP (rsa, m);
+ return I2OSP (c, size);
+ }
+
+ // PKCS #1 v.2.1, Section 7.1.2
+ // RSAES-OAEP-DECRYPT (K, C, L)
+ public static byte[] Decrypt_OAEP (RSA rsa, HashAlgorithm hash, byte[] C)
+ {
+ int size = rsa.KeySize / 8;
+ int hLen = hash.HashSize / 8;
+ if ((size < (2 * hLen + 2)) || (C.Length != size))
+ throw new CryptographicException ("decryption error");
+
+ byte[] c = OS2IP (C);
+ byte[] m = RSADP (rsa, c);
+ byte[] EM = I2OSP (m, size);
+
+ // split EM = Y || maskedSeed || maskedDB
+ byte[] maskedSeed = new byte [hLen];
+ Buffer.BlockCopy (EM, 1, maskedSeed, 0, maskedSeed.Length);
+ byte[] maskedDB = new byte [size - hLen - 1];
+ Buffer.BlockCopy (EM, (EM.Length - maskedDB.Length), maskedDB, 0, maskedDB.Length);
+
+ byte[] seedMask = MGF1 (hash, maskedDB, hLen);
+ byte[] seed = xor (maskedSeed, seedMask);
+ byte[] dbMask = MGF1 (hash, seed, size - hLen - 1);
+ byte[] DB = xor (maskedDB, dbMask);
+
+ byte[] lHash = GetEmptyHash (hash);
+ // split DB = lHash' || PS || 0x01 || M
+ byte[] dbHash = new byte [lHash.Length];
+ Buffer.BlockCopy (DB, 0, dbHash, 0, dbHash.Length);
+ bool h = Compare (lHash, dbHash);
+
+ // find separator 0x01
+ int nPos = lHash.Length;
+ while (DB[nPos] == 0)
+ nPos++;
+
+ int Msize = DB.Length - nPos - 1;
+ byte[] M = new byte [Msize];
+ Buffer.BlockCopy (DB, (nPos + 1), M, 0, Msize);
+
+ // we could have returned EM[0] sooner but would be helping a timing attack
+ if ((EM[0] != 0) || (!h) || (DB[nPos] != 0x01))
+ return null;
+ return M;
+ }
+
+ // PKCS #1 v.2.1, Section 7.2.1
+ // RSAES-PKCS1-V1_5-ENCRYPT ((n, e), M)
+ public static byte[] Encrypt_v15 (RSA rsa, RandomNumberGenerator rng, byte[] M)
+ {
+ int size = rsa.KeySize / 8;
+ if (M.Length > size - 11)
+ throw new CryptographicException ("message too long");
+ int PSLength = System.Math.Max (8, (size - M.Length - 3));
+ byte[] PS = new byte [PSLength];
+ rng.GetNonZeroBytes (PS);
+ byte[] EM = new byte [size];
+ EM [1] = 0x02;
+ Buffer.BlockCopy (PS, 0, EM, 2, PSLength);
+ Buffer.BlockCopy (M, 0, EM, (size - M.Length), M.Length);
+
+ byte[] m = OS2IP (EM);
+ byte[] c = RSAEP (rsa, m);
+ byte[] C = I2OSP (c, size);
+ return C;
+ }
+
+ // PKCS #1 v.2.1, Section 7.2.2
+ // RSAES-PKCS1-V1_5-DECRYPT (K, C)
+ public static byte[] Decrypt_v15 (RSA rsa, byte[] C)
+ {
+ int size = rsa.KeySize >> 3; // div by 8
+ if ((size < 11) || (C.Length > size))
+ throw new CryptographicException ("decryption error");
+ byte[] c = OS2IP (C);
+ byte[] m = RSADP (rsa, c);
+ byte[] EM = I2OSP (m, size);
+
+ if ((EM [0] != 0x00) || (EM [1] != 0x02))
+ return null;
+
+ int mPos = 10;
+ // PS is a minimum of 8 bytes + 2 bytes for header
+ while ((EM [mPos] != 0x00) && (mPos < EM.Length))
+ mPos++;
+ if (EM [mPos] != 0x00)
+ return null;
+ mPos++;
+ byte[] M = new byte [EM.Length - mPos];
+ Buffer.BlockCopy (EM, mPos, M, 0, M.Length);
+ return M;
+ }
+
+ // PKCS #1 v.2.1, Section 8.2.1
+ // RSASSA-PKCS1-V1_5-SIGN (K, M)
+ public static byte[] Sign_v15 (RSA rsa, HashAlgorithm hash, byte[] hashValue)
+ {
+ int size = (rsa.KeySize >> 3); // div 8
+ byte[] EM = Encode_v15 (hash, hashValue, size);
+ byte[] m = OS2IP (EM);
+ byte[] s = RSASP1 (rsa, m);
+ byte[] S = I2OSP (s, size);
+ return S;
+ }
+
+ internal static byte[] Sign_v15 (RSA rsa, string hashName, byte[] hashValue)
+ {
+ using (var hash = CreateFromName (hashName))
+ return Sign_v15 (rsa, hash, hashValue);
+ }
+
+ // PKCS #1 v.2.1, Section 8.2.2
+ // RSASSA-PKCS1-V1_5-VERIFY ((n, e), M, S)
+ public static bool Verify_v15 (RSA rsa, HashAlgorithm hash, byte[] hashValue, byte[] signature)
+ {
+ return Verify_v15 (rsa, hash, hashValue, signature, false);
+ }
+
+ internal static bool Verify_v15 (RSA rsa, string hashName, byte[] hashValue, byte[] signature)
+ {
+ using (var hash = CreateFromName (hashName))
+ return Verify_v15 (rsa, hash, hashValue, signature, false);
+ }
+
+ // DO NOT USE WITHOUT A VERY GOOD REASON
+ public static bool Verify_v15 (RSA rsa, HashAlgorithm hash, byte [] hashValue, byte [] signature, bool tryNonStandardEncoding)
+ {
+ int size = (rsa.KeySize >> 3); // div 8
+ byte[] s = OS2IP (signature);
+ byte[] m = RSAVP1 (rsa, s);
+ byte[] EM2 = I2OSP (m, size);
+ byte[] EM = Encode_v15 (hash, hashValue, size);
+ bool result = Compare (EM, EM2);
+ if (result || !tryNonStandardEncoding)
+ return result;
+
+ // NOTE: some signatures don't include the hash OID (pretty lame but real)
+ // and compatible with MS implementation. E.g. Verisign Authenticode Timestamps
+
+ // we're making this "as safe as possible"
+ if ((EM2 [0] != 0x00) || (EM2 [1] != 0x01))
+ return false;
+ int i;
+ for (i = 2; i < EM2.Length - hashValue.Length - 1; i++) {
+ if (EM2 [i] != 0xFF)
+ return false;
+ }
+ if (EM2 [i++] != 0x00)
+ return false;
+
+ byte [] decryptedHash = new byte [hashValue.Length];
+ Buffer.BlockCopy (EM2, i, decryptedHash, 0, decryptedHash.Length);
+ return Compare (decryptedHash, hashValue);
+ }
+
+ // PKCS #1 v.2.1, Section 9.2
+ // EMSA-PKCS1-v1_5-Encode
+ public static byte[] Encode_v15 (HashAlgorithm hash, byte[] hashValue, int emLength)
+ {
+ if (hashValue.Length != (hash.HashSize >> 3))
+ throw new CryptographicException ("bad hash length for " + hash.ToString ());
+
+ // DigestInfo ::= SEQUENCE {
+ // digestAlgorithm AlgorithmIdentifier,
+ // digest OCTET STRING
+ // }
+
+ byte[] t = null;
+
+ string oid = CryptoConfig.MapNameToOID (hash.ToString ());
+ if (oid != null)
+ {
+ ASN1 digestAlgorithm = new ASN1 (0x30);
+ digestAlgorithm.Add (new ASN1 (CryptoConfig.EncodeOID (oid)));
+ digestAlgorithm.Add (new ASN1 (0x05)); // NULL
+ ASN1 digest = new ASN1 (0x04, hashValue);
+ ASN1 digestInfo = new ASN1 (0x30);
+ digestInfo.Add (digestAlgorithm);
+ digestInfo.Add (digest);
+
+ t = digestInfo.GetBytes ();
+ }
+ else
+ {
+ // There are no valid OID, in this case t = hashValue
+ // This is the case of the MD5SHA hash algorithm
+ t = hashValue;
+ }
+
+ Buffer.BlockCopy (hashValue, 0, t, t.Length - hashValue.Length, hashValue.Length);
+
+ int PSLength = System.Math.Max (8, emLength - t.Length - 3);
+ // PS = PSLength of 0xff
+
+ // EM = 0x00 | 0x01 | PS | 0x00 | T
+ byte[] EM = new byte [PSLength + t.Length + 3];
+ EM [1] = 0x01;
+ for (int i=2; i < PSLength + 2; i++)
+ EM[i] = 0xff;
+ Buffer.BlockCopy (t, 0, EM, PSLength + 3, t.Length);
+
+ return EM;
+ }
+
+ // PKCS #1 v.2.1, Section B.2.1
+ public static byte[] MGF1 (HashAlgorithm hash, byte[] mgfSeed, int maskLen)
+ {
+ // 1. If maskLen > 2^32 hLen, output "mask too long" and stop.
+ // easy - this is impossible by using a int (31bits) as parameter ;-)
+ // BUT with a signed int we do have to check for negative values!
+ if (maskLen < 0)
+ throw new OverflowException();
+
+ int mgfSeedLength = mgfSeed.Length;
+ int hLen = (hash.HashSize >> 3); // from bits to bytes
+ int iterations = (maskLen / hLen);
+ if (maskLen % hLen != 0)
+ iterations++;
+ // 2. Let T be the empty octet string.
+ byte[] T = new byte [iterations * hLen];
+
+ byte[] toBeHashed = new byte [mgfSeedLength + 4];
+ int pos = 0;
+ // 3. For counter from 0 to \ceil (maskLen / hLen) - 1, do the following:
+ for (int counter = 0; counter < iterations; counter++) {
+ // a. Convert counter to an octet string C of length 4 octets
+ byte[] C = I2OSP (counter, 4);
+
+ // b. Concatenate the hash of the seed mgfSeed and C to the octet string T:
+ // T = T || Hash (mgfSeed || C)
+ Buffer.BlockCopy (mgfSeed, 0, toBeHashed, 0, mgfSeedLength);
+ Buffer.BlockCopy (C, 0, toBeHashed, mgfSeedLength, 4);
+ byte[] output = hash.ComputeHash (toBeHashed);
+ Buffer.BlockCopy (output, 0, T, pos, hLen);
+ pos += hLen;
+ }
+
+ // 4. Output the leading maskLen octets of T as the octet string mask.
+ byte[] mask = new byte [maskLen];
+ Buffer.BlockCopy (T, 0, mask, 0, maskLen);
+ return mask;
+ }
+
+ static internal string HashNameFromOid (string oid, bool throwOnError = true)
+ {
+ switch (oid) {
+ case "1.2.840.113549.1.1.2": // MD2 with RSA encryption
+ return "MD2";
+ case "1.2.840.113549.1.1.3": // MD4 with RSA encryption
+ return "MD4";
+ case "1.2.840.113549.1.1.4": // MD5 with RSA encryption
+ return "MD5";
+ case "1.2.840.113549.1.1.5": // SHA-1 with RSA Encryption
+ case "1.3.14.3.2.29": // SHA1 with RSA signature
+ case "1.2.840.10040.4.3": // SHA1-1 with DSA
+ return "SHA1";
+ case "1.2.840.113549.1.1.11": // SHA-256 with RSA Encryption
+ return "SHA256";
+ case "1.2.840.113549.1.1.12": // SHA-384 with RSA Encryption
+ return "SHA384";
+ case "1.2.840.113549.1.1.13": // SHA-512 with RSA Encryption
+ return "SHA512";
+ case "1.3.36.3.3.1.2":
+ return "RIPEMD160";
+ default:
+ if (throwOnError)
+ throw new CryptographicException ("Unsupported hash algorithm: " + oid);
+ return null;
+ }
+ }
+
+ static internal HashAlgorithm CreateFromOid (string oid)
+ {
+ return CreateFromName (HashNameFromOid (oid));
+ }
+
+ static internal HashAlgorithm CreateFromName (string name)
+ {
+#if FULL_AOT_RUNTIME
+ switch (name) {
+ case "MD2":
+ return MD2.Create ();
+ case "MD4":
+ return MD4.Create ();
+ case "MD5":
+ return MD5.Create ();
+ case "SHA1":
+ return SHA1.Create ();
+ case "SHA256":
+ return SHA256.Create ();
+ case "SHA384":
+ return SHA384.Create ();
+ case "SHA512":
+ return SHA512.Create ();
+ case "RIPEMD160":
+ return RIPEMD160.Create ();
+ default:
+ try {
+ return (HashAlgorithm) Activator.CreateInstance (Type.GetType (name));
+ }
+ catch {
+ throw new CryptographicException ("Unsupported hash algorithm: " + name);
+ }
+ }
+#else
+ return HashAlgorithm.Create (name);
+#endif
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/PKCS12.cs b/Emby.Server.Implementations/Cryptography/PKCS12.cs
new file mode 100644
index 000000000..50f3776d9
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/PKCS12.cs
@@ -0,0 +1,1934 @@
+//
+// PKCS12.cs: PKCS 12 - Personal Information Exchange Syntax
+//
+// Author:
+// Sebastien Pouliot <sebastien@xamarin.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004,2005,2006 Novell Inc. (http://www.novell.com)
+// Copyright 2013 Xamarin Inc. (http://www.xamarin.com)
+//
+// Key derivation translated from Bouncy Castle JCE (http://www.bouncycastle.org/)
+// See bouncycastle.txt for license.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Emby.Server.Core.Cryptography
+{
+
+ public class PKCS5 {
+
+ public const string pbeWithMD2AndDESCBC = "1.2.840.113549.1.5.1";
+ public const string pbeWithMD5AndDESCBC = "1.2.840.113549.1.5.3";
+ public const string pbeWithMD2AndRC2CBC = "1.2.840.113549.1.5.4";
+ public const string pbeWithMD5AndRC2CBC = "1.2.840.113549.1.5.6";
+ public const string pbeWithSHA1AndDESCBC = "1.2.840.113549.1.5.10";
+ public const string pbeWithSHA1AndRC2CBC = "1.2.840.113549.1.5.11";
+
+ public PKCS5 () {}
+ }
+
+ public class PKCS9 {
+
+ public const string friendlyName = "1.2.840.113549.1.9.20";
+ public const string localKeyId = "1.2.840.113549.1.9.21";
+
+ public PKCS9 () {}
+ }
+
+
+ internal class SafeBag {
+ private string _bagOID;
+ private ASN1 _asn1;
+
+ public SafeBag(string bagOID, ASN1 asn1) {
+ _bagOID = bagOID;
+ _asn1 = asn1;
+ }
+
+ public string BagOID {
+ get { return _bagOID; }
+ }
+
+ public ASN1 ASN1 {
+ get { return _asn1; }
+ }
+ }
+
+
+ public class PKCS12 : ICloneable {
+
+ public const string pbeWithSHAAnd128BitRC4 = "1.2.840.113549.1.12.1.1";
+ public const string pbeWithSHAAnd40BitRC4 = "1.2.840.113549.1.12.1.2";
+ public const string pbeWithSHAAnd3KeyTripleDESCBC = "1.2.840.113549.1.12.1.3";
+ public const string pbeWithSHAAnd2KeyTripleDESCBC = "1.2.840.113549.1.12.1.4";
+ public const string pbeWithSHAAnd128BitRC2CBC = "1.2.840.113549.1.12.1.5";
+ public const string pbeWithSHAAnd40BitRC2CBC = "1.2.840.113549.1.12.1.6";
+
+ // bags
+ public const string keyBag = "1.2.840.113549.1.12.10.1.1";
+ public const string pkcs8ShroudedKeyBag = "1.2.840.113549.1.12.10.1.2";
+ public const string certBag = "1.2.840.113549.1.12.10.1.3";
+ public const string crlBag = "1.2.840.113549.1.12.10.1.4";
+ public const string secretBag = "1.2.840.113549.1.12.10.1.5";
+ public const string safeContentsBag = "1.2.840.113549.1.12.10.1.6";
+
+ // types
+ public const string x509Certificate = "1.2.840.113549.1.9.22.1";
+ public const string sdsiCertificate = "1.2.840.113549.1.9.22.2";
+ public const string x509Crl = "1.2.840.113549.1.9.23.1";
+
+ // Adapted from BouncyCastle PKCS12ParametersGenerator.java
+ public class DeriveBytes {
+
+ public enum Purpose {
+ Key,
+ IV,
+ MAC
+ }
+
+ static private byte[] keyDiversifier = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 };
+ static private byte[] ivDiversifier = { 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 };
+ static private byte[] macDiversifier = { 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3 };
+
+ private string _hashName;
+ private int _iterations;
+ private byte[] _password;
+ private byte[] _salt;
+
+ public DeriveBytes () {}
+
+ public string HashName {
+ get { return _hashName; }
+ set { _hashName = value; }
+ }
+
+ public int IterationCount {
+ get { return _iterations; }
+ set { _iterations = value; }
+ }
+
+ public byte[] Password {
+ get { return (byte[]) _password.Clone (); }
+ set {
+ if (value == null)
+ _password = new byte [0];
+ else
+ _password = (byte[]) value.Clone ();
+ }
+ }
+
+ public byte[] Salt {
+ get { return (byte[]) _salt.Clone (); }
+ set {
+ if (value != null)
+ _salt = (byte[]) value.Clone ();
+ else
+ _salt = null;
+ }
+ }
+
+ private void Adjust (byte[] a, int aOff, byte[] b)
+ {
+ int x = (b[b.Length - 1] & 0xff) + (a [aOff + b.Length - 1] & 0xff) + 1;
+
+ a [aOff + b.Length - 1] = (byte) x;
+ x >>= 8;
+
+ for (int i = b.Length - 2; i >= 0; i--) {
+ x += (b [i] & 0xff) + (a [aOff + i] & 0xff);
+ a [aOff + i] = (byte) x;
+ x >>= 8;
+ }
+ }
+
+ private byte[] Derive (byte[] diversifier, int n)
+ {
+ HashAlgorithm digest = PKCS1.CreateFromName (_hashName);
+ int u = (digest.HashSize >> 3); // div 8
+ int v = 64;
+ byte[] dKey = new byte [n];
+
+ byte[] S;
+ if ((_salt != null) && (_salt.Length != 0)) {
+ S = new byte[v * ((_salt.Length + v - 1) / v)];
+
+ for (int i = 0; i != S.Length; i++) {
+ S[i] = _salt[i % _salt.Length];
+ }
+ }
+ else {
+ S = new byte[0];
+ }
+
+ byte[] P;
+ if ((_password != null) && (_password.Length != 0)) {
+ P = new byte[v * ((_password.Length + v - 1) / v)];
+
+ for (int i = 0; i != P.Length; i++) {
+ P[i] = _password[i % _password.Length];
+ }
+ }
+ else {
+ P = new byte[0];
+ }
+
+ byte[] I = new byte [S.Length + P.Length];
+
+ Buffer.BlockCopy (S, 0, I, 0, S.Length);
+ Buffer.BlockCopy (P, 0, I, S.Length, P.Length);
+
+ byte[] B = new byte[v];
+ int c = (n + u - 1) / u;
+
+ for (int i = 1; i <= c; i++) {
+ digest.TransformBlock (diversifier, 0, diversifier.Length, diversifier, 0);
+ digest.TransformFinalBlock (I, 0, I.Length);
+ byte[] A = digest.Hash;
+ digest.Initialize ();
+ for (int j = 1; j != _iterations; j++) {
+ A = digest.ComputeHash (A, 0, A.Length);
+ }
+
+ for (int j = 0; j != B.Length; j++) {
+ B [j] = A [j % A.Length];
+ }
+
+ for (int j = 0; j != I.Length / v; j++) {
+ Adjust (I, j * v, B);
+ }
+
+ if (i == c) {
+ Buffer.BlockCopy(A, 0, dKey, (i - 1) * u, dKey.Length - ((i - 1) * u));
+ }
+ else {
+ Buffer.BlockCopy(A, 0, dKey, (i - 1) * u, A.Length);
+ }
+ }
+
+ return dKey;
+ }
+
+ public byte[] DeriveKey (int size)
+ {
+ return Derive (keyDiversifier, size);
+ }
+
+ public byte[] DeriveIV (int size)
+ {
+ return Derive (ivDiversifier, size);
+ }
+
+ public byte[] DeriveMAC (int size)
+ {
+ return Derive (macDiversifier, size);
+ }
+ }
+
+ const int recommendedIterationCount = 2000;
+
+ //private int _version;
+ private byte[] _password;
+ private ArrayList _keyBags;
+ private ArrayList _secretBags;
+ private X509CertificateCollection _certs;
+ private bool _keyBagsChanged;
+ private bool _secretBagsChanged;
+ private bool _certsChanged;
+ private int _iterations;
+ private ArrayList _safeBags;
+ private RandomNumberGenerator _rng;
+
+ // constructors
+
+ public PKCS12 ()
+ {
+ _iterations = recommendedIterationCount;
+ _keyBags = new ArrayList ();
+ _secretBags = new ArrayList ();
+ _certs = new X509CertificateCollection ();
+ _keyBagsChanged = false;
+ _secretBagsChanged = false;
+ _certsChanged = false;
+ _safeBags = new ArrayList ();
+ }
+
+ public PKCS12 (byte[] data)
+ : this ()
+ {
+ Password = null;
+ Decode (data);
+ }
+
+ /*
+ * PFX ::= SEQUENCE {
+ * version INTEGER {v3(3)}(v3,...),
+ * authSafe ContentInfo,
+ * macData MacData OPTIONAL
+ * }
+ *
+ * MacData ::= SEQUENCE {
+ * mac DigestInfo,
+ * macSalt OCTET STRING,
+ * iterations INTEGER DEFAULT 1
+ * -- Note: The default is for historical reasons and its use is deprecated. A higher
+ * -- value, like 1024 is recommended.
+ * }
+ *
+ * SafeContents ::= SEQUENCE OF SafeBag
+ *
+ * SafeBag ::= SEQUENCE {
+ * bagId BAG-TYPE.&id ({PKCS12BagSet}),
+ * bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}),
+ * bagAttributes SET OF PKCS12Attribute OPTIONAL
+ * }
+ */
+ public PKCS12 (byte[] data, string password)
+ : this ()
+ {
+ Password = password;
+ Decode (data);
+ }
+
+ public PKCS12 (byte[] data, byte[] password)
+ : this ()
+ {
+ _password = password;
+ Decode (data);
+ }
+
+ private void Decode (byte[] data)
+ {
+ ASN1 pfx = new ASN1 (data);
+ if (pfx.Tag != 0x30)
+ throw new ArgumentException ("invalid data");
+
+ ASN1 version = pfx [0];
+ if (version.Tag != 0x02)
+ throw new ArgumentException ("invalid PFX version");
+ //_version = version.Value [0];
+
+ PKCS7.ContentInfo authSafe = new PKCS7.ContentInfo (pfx [1]);
+ if (authSafe.ContentType != PKCS7.Oid.data)
+ throw new ArgumentException ("invalid authenticated safe");
+
+ // now that we know it's a PKCS#12 file, check the (optional) MAC
+ // before decoding anything else in the file
+ if (pfx.Count > 2) {
+ ASN1 macData = pfx [2];
+ if (macData.Tag != 0x30)
+ throw new ArgumentException ("invalid MAC");
+
+ ASN1 mac = macData [0];
+ if (mac.Tag != 0x30)
+ throw new ArgumentException ("invalid MAC");
+ ASN1 macAlgorithm = mac [0];
+ string macOid = ASN1Convert.ToOid (macAlgorithm [0]);
+ if (macOid != "1.3.14.3.2.26")
+ throw new ArgumentException ("unsupported HMAC");
+ byte[] macValue = mac [1].Value;
+
+ ASN1 macSalt = macData [1];
+ if (macSalt.Tag != 0x04)
+ throw new ArgumentException ("missing MAC salt");
+
+ _iterations = 1; // default value
+ if (macData.Count > 2) {
+ ASN1 iters = macData [2];
+ if (iters.Tag != 0x02)
+ throw new ArgumentException ("invalid MAC iteration");
+ _iterations = ASN1Convert.ToInt32 (iters);
+ }
+
+ byte[] authSafeData = authSafe.Content [0].Value;
+ byte[] calculatedMac = MAC (_password, macSalt.Value, _iterations, authSafeData);
+ if (!Compare (macValue, calculatedMac)) {
+ byte[] nullPassword = {0, 0};
+ calculatedMac = MAC(nullPassword, macSalt.Value, _iterations, authSafeData);
+ if (!Compare (macValue, calculatedMac))
+ throw new CryptographicException ("Invalid MAC - file may have been tampe red!");
+ _password = nullPassword;
+ }
+ }
+
+ // we now returns to our original presentation - PFX
+ ASN1 authenticatedSafe = new ASN1 (authSafe.Content [0].Value);
+ for (int i=0; i < authenticatedSafe.Count; i++) {
+ PKCS7.ContentInfo ci = new PKCS7.ContentInfo (authenticatedSafe [i]);
+ switch (ci.ContentType) {
+ case PKCS7.Oid.data:
+ // unencrypted (by PKCS#12)
+ ASN1 safeContents = new ASN1 (ci.Content [0].Value);
+ for (int j=0; j < safeContents.Count; j++) {
+ ASN1 safeBag = safeContents [j];
+ ReadSafeBag (safeBag);
+ }
+ break;
+ case PKCS7.Oid.encryptedData:
+ // password encrypted
+ PKCS7.EncryptedData ed = new PKCS7.EncryptedData (ci.Content [0]);
+ ASN1 decrypted = new ASN1 (Decrypt (ed));
+ for (int j=0; j < decrypted.Count; j++) {
+ ASN1 safeBag = decrypted [j];
+ ReadSafeBag (safeBag);
+ }
+ break;
+ case PKCS7.Oid.envelopedData:
+ // public key encrypted
+ throw new NotImplementedException ("public key encrypted");
+ default:
+ throw new ArgumentException ("unknown authenticatedSafe");
+ }
+ }
+ }
+
+ ~PKCS12 ()
+ {
+ if (_password != null) {
+ Array.Clear (_password, 0, _password.Length);
+ }
+ _password = null;
+ }
+
+ // properties
+
+ public string Password {
+ set {
+ // Clear old password.
+ if (_password != null)
+ Array.Clear (_password, 0, _password.Length);
+ _password = null;
+ if (value != null) {
+ if (value.Length > 0) {
+ int size = value.Length;
+ int nul = 0;
+ if (size < MaximumPasswordLength) {
+ // if not present, add space for a NULL (0x00) character
+ if (value[size - 1] != 0x00)
+ nul = 1;
+ } else {
+ size = MaximumPasswordLength;
+ }
+ _password = new byte[(size + nul) << 1]; // double for unicode
+ Encoding.BigEndianUnicode.GetBytes (value, 0, size, _password, 0);
+ } else {
+ // double-byte (Unicode) NULL (0x00) - see bug #79617
+ _password = new byte[2];
+ }
+ }
+ }
+ }
+
+ public int IterationCount {
+ get { return _iterations; }
+ set { _iterations = value; }
+ }
+
+ public ArrayList Keys {
+ get {
+ if (_keyBagsChanged) {
+ _keyBags.Clear ();
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (keyBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (bagValue.Value);
+ byte[] privateKey = pki.PrivateKey;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ _keyBags.Add (PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p));
+ break;
+ case 0x30:
+ _keyBags.Add (PKCS8.PrivateKeyInfo.DecodeRSA (privateKey));
+ break;
+ default:
+ break;
+ }
+ Array.Clear (privateKey, 0, privateKey.Length);
+
+ } else if (sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+ byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (decrypted);
+ byte[] privateKey = pki.PrivateKey;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ _keyBags.Add (PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p));
+ break;
+ case 0x30:
+ _keyBags.Add (PKCS8.PrivateKeyInfo.DecodeRSA (privateKey));
+ break;
+ default:
+ break;
+ }
+ Array.Clear (privateKey, 0, privateKey.Length);
+ Array.Clear (decrypted, 0, decrypted.Length);
+ }
+ }
+ _keyBagsChanged = false;
+ }
+ return ArrayList.ReadOnly(_keyBags);
+ }
+ }
+
+ public ArrayList Secrets {
+ get {
+ if (_secretBagsChanged) {
+ _secretBags.Clear ();
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (secretBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ byte[] secret = bagValue.Value;
+ _secretBags.Add(secret);
+ }
+ }
+ _secretBagsChanged = false;
+ }
+ return ArrayList.ReadOnly(_secretBags);
+ }
+ }
+
+ public X509CertificateCollection Certificates {
+ get {
+ if (_certsChanged) {
+ _certs.Clear ();
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (certBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ PKCS7.ContentInfo cert = new PKCS7.ContentInfo (bagValue.Value);
+ _certs.Add (new X509Certificate (cert.Content [0].Value));
+ }
+ }
+ _certsChanged = false;
+ }
+ return _certs;
+ }
+ }
+
+ internal RandomNumberGenerator RNG {
+ get {
+ if (_rng == null)
+ _rng = RandomNumberGenerator.Create ();
+ return _rng;
+ }
+ }
+
+ // private methods
+
+ private bool Compare (byte[] expected, byte[] actual)
+ {
+ bool compare = false;
+ if (expected.Length == actual.Length) {
+ for (int i=0; i < expected.Length; i++) {
+ if (expected [i] != actual [i])
+ return false;
+ }
+ compare = true;
+ }
+ return compare;
+ }
+
+ private SymmetricAlgorithm GetSymmetricAlgorithm (string algorithmOid, byte[] salt, int iterationCount)
+ {
+ string algorithm = null;
+ int keyLength = 8; // 64 bits (default)
+ int ivLength = 8; // 64 bits (default)
+
+ PKCS12.DeriveBytes pd = new PKCS12.DeriveBytes ();
+ pd.Password = _password;
+ pd.Salt = salt;
+ pd.IterationCount = iterationCount;
+
+ switch (algorithmOid) {
+ case PKCS5.pbeWithMD2AndDESCBC: // no unit test available
+ pd.HashName = "MD2";
+ algorithm = "DES";
+ break;
+ case PKCS5.pbeWithMD5AndDESCBC: // no unit test available
+ pd.HashName = "MD5";
+ algorithm = "DES";
+ break;
+ case PKCS5.pbeWithMD2AndRC2CBC: // no unit test available
+ // TODO - RC2-CBC-Parameter (PKCS5)
+ // if missing default to 32 bits !!!
+ pd.HashName = "MD2";
+ algorithm = "RC2";
+ keyLength = 4; // default
+ break;
+ case PKCS5.pbeWithMD5AndRC2CBC: // no unit test available
+ // TODO - RC2-CBC-Parameter (PKCS5)
+ // if missing default to 32 bits !!!
+ pd.HashName = "MD5";
+ algorithm = "RC2";
+ keyLength = 4; // default
+ break;
+ case PKCS5.pbeWithSHA1AndDESCBC: // no unit test available
+ pd.HashName = "SHA1";
+ algorithm = "DES";
+ break;
+ case PKCS5.pbeWithSHA1AndRC2CBC: // no unit test available
+ // TODO - RC2-CBC-Parameter (PKCS5)
+ // if missing default to 32 bits !!!
+ pd.HashName = "SHA1";
+ algorithm = "RC2";
+ keyLength = 4; // default
+ break;
+ case PKCS12.pbeWithSHAAnd128BitRC4: // no unit test available
+ pd.HashName = "SHA1";
+ algorithm = "RC4";
+ keyLength = 16;
+ ivLength = 0; // N/A
+ break;
+ case PKCS12.pbeWithSHAAnd40BitRC4: // no unit test available
+ pd.HashName = "SHA1";
+ algorithm = "RC4";
+ keyLength = 5;
+ ivLength = 0; // N/A
+ break;
+ case PKCS12.pbeWithSHAAnd3KeyTripleDESCBC:
+ pd.HashName = "SHA1";
+ algorithm = "TripleDES";
+ keyLength = 24;
+ break;
+ case PKCS12.pbeWithSHAAnd2KeyTripleDESCBC: // no unit test available
+ pd.HashName = "SHA1";
+ algorithm = "TripleDES";
+ keyLength = 16;
+ break;
+ case PKCS12.pbeWithSHAAnd128BitRC2CBC: // no unit test available
+ pd.HashName = "SHA1";
+ algorithm = "RC2";
+ keyLength = 16;
+ break;
+ case PKCS12.pbeWithSHAAnd40BitRC2CBC:
+ pd.HashName = "SHA1";
+ algorithm = "RC2";
+ keyLength = 5;
+ break;
+ default:
+ throw new NotSupportedException ("unknown oid " + algorithm);
+ }
+
+ SymmetricAlgorithm sa = null;
+ sa = SymmetricAlgorithm.Create(algorithm);
+ sa.Key = pd.DeriveKey (keyLength);
+ // IV required only for block ciphers (not stream ciphers)
+ if (ivLength > 0) {
+ sa.IV = pd.DeriveIV (ivLength);
+ sa.Mode = CipherMode.CBC;
+ }
+ return sa;
+ }
+
+ public byte[] Decrypt (string algorithmOid, byte[] salt, int iterationCount, byte[] encryptedData)
+ {
+ SymmetricAlgorithm sa = null;
+ byte[] result = null;
+ try {
+ sa = GetSymmetricAlgorithm (algorithmOid, salt, iterationCount);
+ ICryptoTransform ct = sa.CreateDecryptor ();
+ result = ct.TransformFinalBlock (encryptedData, 0, encryptedData.Length);
+ }
+ finally {
+ if (sa != null)
+ sa.Clear ();
+ }
+ return result;
+ }
+
+ public byte[] Decrypt (PKCS7.EncryptedData ed)
+ {
+ return Decrypt (ed.EncryptionAlgorithm.ContentType,
+ ed.EncryptionAlgorithm.Content [0].Value,
+ ASN1Convert.ToInt32 (ed.EncryptionAlgorithm.Content [1]),
+ ed.EncryptedContent);
+ }
+
+ public byte[] Encrypt (string algorithmOid, byte[] salt, int iterationCount, byte[] data)
+ {
+ byte[] result = null;
+ using (SymmetricAlgorithm sa = GetSymmetricAlgorithm (algorithmOid, salt, iterationCount)) {
+ ICryptoTransform ct = sa.CreateEncryptor ();
+ result = ct.TransformFinalBlock (data, 0, data.Length);
+ }
+ return result;
+ }
+
+ private DSAParameters GetExistingParameters (out bool found)
+ {
+ foreach (X509Certificate cert in Certificates) {
+ // FIXME: that won't work if parts of the parameters are missing
+ if (cert.KeyAlgorithmParameters != null) {
+ DSA dsa = cert.DSA;
+ if (dsa != null) {
+ found = true;
+ return dsa.ExportParameters (false);
+ }
+ }
+ }
+ found = false;
+ return new DSAParameters ();
+ }
+
+ private void AddPrivateKey (PKCS8.PrivateKeyInfo pki)
+ {
+ byte[] privateKey = pki.PrivateKey;
+ switch (privateKey [0]) {
+ case 0x02:
+ bool found;
+ DSAParameters p = GetExistingParameters (out found);
+ if (found) {
+ _keyBags.Add (PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p));
+ }
+ break;
+ case 0x30:
+ _keyBags.Add (PKCS8.PrivateKeyInfo.DecodeRSA (privateKey));
+ break;
+ default:
+ Array.Clear (privateKey, 0, privateKey.Length);
+ throw new CryptographicException ("Unknown private key format");
+ }
+ Array.Clear (privateKey, 0, privateKey.Length);
+ }
+
+ private void ReadSafeBag (ASN1 safeBag)
+ {
+ if (safeBag.Tag != 0x30)
+ throw new ArgumentException ("invalid safeBag");
+
+ ASN1 bagId = safeBag [0];
+ if (bagId.Tag != 0x06)
+ throw new ArgumentException ("invalid safeBag id");
+
+ ASN1 bagValue = safeBag [1];
+ string oid = ASN1Convert.ToOid (bagId);
+ switch (oid) {
+ case keyBag:
+ // NEED UNIT TEST
+ AddPrivateKey (new PKCS8.PrivateKeyInfo (bagValue.Value));
+ break;
+ case pkcs8ShroudedKeyBag:
+ PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+ byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+ AddPrivateKey (new PKCS8.PrivateKeyInfo (decrypted));
+ Array.Clear (decrypted, 0, decrypted.Length);
+ break;
+ case certBag:
+ PKCS7.ContentInfo cert = new PKCS7.ContentInfo (bagValue.Value);
+ if (cert.ContentType != x509Certificate)
+ throw new NotSupportedException ("unsupport certificate type");
+ X509Certificate x509 = new X509Certificate (cert.Content [0].Value);
+ _certs.Add (x509);
+ break;
+ case crlBag:
+ // TODO
+ break;
+ case secretBag:
+ byte[] secret = bagValue.Value;
+ _secretBags.Add(secret);
+ break;
+ case safeContentsBag:
+ // TODO - ? recurse ?
+ break;
+ default:
+ throw new ArgumentException ("unknown safeBag oid");
+ }
+
+ if (safeBag.Count > 2) {
+ ASN1 bagAttributes = safeBag [2];
+ if (bagAttributes.Tag != 0x31)
+ throw new ArgumentException ("invalid safeBag attributes id");
+
+ for (int i = 0; i < bagAttributes.Count; i++) {
+ ASN1 pkcs12Attribute = bagAttributes[i];
+
+ if (pkcs12Attribute.Tag != 0x30)
+ throw new ArgumentException ("invalid PKCS12 attributes id");
+
+ ASN1 attrId = pkcs12Attribute [0];
+ if (attrId.Tag != 0x06)
+ throw new ArgumentException ("invalid attribute id");
+
+ string attrOid = ASN1Convert.ToOid (attrId);
+
+ ASN1 attrValues = pkcs12Attribute[1];
+ for (int j = 0; j < attrValues.Count; j++) {
+ ASN1 attrValue = attrValues[j];
+
+ switch (attrOid) {
+ case PKCS9.friendlyName:
+ if (attrValue.Tag != 0x1e)
+ throw new ArgumentException ("invalid attribute value id");
+ break;
+ case PKCS9.localKeyId:
+ if (attrValue.Tag != 0x04)
+ throw new ArgumentException ("invalid attribute value id");
+ break;
+ default:
+ // Unknown OID -- don't check Tag
+ break;
+ }
+ }
+ }
+ }
+
+ _safeBags.Add (new SafeBag(oid, safeBag));
+ }
+
+ private ASN1 Pkcs8ShroudedKeyBagSafeBag (AsymmetricAlgorithm aa, IDictionary attributes)
+ {
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo ();
+ if (aa is RSA) {
+ pki.Algorithm = "1.2.840.113549.1.1.1";
+ pki.PrivateKey = PKCS8.PrivateKeyInfo.Encode ((RSA)aa);
+ }
+ else if (aa is DSA) {
+ pki.Algorithm = null;
+ pki.PrivateKey = PKCS8.PrivateKeyInfo.Encode ((DSA)aa);
+ }
+ else
+ throw new CryptographicException ("Unknown asymmetric algorithm {0}", aa.ToString ());
+
+ PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo ();
+ epki.Algorithm = pbeWithSHAAnd3KeyTripleDESCBC;
+ epki.IterationCount = _iterations;
+ epki.EncryptedData = Encrypt (pbeWithSHAAnd3KeyTripleDESCBC, epki.Salt, _iterations, pki.GetBytes ());
+
+ ASN1 safeBag = new ASN1 (0x30);
+ safeBag.Add (ASN1Convert.FromOid (pkcs8ShroudedKeyBag));
+ ASN1 bagValue = new ASN1 (0xA0);
+ bagValue.Add (new ASN1 (epki.GetBytes ()));
+ safeBag.Add (bagValue);
+
+ if (attributes != null) {
+ ASN1 bagAttributes = new ASN1 (0x31);
+ IDictionaryEnumerator de = attributes.GetEnumerator ();
+
+ while (de.MoveNext ()) {
+ string oid = (string)de.Key;
+ switch (oid) {
+ case PKCS9.friendlyName:
+ ArrayList names = (ArrayList)de.Value;
+ if (names.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.friendlyName));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] name in names) {
+ ASN1 attrValue = new ASN1 (0x1e);
+ attrValue.Value = name;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ case PKCS9.localKeyId:
+ ArrayList keys = (ArrayList)de.Value;
+ if (keys.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.localKeyId));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] key in keys) {
+ ASN1 attrValue = new ASN1 (0x04);
+ attrValue.Value = key;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (bagAttributes.Count > 0) {
+ safeBag.Add (bagAttributes);
+ }
+ }
+
+ return safeBag;
+ }
+
+ private ASN1 KeyBagSafeBag (AsymmetricAlgorithm aa, IDictionary attributes)
+ {
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo ();
+ if (aa is RSA) {
+ pki.Algorithm = "1.2.840.113549.1.1.1";
+ pki.PrivateKey = PKCS8.PrivateKeyInfo.Encode ((RSA)aa);
+ }
+ else if (aa is DSA) {
+ pki.Algorithm = null;
+ pki.PrivateKey = PKCS8.PrivateKeyInfo.Encode ((DSA)aa);
+ }
+ else
+ throw new CryptographicException ("Unknown asymmetric algorithm {0}", aa.ToString ());
+
+ ASN1 safeBag = new ASN1 (0x30);
+ safeBag.Add (ASN1Convert.FromOid (keyBag));
+ ASN1 bagValue = new ASN1 (0xA0);
+ bagValue.Add (new ASN1 (pki.GetBytes ()));
+ safeBag.Add (bagValue);
+
+ if (attributes != null) {
+ ASN1 bagAttributes = new ASN1 (0x31);
+ IDictionaryEnumerator de = attributes.GetEnumerator ();
+
+ while (de.MoveNext ()) {
+ string oid = (string)de.Key;
+ switch (oid) {
+ case PKCS9.friendlyName:
+ ArrayList names = (ArrayList)de.Value;
+ if (names.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.friendlyName));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] name in names) {
+ ASN1 attrValue = new ASN1 (0x1e);
+ attrValue.Value = name;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ case PKCS9.localKeyId:
+ ArrayList keys = (ArrayList)de.Value;
+ if (keys.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.localKeyId));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] key in keys) {
+ ASN1 attrValue = new ASN1 (0x04);
+ attrValue.Value = key;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (bagAttributes.Count > 0) {
+ safeBag.Add (bagAttributes);
+ }
+ }
+
+ return safeBag;
+ }
+
+ private ASN1 SecretBagSafeBag (byte[] secret, IDictionary attributes)
+ {
+ ASN1 safeBag = new ASN1 (0x30);
+ safeBag.Add (ASN1Convert.FromOid (secretBag));
+ ASN1 bagValue = new ASN1 (0x80, secret);
+ safeBag.Add (bagValue);
+
+ if (attributes != null) {
+ ASN1 bagAttributes = new ASN1 (0x31);
+ IDictionaryEnumerator de = attributes.GetEnumerator ();
+
+ while (de.MoveNext ()) {
+ string oid = (string)de.Key;
+ switch (oid) {
+ case PKCS9.friendlyName:
+ ArrayList names = (ArrayList)de.Value;
+ if (names.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.friendlyName));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] name in names) {
+ ASN1 attrValue = new ASN1 (0x1e);
+ attrValue.Value = name;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ case PKCS9.localKeyId:
+ ArrayList keys = (ArrayList)de.Value;
+ if (keys.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.localKeyId));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] key in keys) {
+ ASN1 attrValue = new ASN1 (0x04);
+ attrValue.Value = key;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (bagAttributes.Count > 0) {
+ safeBag.Add (bagAttributes);
+ }
+ }
+
+ return safeBag;
+ }
+
+ private ASN1 CertificateSafeBag (X509Certificate x509, IDictionary attributes)
+ {
+ ASN1 encapsulatedCertificate = new ASN1 (0x04, x509.RawData);
+
+ PKCS7.ContentInfo ci = new PKCS7.ContentInfo ();
+ ci.ContentType = x509Certificate;
+ ci.Content.Add (encapsulatedCertificate);
+
+ ASN1 bagValue = new ASN1 (0xA0);
+ bagValue.Add (ci.ASN1);
+
+ ASN1 safeBag = new ASN1 (0x30);
+ safeBag.Add (ASN1Convert.FromOid (certBag));
+ safeBag.Add (bagValue);
+
+ if (attributes != null) {
+ ASN1 bagAttributes = new ASN1 (0x31);
+ IDictionaryEnumerator de = attributes.GetEnumerator ();
+
+ while (de.MoveNext ()) {
+ string oid = (string)de.Key;
+ switch (oid) {
+ case PKCS9.friendlyName:
+ ArrayList names = (ArrayList)de.Value;
+ if (names.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.friendlyName));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] name in names) {
+ ASN1 attrValue = new ASN1 (0x1e);
+ attrValue.Value = name;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ case PKCS9.localKeyId:
+ ArrayList keys = (ArrayList)de.Value;
+ if (keys.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.localKeyId));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] key in keys) {
+ ASN1 attrValue = new ASN1 (0x04);
+ attrValue.Value = key;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (bagAttributes.Count > 0) {
+ safeBag.Add (bagAttributes);
+ }
+ }
+
+ return safeBag;
+ }
+
+ private byte[] MAC (byte[] password, byte[] salt, int iterations, byte[] data)
+ {
+ PKCS12.DeriveBytes pd = new PKCS12.DeriveBytes ();
+ pd.HashName = "SHA1";
+ pd.Password = password;
+ pd.Salt = salt;
+ pd.IterationCount = iterations;
+
+ HMACSHA1 hmac = (HMACSHA1) HMACSHA1.Create ();
+ hmac.Key = pd.DeriveMAC (20);
+ return hmac.ComputeHash (data, 0, data.Length);
+ }
+
+ /*
+ * SafeContents ::= SEQUENCE OF SafeBag
+ *
+ * SafeBag ::= SEQUENCE {
+ * bagId BAG-TYPE.&id ({PKCS12BagSet}),
+ * bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}),
+ * bagAttributes SET OF PKCS12Attribute OPTIONAL
+ * }
+ */
+ public byte[] GetBytes ()
+ {
+ // TODO (incomplete)
+ ASN1 safeBagSequence = new ASN1 (0x30);
+
+ // Sync Safe Bag list since X509CertificateCollection may be updated
+ ArrayList scs = new ArrayList ();
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (certBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ PKCS7.ContentInfo cert = new PKCS7.ContentInfo (bagValue.Value);
+ scs.Add (new X509Certificate (cert.Content [0].Value));
+ }
+ }
+
+ ArrayList addcerts = new ArrayList ();
+ ArrayList removecerts = new ArrayList ();
+
+ foreach (X509Certificate c in Certificates) {
+ bool found = false;
+
+ foreach (X509Certificate lc in scs) {
+ if (Compare (c.RawData, lc.RawData)) {
+ found = true;
+ }
+ }
+
+ if (!found) {
+ addcerts.Add (c);
+ }
+ }
+ foreach (X509Certificate c in scs) {
+ bool found = false;
+
+ foreach (X509Certificate lc in Certificates) {
+ if (Compare (c.RawData, lc.RawData)) {
+ found = true;
+ }
+ }
+
+ if (!found) {
+ removecerts.Add (c);
+ }
+ }
+
+ foreach (X509Certificate c in removecerts) {
+ RemoveCertificate (c);
+ }
+
+ foreach (X509Certificate c in addcerts) {
+ AddCertificate (c);
+ }
+ // Sync done
+
+ if (_safeBags.Count > 0) {
+ ASN1 certsSafeBag = new ASN1 (0x30);
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (certBag)) {
+ certsSafeBag.Add (sb.ASN1);
+ }
+ }
+
+ if (certsSafeBag.Count > 0) {
+ PKCS7.ContentInfo contentInfo = EncryptedContentInfo (certsSafeBag, pbeWithSHAAnd3KeyTripleDESCBC);
+ safeBagSequence.Add (contentInfo.ASN1);
+ }
+ }
+
+ if (_safeBags.Count > 0) {
+ ASN1 safeContents = new ASN1 (0x30);
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (keyBag) ||
+ sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+ safeContents.Add (sb.ASN1);
+ }
+ }
+ if (safeContents.Count > 0) {
+ ASN1 content = new ASN1 (0xA0);
+ content.Add (new ASN1 (0x04, safeContents.GetBytes ()));
+
+ PKCS7.ContentInfo keyBag = new PKCS7.ContentInfo (PKCS7.Oid.data);
+ keyBag.Content = content;
+ safeBagSequence.Add (keyBag.ASN1);
+ }
+ }
+
+ // Doing SecretBags separately in case we want to change their encryption independently.
+ if (_safeBags.Count > 0) {
+ ASN1 secretsSafeBag = new ASN1 (0x30);
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (secretBag)) {
+ secretsSafeBag.Add (sb.ASN1);
+ }
+ }
+
+ if (secretsSafeBag.Count > 0) {
+ PKCS7.ContentInfo contentInfo = EncryptedContentInfo (secretsSafeBag, pbeWithSHAAnd3KeyTripleDESCBC);
+ safeBagSequence.Add (contentInfo.ASN1);
+ }
+ }
+
+
+ ASN1 encapsulates = new ASN1 (0x04, safeBagSequence.GetBytes ());
+ ASN1 ci = new ASN1 (0xA0);
+ ci.Add (encapsulates);
+ PKCS7.ContentInfo authSafe = new PKCS7.ContentInfo (PKCS7.Oid.data);
+ authSafe.Content = ci;
+
+ ASN1 macData = new ASN1 (0x30);
+ if (_password != null) {
+ // only for password based encryption
+ byte[] salt = new byte [20];
+ RNG.GetBytes (salt);
+ byte[] macValue = MAC (_password, salt, _iterations, authSafe.Content [0].Value);
+ ASN1 oidSeq = new ASN1 (0x30);
+ oidSeq.Add (ASN1Convert.FromOid ("1.3.14.3.2.26")); // SHA1
+ oidSeq.Add (new ASN1 (0x05));
+
+ ASN1 mac = new ASN1 (0x30);
+ mac.Add (oidSeq);
+ mac.Add (new ASN1 (0x04, macValue));
+
+ macData.Add (mac);
+ macData.Add (new ASN1 (0x04, salt));
+ macData.Add (ASN1Convert.FromInt32 (_iterations));
+ }
+
+ ASN1 version = new ASN1 (0x02, new byte [1] { 0x03 });
+
+ ASN1 pfx = new ASN1 (0x30);
+ pfx.Add (version);
+ pfx.Add (authSafe.ASN1);
+ if (macData.Count > 0) {
+ // only for password based encryption
+ pfx.Add (macData);
+ }
+
+ return pfx.GetBytes ();
+ }
+
+ // Creates an encrypted PKCS#7 ContentInfo with safeBags as its SafeContents. Used in GetBytes(), above.
+ private PKCS7.ContentInfo EncryptedContentInfo(ASN1 safeBags, string algorithmOid)
+ {
+ byte[] salt = new byte [8];
+ RNG.GetBytes (salt);
+
+ ASN1 seqParams = new ASN1 (0x30);
+ seqParams.Add (new ASN1 (0x04, salt));
+ seqParams.Add (ASN1Convert.FromInt32 (_iterations));
+
+ ASN1 seqPbe = new ASN1 (0x30);
+ seqPbe.Add (ASN1Convert.FromOid (algorithmOid));
+ seqPbe.Add (seqParams);
+
+ byte[] encrypted = Encrypt (algorithmOid, salt, _iterations, safeBags.GetBytes ());
+ ASN1 encryptedContent = new ASN1 (0x80, encrypted);
+
+ ASN1 seq = new ASN1 (0x30);
+ seq.Add (ASN1Convert.FromOid (PKCS7.Oid.data));
+ seq.Add (seqPbe);
+ seq.Add (encryptedContent);
+
+ ASN1 version = new ASN1 (0x02, new byte [1] { 0x00 });
+ ASN1 encData = new ASN1 (0x30);
+ encData.Add (version);
+ encData.Add (seq);
+
+ ASN1 finalContent = new ASN1 (0xA0);
+ finalContent.Add (encData);
+
+ PKCS7.ContentInfo bag = new PKCS7.ContentInfo (PKCS7.Oid.encryptedData);
+ bag.Content = finalContent;
+ return bag;
+ }
+
+ public void AddCertificate (X509Certificate cert)
+ {
+ AddCertificate (cert, null);
+ }
+
+ public void AddCertificate (X509Certificate cert, IDictionary attributes)
+ {
+ bool found = false;
+
+ for (int i = 0; !found && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (certBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ PKCS7.ContentInfo crt = new PKCS7.ContentInfo (bagValue.Value);
+ X509Certificate c = new X509Certificate (crt.Content [0].Value);
+ if (Compare (cert.RawData, c.RawData)) {
+ found = true;
+ }
+ }
+ }
+
+ if (!found) {
+ _safeBags.Add (new SafeBag (certBag, CertificateSafeBag (cert, attributes)));
+ _certsChanged = true;
+ }
+ }
+
+ public void RemoveCertificate (X509Certificate cert)
+ {
+ RemoveCertificate (cert, null);
+ }
+
+ public void RemoveCertificate (X509Certificate cert, IDictionary attrs)
+ {
+ int certIndex = -1;
+
+ for (int i = 0; certIndex == -1 && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (certBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ PKCS7.ContentInfo crt = new PKCS7.ContentInfo (bagValue.Value);
+ X509Certificate c = new X509Certificate (crt.Content [0].Value);
+ if (Compare (cert.RawData, c.RawData)) {
+ if (attrs != null) {
+ if (safeBag.Count == 3) {
+ ASN1 bagAttributes = safeBag [2];
+ int bagAttributesFound = 0;
+ for (int j = 0; j < bagAttributes.Count; j++) {
+ ASN1 pkcs12Attribute = bagAttributes [j];
+ ASN1 attrId = pkcs12Attribute [0];
+ string ao = ASN1Convert.ToOid (attrId);
+ ArrayList dattrValues = (ArrayList)attrs [ao];
+
+ if (dattrValues != null) {
+ ASN1 attrValues = pkcs12Attribute [1];
+
+ if (dattrValues.Count == attrValues.Count) {
+ int attrValuesFound = 0;
+ for (int k = 0; k < attrValues.Count; k++) {
+ ASN1 attrValue = attrValues [k];
+ byte[] value = (byte[])dattrValues [k];
+
+ if (Compare (value, attrValue.Value)) {
+ attrValuesFound += 1;
+ }
+ }
+ if (attrValuesFound == attrValues.Count) {
+ bagAttributesFound += 1;
+ }
+ }
+ }
+ }
+ if (bagAttributesFound == bagAttributes.Count) {
+ certIndex = i;
+ }
+ }
+ } else {
+ certIndex = i;
+ }
+ }
+ }
+ }
+
+ if (certIndex != -1) {
+ _safeBags.RemoveAt (certIndex);
+ _certsChanged = true;
+ }
+ }
+
+ private bool CompareAsymmetricAlgorithm (AsymmetricAlgorithm a1, AsymmetricAlgorithm a2)
+ {
+ // fast path
+ if (a1.KeySize != a2.KeySize)
+ return false;
+ // compare public keys - if they match we can assume the private match too
+ return (a1.ToXmlString (false) == a2.ToXmlString (false));
+ }
+
+ public void AddPkcs8ShroudedKeyBag (AsymmetricAlgorithm aa)
+ {
+ AddPkcs8ShroudedKeyBag (aa, null);
+ }
+
+ public void AddPkcs8ShroudedKeyBag (AsymmetricAlgorithm aa, IDictionary attributes)
+ {
+ bool found = false;
+
+ for (int i = 0; !found && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+ ASN1 bagValue = sb.ASN1 [1];
+ PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+ byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (decrypted);
+ byte[] privateKey = pki.PrivateKey;
+
+ AsymmetricAlgorithm saa = null;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+ break;
+ case 0x30:
+ saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+ break;
+ default:
+ Array.Clear (decrypted, 0, decrypted.Length);
+ Array.Clear (privateKey, 0, privateKey.Length);
+ throw new CryptographicException ("Unknown private key format");
+ }
+
+ Array.Clear (decrypted, 0, decrypted.Length);
+ Array.Clear (privateKey, 0, privateKey.Length);
+
+ if (CompareAsymmetricAlgorithm (aa , saa)) {
+ found = true;
+ }
+ }
+ }
+
+ if (!found) {
+ _safeBags.Add (new SafeBag (pkcs8ShroudedKeyBag, Pkcs8ShroudedKeyBagSafeBag (aa, attributes)));
+ _keyBagsChanged = true;
+ }
+ }
+
+ public void RemovePkcs8ShroudedKeyBag (AsymmetricAlgorithm aa)
+ {
+ int aaIndex = -1;
+
+ for (int i = 0; aaIndex == -1 && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+ ASN1 bagValue = sb.ASN1 [1];
+ PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+ byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (decrypted);
+ byte[] privateKey = pki.PrivateKey;
+
+ AsymmetricAlgorithm saa = null;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+ break;
+ case 0x30:
+ saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+ break;
+ default:
+ Array.Clear (decrypted, 0, decrypted.Length);
+ Array.Clear (privateKey, 0, privateKey.Length);
+ throw new CryptographicException ("Unknown private key format");
+ }
+
+ Array.Clear (decrypted, 0, decrypted.Length);
+ Array.Clear (privateKey, 0, privateKey.Length);
+
+ if (CompareAsymmetricAlgorithm (aa, saa)) {
+ aaIndex = i;
+ }
+ }
+ }
+
+ if (aaIndex != -1) {
+ _safeBags.RemoveAt (aaIndex);
+ _keyBagsChanged = true;
+ }
+ }
+
+ public void AddKeyBag (AsymmetricAlgorithm aa)
+ {
+ AddKeyBag (aa, null);
+ }
+
+ public void AddKeyBag (AsymmetricAlgorithm aa, IDictionary attributes)
+ {
+ bool found = false;
+
+ for (int i = 0; !found && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (keyBag)) {
+ ASN1 bagValue = sb.ASN1 [1];
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (bagValue.Value);
+ byte[] privateKey = pki.PrivateKey;
+
+ AsymmetricAlgorithm saa = null;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+ break;
+ case 0x30:
+ saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+ break;
+ default:
+ Array.Clear (privateKey, 0, privateKey.Length);
+ throw new CryptographicException ("Unknown private key format");
+ }
+
+ Array.Clear (privateKey, 0, privateKey.Length);
+
+ if (CompareAsymmetricAlgorithm (aa, saa)) {
+ found = true;
+ }
+ }
+ }
+
+ if (!found) {
+ _safeBags.Add (new SafeBag (keyBag, KeyBagSafeBag (aa, attributes)));
+ _keyBagsChanged = true;
+ }
+ }
+
+ public void RemoveKeyBag (AsymmetricAlgorithm aa)
+ {
+ int aaIndex = -1;
+
+ for (int i = 0; aaIndex == -1 && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (keyBag)) {
+ ASN1 bagValue = sb.ASN1 [1];
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (bagValue.Value);
+ byte[] privateKey = pki.PrivateKey;
+
+ AsymmetricAlgorithm saa = null;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+ break;
+ case 0x30:
+ saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+ break;
+ default:
+ Array.Clear (privateKey, 0, privateKey.Length);
+ throw new CryptographicException ("Unknown private key format");
+ }
+
+ Array.Clear (privateKey, 0, privateKey.Length);
+
+ if (CompareAsymmetricAlgorithm (aa, saa)) {
+ aaIndex = i;
+ }
+ }
+ }
+
+ if (aaIndex != -1) {
+ _safeBags.RemoveAt (aaIndex);
+ _keyBagsChanged = true;
+ }
+ }
+
+ public void AddSecretBag (byte[] secret)
+ {
+ AddSecretBag (secret, null);
+ }
+
+ public void AddSecretBag (byte[] secret, IDictionary attributes)
+ {
+ bool found = false;
+
+ for (int i = 0; !found && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (secretBag)) {
+ ASN1 bagValue = sb.ASN1 [1];
+ byte[] ssecret = bagValue.Value;
+
+ if (Compare (secret, ssecret)) {
+ found = true;
+ }
+ }
+ }
+
+ if (!found) {
+ _safeBags.Add (new SafeBag (secretBag, SecretBagSafeBag (secret, attributes)));
+ _secretBagsChanged = true;
+ }
+ }
+
+ public void RemoveSecretBag (byte[] secret)
+ {
+ int sIndex = -1;
+
+ for (int i = 0; sIndex == -1 && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (secretBag)) {
+ ASN1 bagValue = sb.ASN1 [1];
+ byte[] ssecret = bagValue.Value;
+
+ if (Compare (secret, ssecret)) {
+ sIndex = i;
+ }
+ }
+ }
+
+ if (sIndex != -1) {
+ _safeBags.RemoveAt (sIndex);
+ _secretBagsChanged = true;
+ }
+ }
+
+ public AsymmetricAlgorithm GetAsymmetricAlgorithm (IDictionary attrs)
+ {
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (keyBag) || sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+ ASN1 safeBag = sb.ASN1;
+
+ if (safeBag.Count == 3) {
+ ASN1 bagAttributes = safeBag [2];
+
+ int bagAttributesFound = 0;
+ for (int i = 0; i < bagAttributes.Count; i++) {
+ ASN1 pkcs12Attribute = bagAttributes [i];
+ ASN1 attrId = pkcs12Attribute [0];
+ string ao = ASN1Convert.ToOid (attrId);
+ ArrayList dattrValues = (ArrayList)attrs [ao];
+
+ if (dattrValues != null) {
+ ASN1 attrValues = pkcs12Attribute [1];
+
+ if (dattrValues.Count == attrValues.Count) {
+ int attrValuesFound = 0;
+ for (int j = 0; j < attrValues.Count; j++) {
+ ASN1 attrValue = attrValues [j];
+ byte[] value = (byte[])dattrValues [j];
+
+ if (Compare (value, attrValue.Value)) {
+ attrValuesFound += 1;
+ }
+ }
+ if (attrValuesFound == attrValues.Count) {
+ bagAttributesFound += 1;
+ }
+ }
+ }
+ }
+ if (bagAttributesFound == bagAttributes.Count) {
+ ASN1 bagValue = safeBag [1];
+ AsymmetricAlgorithm aa = null;
+ if (sb.BagOID.Equals (keyBag)) {
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (bagValue.Value);
+ byte[] privateKey = pki.PrivateKey;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ aa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+ break;
+ case 0x30:
+ aa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+ break;
+ default:
+ break;
+ }
+ Array.Clear (privateKey, 0, privateKey.Length);
+ } else if (sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+ PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+ byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (decrypted);
+ byte[] privateKey = pki.PrivateKey;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ aa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+ break;
+ case 0x30:
+ aa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+ break;
+ default:
+ break;
+ }
+ Array.Clear (privateKey, 0, privateKey.Length);
+ Array.Clear (decrypted, 0, decrypted.Length);
+ }
+ return aa;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public byte[] GetSecret (IDictionary attrs)
+ {
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (secretBag)) {
+ ASN1 safeBag = sb.ASN1;
+
+ if (safeBag.Count == 3) {
+ ASN1 bagAttributes = safeBag [2];
+
+ int bagAttributesFound = 0;
+ for (int i = 0; i < bagAttributes.Count; i++) {
+ ASN1 pkcs12Attribute = bagAttributes [i];
+ ASN1 attrId = pkcs12Attribute [0];
+ string ao = ASN1Convert.ToOid (attrId);
+ ArrayList dattrValues = (ArrayList)attrs [ao];
+
+ if (dattrValues != null) {
+ ASN1 attrValues = pkcs12Attribute [1];
+
+ if (dattrValues.Count == attrValues.Count) {
+ int attrValuesFound = 0;
+ for (int j = 0; j < attrValues.Count; j++) {
+ ASN1 attrValue = attrValues [j];
+ byte[] value = (byte[])dattrValues [j];
+
+ if (Compare (value, attrValue.Value)) {
+ attrValuesFound += 1;
+ }
+ }
+ if (attrValuesFound == attrValues.Count) {
+ bagAttributesFound += 1;
+ }
+ }
+ }
+ }
+ if (bagAttributesFound == bagAttributes.Count) {
+ ASN1 bagValue = safeBag [1];
+ return bagValue.Value;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public X509Certificate GetCertificate (IDictionary attrs)
+ {
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (certBag)) {
+ ASN1 safeBag = sb.ASN1;
+
+ if (safeBag.Count == 3) {
+ ASN1 bagAttributes = safeBag [2];
+
+ int bagAttributesFound = 0;
+ for (int i = 0; i < bagAttributes.Count; i++) {
+ ASN1 pkcs12Attribute = bagAttributes [i];
+ ASN1 attrId = pkcs12Attribute [0];
+ string ao = ASN1Convert.ToOid (attrId);
+ ArrayList dattrValues = (ArrayList)attrs [ao];
+
+ if (dattrValues != null) {
+ ASN1 attrValues = pkcs12Attribute [1];
+
+ if (dattrValues.Count == attrValues.Count) {
+ int attrValuesFound = 0;
+ for (int j = 0; j < attrValues.Count; j++) {
+ ASN1 attrValue = attrValues [j];
+ byte[] value = (byte[])dattrValues [j];
+
+ if (Compare (value, attrValue.Value)) {
+ attrValuesFound += 1;
+ }
+ }
+ if (attrValuesFound == attrValues.Count) {
+ bagAttributesFound += 1;
+ }
+ }
+ }
+ }
+ if (bagAttributesFound == bagAttributes.Count) {
+ ASN1 bagValue = safeBag [1];
+ PKCS7.ContentInfo crt = new PKCS7.ContentInfo (bagValue.Value);
+ return new X509Certificate (crt.Content [0].Value);
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public IDictionary GetAttributes (AsymmetricAlgorithm aa)
+ {
+ IDictionary result = new Hashtable ();
+
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (keyBag) || sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+ ASN1 safeBag = sb.ASN1;
+
+ ASN1 bagValue = safeBag [1];
+ AsymmetricAlgorithm saa = null;
+ if (sb.BagOID.Equals (keyBag)) {
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (bagValue.Value);
+ byte[] privateKey = pki.PrivateKey;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+ break;
+ case 0x30:
+ saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+ break;
+ default:
+ break;
+ }
+ Array.Clear (privateKey, 0, privateKey.Length);
+ } else if (sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+ PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+ byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (decrypted);
+ byte[] privateKey = pki.PrivateKey;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+ break;
+ case 0x30:
+ saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+ break;
+ default:
+ break;
+ }
+ Array.Clear (privateKey, 0, privateKey.Length);
+ Array.Clear (decrypted, 0, decrypted.Length);
+ }
+
+ if (saa != null && CompareAsymmetricAlgorithm (saa, aa)) {
+ if (safeBag.Count == 3) {
+ ASN1 bagAttributes = safeBag [2];
+
+ for (int i = 0; i < bagAttributes.Count; i++) {
+ ASN1 pkcs12Attribute = bagAttributes [i];
+ ASN1 attrId = pkcs12Attribute [0];
+ string aOid = ASN1Convert.ToOid (attrId);
+ ArrayList aValues = new ArrayList ();
+
+ ASN1 attrValues = pkcs12Attribute [1];
+
+ for (int j = 0; j < attrValues.Count; j++) {
+ ASN1 attrValue = attrValues [j];
+ aValues.Add (attrValue.Value);
+ }
+ result.Add (aOid, aValues);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public IDictionary GetAttributes (X509Certificate cert)
+ {
+ IDictionary result = new Hashtable ();
+
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (certBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ PKCS7.ContentInfo crt = new PKCS7.ContentInfo (bagValue.Value);
+ X509Certificate xc = new X509Certificate (crt.Content [0].Value);
+
+ if (Compare (cert.RawData, xc.RawData)) {
+ if (safeBag.Count == 3) {
+ ASN1 bagAttributes = safeBag [2];
+
+ for (int i = 0; i < bagAttributes.Count; i++) {
+ ASN1 pkcs12Attribute = bagAttributes [i];
+ ASN1 attrId = pkcs12Attribute [0];
+
+ string aOid = ASN1Convert.ToOid (attrId);
+ ArrayList aValues = new ArrayList ();
+
+ ASN1 attrValues = pkcs12Attribute [1];
+
+ for (int j = 0; j < attrValues.Count; j++) {
+ ASN1 attrValue = attrValues [j];
+ aValues.Add (attrValue.Value);
+ }
+ result.Add (aOid, aValues);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public void SaveToFile (string filename)
+ {
+ if (filename == null)
+ throw new ArgumentNullException ("filename");
+
+ using (FileStream fs = File.Create (filename)) {
+ byte[] data = GetBytes ();
+ fs.Write (data, 0, data.Length);
+ }
+ }
+
+ public object Clone ()
+ {
+ PKCS12 clone = null;
+ if (_password != null) {
+ clone = new PKCS12 (GetBytes (), Encoding.BigEndianUnicode.GetString (_password));
+ } else {
+ clone = new PKCS12 (GetBytes ());
+ }
+ clone.IterationCount = this.IterationCount;
+
+ return clone;
+ }
+
+ // static
+
+ public const int CryptoApiPasswordLimit = 32;
+
+ static private int password_max_length = Int32.MaxValue;
+
+ // static properties
+
+ // MS CryptoAPI limits the password to a maximum of 31 characters
+ // other implementations, like OpenSSL, have no such limitation.
+ // Setting a maximum value will truncate the password length to
+ // ensure compatibility with MS's PFXImportCertStore API.
+ static public int MaximumPasswordLength {
+ get { return password_max_length; }
+ set {
+ if (value < CryptoApiPasswordLimit) {
+ string msg = string.Format ("Maximum password length cannot be less than {0}.", CryptoApiPasswordLimit);
+ throw new ArgumentOutOfRangeException (msg);
+ }
+ password_max_length = value;
+ }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/PKCS7.cs b/Emby.Server.Implementations/Cryptography/PKCS7.cs
new file mode 100644
index 000000000..475854500
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/PKCS7.cs
@@ -0,0 +1,1012 @@
+//
+// PKCS7.cs: PKCS #7 - Cryptographic Message Syntax Standard
+// http://www.rsasecurity.com/rsalabs/pkcs/pkcs-7/index.html
+//
+// Authors:
+// Sebastien Pouliot <sebastien@ximian.com>
+// Daniel Granath <dgranath#gmail.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Security.Cryptography;
+
+namespace Emby.Server.Core.Cryptography
+{
+
+ public sealed class PKCS7 {
+
+ public class Oid {
+ // pkcs 1
+ public const string rsaEncryption = "1.2.840.113549.1.1.1";
+ // pkcs 7
+ public const string data = "1.2.840.113549.1.7.1";
+ public const string signedData = "1.2.840.113549.1.7.2";
+ public const string envelopedData = "1.2.840.113549.1.7.3";
+ public const string signedAndEnvelopedData = "1.2.840.113549.1.7.4";
+ public const string digestedData = "1.2.840.113549.1.7.5";
+ public const string encryptedData = "1.2.840.113549.1.7.6";
+ // pkcs 9
+ public const string contentType = "1.2.840.113549.1.9.3";
+ public const string messageDigest = "1.2.840.113549.1.9.4";
+ public const string signingTime = "1.2.840.113549.1.9.5";
+ public const string countersignature = "1.2.840.113549.1.9.6";
+
+ public Oid ()
+ {
+ }
+ }
+
+ private PKCS7 ()
+ {
+ }
+
+ static public ASN1 Attribute (string oid, ASN1 value)
+ {
+ ASN1 attr = new ASN1 (0x30);
+ attr.Add (ASN1Convert.FromOid (oid));
+ ASN1 aset = attr.Add (new ASN1 (0x31));
+ aset.Add (value);
+ return attr;
+ }
+
+ static public ASN1 AlgorithmIdentifier (string oid)
+ {
+ ASN1 ai = new ASN1 (0x30);
+ ai.Add (ASN1Convert.FromOid (oid));
+ ai.Add (new ASN1 (0x05)); // NULL
+ return ai;
+ }
+
+ static public ASN1 AlgorithmIdentifier (string oid, ASN1 parameters)
+ {
+ ASN1 ai = new ASN1 (0x30);
+ ai.Add (ASN1Convert.FromOid (oid));
+ ai.Add (parameters);
+ return ai;
+ }
+
+ /*
+ * IssuerAndSerialNumber ::= SEQUENCE {
+ * issuer Name,
+ * serialNumber CertificateSerialNumber
+ * }
+ */
+ static public ASN1 IssuerAndSerialNumber (X509Certificate x509)
+ {
+ ASN1 issuer = null;
+ ASN1 serial = null;
+ ASN1 cert = new ASN1 (x509.RawData);
+ int tbs = 0;
+ bool flag = false;
+ while (tbs < cert[0].Count) {
+ ASN1 e = cert[0][tbs++];
+ if (e.Tag == 0x02)
+ serial = e;
+ else if (e.Tag == 0x30) {
+ if (flag) {
+ issuer = e;
+ break;
+ }
+ flag = true;
+ }
+ }
+ ASN1 iasn = new ASN1 (0x30);
+ iasn.Add (issuer);
+ iasn.Add (serial);
+ return iasn;
+ }
+
+ /*
+ * ContentInfo ::= SEQUENCE {
+ * contentType ContentType,
+ * content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL
+ * }
+ * ContentType ::= OBJECT IDENTIFIER
+ */
+ public class ContentInfo {
+
+ private string contentType;
+ private ASN1 content;
+
+ public ContentInfo ()
+ {
+ content = new ASN1 (0xA0);
+ }
+
+ public ContentInfo (string oid) : this ()
+ {
+ contentType = oid;
+ }
+
+ public ContentInfo (byte[] data)
+ : this (new ASN1 (data)) {}
+
+ public ContentInfo (ASN1 asn1)
+ {
+ // SEQUENCE with 1 or 2 elements
+ if ((asn1.Tag != 0x30) || ((asn1.Count < 1) && (asn1.Count > 2)))
+ throw new ArgumentException ("Invalid ASN1");
+ if (asn1[0].Tag != 0x06)
+ throw new ArgumentException ("Invalid contentType");
+ contentType = ASN1Convert.ToOid (asn1[0]);
+ if (asn1.Count > 1) {
+ if (asn1[1].Tag != 0xA0)
+ throw new ArgumentException ("Invalid content");
+ content = asn1[1];
+ }
+ }
+
+ public ASN1 ASN1 {
+ get { return GetASN1(); }
+ }
+
+ public ASN1 Content {
+ get { return content; }
+ set { content = value; }
+ }
+
+ public string ContentType {
+ get { return contentType; }
+ set { contentType = value; }
+ }
+
+ internal ASN1 GetASN1 ()
+ {
+ // ContentInfo ::= SEQUENCE {
+ ASN1 contentInfo = new ASN1 (0x30);
+ // contentType ContentType, -> ContentType ::= OBJECT IDENTIFIER
+ contentInfo.Add (ASN1Convert.FromOid (contentType));
+ // content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL
+ if ((content != null) && (content.Count > 0))
+ contentInfo.Add (content);
+ return contentInfo;
+ }
+
+ public byte[] GetBytes ()
+ {
+ return GetASN1 ().GetBytes ();
+ }
+ }
+
+ /*
+ * EncryptedData ::= SEQUENCE {
+ * version INTEGER {edVer0(0)} (edVer0),
+ * encryptedContentInfo EncryptedContentInfo
+ * }
+ */
+ public class EncryptedData {
+ private byte _version;
+ private ContentInfo _content;
+ private ContentInfo _encryptionAlgorithm;
+ private byte[] _encrypted;
+
+ public EncryptedData ()
+ {
+ _version = 0;
+ }
+
+ public EncryptedData (byte[] data)
+ : this (new ASN1 (data))
+ {
+ }
+
+ public EncryptedData (ASN1 asn1) : this ()
+ {
+ if ((asn1.Tag != 0x30) || (asn1.Count < 2))
+ throw new ArgumentException ("Invalid EncryptedData");
+
+ if (asn1 [0].Tag != 0x02)
+ throw new ArgumentException ("Invalid version");
+ _version = asn1 [0].Value [0];
+
+ ASN1 encryptedContentInfo = asn1 [1];
+ if (encryptedContentInfo.Tag != 0x30)
+ throw new ArgumentException ("missing EncryptedContentInfo");
+
+ ASN1 contentType = encryptedContentInfo [0];
+ if (contentType.Tag != 0x06)
+ throw new ArgumentException ("missing EncryptedContentInfo.ContentType");
+ _content = new ContentInfo (ASN1Convert.ToOid (contentType));
+
+ ASN1 contentEncryptionAlgorithm = encryptedContentInfo [1];
+ if (contentEncryptionAlgorithm.Tag != 0x30)
+ throw new ArgumentException ("missing EncryptedContentInfo.ContentEncryptionAlgorithmIdentifier");
+ _encryptionAlgorithm = new ContentInfo (ASN1Convert.ToOid (contentEncryptionAlgorithm [0]));
+ _encryptionAlgorithm.Content = contentEncryptionAlgorithm [1];
+
+ ASN1 encryptedContent = encryptedContentInfo [2];
+ if (encryptedContent.Tag != 0x80)
+ throw new ArgumentException ("missing EncryptedContentInfo.EncryptedContent");
+ _encrypted = encryptedContent.Value;
+ }
+
+ public ASN1 ASN1 {
+ get { return GetASN1(); }
+ }
+
+ public ContentInfo ContentInfo {
+ get { return _content; }
+ }
+
+ public ContentInfo EncryptionAlgorithm {
+ get { return _encryptionAlgorithm; }
+ }
+
+ public byte[] EncryptedContent {
+ get {
+ if (_encrypted == null)
+ return null;
+ return (byte[]) _encrypted.Clone ();
+ }
+ }
+
+ public byte Version {
+ get { return _version; }
+ set { _version = value; }
+ }
+
+ // methods
+
+ internal ASN1 GetASN1 ()
+ {
+ return null;
+ }
+
+ public byte[] GetBytes ()
+ {
+ return GetASN1 ().GetBytes ();
+ }
+ }
+
+ /*
+ * EnvelopedData ::= SEQUENCE {
+ * version Version,
+ * recipientInfos RecipientInfos,
+ * encryptedContentInfo EncryptedContentInfo
+ * }
+ *
+ * RecipientInfos ::= SET OF RecipientInfo
+ *
+ * EncryptedContentInfo ::= SEQUENCE {
+ * contentType ContentType,
+ * contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
+ * encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL
+ * }
+ *
+ * EncryptedContent ::= OCTET STRING
+ *
+ */
+ public class EnvelopedData {
+ private byte _version;
+ private ContentInfo _content;
+ private ContentInfo _encryptionAlgorithm;
+ private ArrayList _recipientInfos;
+ private byte[] _encrypted;
+
+ public EnvelopedData ()
+ {
+ _version = 0;
+ _content = new ContentInfo ();
+ _encryptionAlgorithm = new ContentInfo ();
+ _recipientInfos = new ArrayList ();
+ }
+
+ public EnvelopedData (byte[] data)
+ : this (new ASN1 (data))
+ {
+ }
+
+ public EnvelopedData (ASN1 asn1) : this ()
+ {
+ if ((asn1[0].Tag != 0x30) || (asn1[0].Count < 3))
+ throw new ArgumentException ("Invalid EnvelopedData");
+
+ if (asn1[0][0].Tag != 0x02)
+ throw new ArgumentException ("Invalid version");
+ _version = asn1[0][0].Value[0];
+
+ // recipientInfos
+
+ ASN1 recipientInfos = asn1 [0][1];
+ if (recipientInfos.Tag != 0x31)
+ throw new ArgumentException ("missing RecipientInfos");
+ for (int i=0; i < recipientInfos.Count; i++) {
+ ASN1 recipientInfo = recipientInfos [i];
+ _recipientInfos.Add (new RecipientInfo (recipientInfo));
+ }
+
+ ASN1 encryptedContentInfo = asn1[0][2];
+ if (encryptedContentInfo.Tag != 0x30)
+ throw new ArgumentException ("missing EncryptedContentInfo");
+
+ ASN1 contentType = encryptedContentInfo [0];
+ if (contentType.Tag != 0x06)
+ throw new ArgumentException ("missing EncryptedContentInfo.ContentType");
+ _content = new ContentInfo (ASN1Convert.ToOid (contentType));
+
+ ASN1 contentEncryptionAlgorithm = encryptedContentInfo [1];
+ if (contentEncryptionAlgorithm.Tag != 0x30)
+ throw new ArgumentException ("missing EncryptedContentInfo.ContentEncryptionAlgorithmIdentifier");
+ _encryptionAlgorithm = new ContentInfo (ASN1Convert.ToOid (contentEncryptionAlgorithm [0]));
+ _encryptionAlgorithm.Content = contentEncryptionAlgorithm [1];
+
+ ASN1 encryptedContent = encryptedContentInfo [2];
+ if (encryptedContent.Tag != 0x80)
+ throw new ArgumentException ("missing EncryptedContentInfo.EncryptedContent");
+ _encrypted = encryptedContent.Value;
+ }
+
+ public ArrayList RecipientInfos {
+ get { return _recipientInfos; }
+ }
+
+ public ASN1 ASN1 {
+ get { return GetASN1(); }
+ }
+
+ public ContentInfo ContentInfo {
+ get { return _content; }
+ }
+
+ public ContentInfo EncryptionAlgorithm {
+ get { return _encryptionAlgorithm; }
+ }
+
+ public byte[] EncryptedContent {
+ get {
+ if (_encrypted == null)
+ return null;
+ return (byte[]) _encrypted.Clone ();
+ }
+ }
+
+ public byte Version {
+ get { return _version; }
+ set { _version = value; }
+ }
+
+ internal ASN1 GetASN1 ()
+ {
+ // SignedData ::= SEQUENCE {
+ ASN1 signedData = new ASN1 (0x30);
+ // version Version -> Version ::= INTEGER
+/* byte[] ver = { _version };
+ signedData.Add (new ASN1 (0x02, ver));
+ // digestAlgorithms DigestAlgorithmIdentifiers -> DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
+ ASN1 digestAlgorithms = signedData.Add (new ASN1 (0x31));
+ if (hashAlgorithm != null) {
+ string hashOid = CryptoConfig.MapNameToOid (hashAlgorithm);
+ digestAlgorithms.Add (AlgorithmIdentifier (hashOid));
+ }
+
+ // contentInfo ContentInfo,
+ ASN1 ci = contentInfo.ASN1;
+ signedData.Add (ci);
+ if ((mda == null) && (hashAlgorithm != null)) {
+ // automatically add the messageDigest authenticated attribute
+ HashAlgorithm ha = HashAlgorithm.Create (hashAlgorithm);
+ byte[] idcHash = ha.ComputeHash (ci[1][0].Value);
+ ASN1 md = new ASN1 (0x30);
+ mda = Attribute (messageDigest, md.Add (new ASN1 (0x04, idcHash)));
+ signerInfo.AuthenticatedAttributes.Add (mda);
+ }
+
+ // certificates [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL,
+ if (certs.Count > 0) {
+ ASN1 a0 = signedData.Add (new ASN1 (0xA0));
+ foreach (X509Certificate x in certs)
+ a0.Add (new ASN1 (x.RawData));
+ }
+ // crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
+ if (crls.Count > 0) {
+ ASN1 a1 = signedData.Add (new ASN1 (0xA1));
+ foreach (byte[] crl in crls)
+ a1.Add (new ASN1 (crl));
+ }
+ // signerInfos SignerInfos -> SignerInfos ::= SET OF SignerInfo
+ ASN1 signerInfos = signedData.Add (new ASN1 (0x31));
+ if (signerInfo.Key != null)
+ signerInfos.Add (signerInfo.ASN1);*/
+ return signedData;
+ }
+
+ public byte[] GetBytes () {
+ return GetASN1 ().GetBytes ();
+ }
+ }
+
+ /* RecipientInfo ::= SEQUENCE {
+ * version Version,
+ * issuerAndSerialNumber IssuerAndSerialNumber,
+ * keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
+ * encryptedKey EncryptedKey
+ * }
+ *
+ * KeyEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * EncryptedKey ::= OCTET STRING
+ */
+ public class RecipientInfo {
+
+ private int _version;
+ private string _oid;
+ private byte[] _key;
+ private byte[] _ski;
+ private string _issuer;
+ private byte[] _serial;
+
+ public RecipientInfo () {}
+
+ public RecipientInfo (ASN1 data)
+ {
+ if (data.Tag != 0x30)
+ throw new ArgumentException ("Invalid RecipientInfo");
+
+ ASN1 version = data [0];
+ if (version.Tag != 0x02)
+ throw new ArgumentException ("missing Version");
+ _version = version.Value [0];
+
+ // issuerAndSerialNumber IssuerAndSerialNumber
+ ASN1 subjectIdentifierType = data [1];
+ if ((subjectIdentifierType.Tag == 0x80) && (_version == 3)) {
+ _ski = subjectIdentifierType.Value;
+ }
+ else {
+ _issuer = X501.ToString (subjectIdentifierType [0]);
+ _serial = subjectIdentifierType [1].Value;
+ }
+
+ ASN1 keyEncryptionAlgorithm = data [2];
+ _oid = ASN1Convert.ToOid (keyEncryptionAlgorithm [0]);
+
+ ASN1 encryptedKey = data [3];
+ _key = encryptedKey.Value;
+ }
+
+ public string Oid {
+ get { return _oid; }
+ }
+
+ public byte[] Key {
+ get {
+ if (_key == null)
+ return null;
+ return (byte[]) _key.Clone ();
+ }
+ }
+
+ public byte[] SubjectKeyIdentifier {
+ get {
+ if (_ski == null)
+ return null;
+ return (byte[]) _ski.Clone ();
+ }
+ }
+
+ public string Issuer {
+ get { return _issuer; }
+ }
+
+ public byte[] Serial {
+ get {
+ if (_serial == null)
+ return null;
+ return (byte[]) _serial.Clone ();
+ }
+ }
+
+ public int Version {
+ get { return _version; }
+ }
+ }
+
+ /*
+ * SignedData ::= SEQUENCE {
+ * version Version,
+ * digestAlgorithms DigestAlgorithmIdentifiers,
+ * contentInfo ContentInfo,
+ * certificates [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL,
+ * crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
+ * signerInfos SignerInfos
+ * }
+ */
+ public class SignedData {
+ private byte version;
+ private string hashAlgorithm;
+ private ContentInfo contentInfo;
+ private X509CertificateCollection certs;
+ private ArrayList crls;
+ private SignerInfo signerInfo;
+ private bool mda;
+ private bool signed;
+
+ public SignedData ()
+ {
+ version = 1;
+ contentInfo = new ContentInfo ();
+ certs = new X509CertificateCollection ();
+ crls = new ArrayList ();
+ signerInfo = new SignerInfo ();
+ mda = true;
+ signed = false;
+ }
+
+ public SignedData (byte[] data)
+ : this (new ASN1 (data))
+ {
+ }
+
+ public SignedData (ASN1 asn1)
+ {
+ if ((asn1[0].Tag != 0x30) || (asn1[0].Count < 4))
+ throw new ArgumentException ("Invalid SignedData");
+
+ if (asn1[0][0].Tag != 0x02)
+ throw new ArgumentException ("Invalid version");
+ version = asn1[0][0].Value[0];
+
+ contentInfo = new ContentInfo (asn1[0][2]);
+
+ int n = 3;
+ certs = new X509CertificateCollection ();
+ if (asn1[0][n].Tag == 0xA0) {
+ for (int i=0; i < asn1[0][n].Count; i++)
+ certs.Add (new X509Certificate (asn1[0][n][i].GetBytes ()));
+ n++;
+ }
+
+ crls = new ArrayList ();
+ if (asn1[0][n].Tag == 0xA1) {
+ for (int i=0; i < asn1[0][n].Count; i++)
+ crls.Add (asn1[0][n][i].GetBytes ());
+ n++;
+ }
+
+ if (asn1[0][n].Count > 0)
+ signerInfo = new SignerInfo (asn1[0][n]);
+ else
+ signerInfo = new SignerInfo ();
+
+ // Exchange hash algorithm Oid from SignerInfo
+ if (signerInfo.HashName != null) {
+ HashName = OidToName(signerInfo.HashName);
+ }
+
+ // Check if SignerInfo has authenticated attributes
+ mda = (signerInfo.AuthenticatedAttributes.Count > 0);
+ }
+
+ public ASN1 ASN1 {
+ get { return GetASN1(); }
+ }
+
+ public X509CertificateCollection Certificates {
+ get { return certs; }
+ }
+
+ public ContentInfo ContentInfo {
+ get { return contentInfo; }
+ }
+
+ public ArrayList Crls {
+ get { return crls; }
+ }
+
+ public string HashName {
+ get { return hashAlgorithm; }
+ // todo add validation
+ set {
+ hashAlgorithm = value;
+ signerInfo.HashName = value;
+ }
+ }
+
+ public SignerInfo SignerInfo {
+ get { return signerInfo; }
+ }
+
+ public byte Version {
+ get { return version; }
+ set { version = value; }
+ }
+
+ public bool UseAuthenticatedAttributes {
+ get { return mda; }
+ set { mda = value; }
+ }
+
+ public bool VerifySignature (AsymmetricAlgorithm aa)
+ {
+ if (aa == null) {
+ return false;
+ }
+
+ RSAPKCS1SignatureDeformatter r = new RSAPKCS1SignatureDeformatter (aa);
+ r.SetHashAlgorithm (hashAlgorithm);
+ HashAlgorithm ha = HashAlgorithm.Create (hashAlgorithm);
+
+ byte[] signature = signerInfo.Signature;
+ byte[] hash = null;
+
+ if (mda) {
+ ASN1 asn = new ASN1 (0x31);
+ foreach (ASN1 attr in signerInfo.AuthenticatedAttributes)
+ asn.Add (attr);
+
+ hash = ha.ComputeHash (asn.GetBytes ());
+ } else {
+ hash = ha.ComputeHash (contentInfo.Content[0].Value);
+ }
+
+ if (hash != null && signature != null) {
+ return r.VerifySignature (hash, signature);
+ }
+ return false;
+ }
+
+ internal string OidToName (string oid)
+ {
+ switch (oid) {
+ case "1.3.14.3.2.26" :
+ return "SHA1";
+ case "1.2.840.113549.2.2" :
+ return "MD2";
+ case "1.2.840.113549.2.5" :
+ return "MD5";
+ case "2.16.840.1.101.3.4.1" :
+ return "SHA256";
+ case "2.16.840.1.101.3.4.2" :
+ return "SHA384";
+ case "2.16.840.1.101.3.4.3" :
+ return "SHA512";
+ default :
+ break;
+ }
+ // Unknown Oid
+ return oid;
+ }
+
+ internal ASN1 GetASN1 ()
+ {
+ // SignedData ::= SEQUENCE {
+ ASN1 signedData = new ASN1 (0x30);
+ // version Version -> Version ::= INTEGER
+ byte[] ver = { version };
+ signedData.Add (new ASN1 (0x02, ver));
+ // digestAlgorithms DigestAlgorithmIdentifiers -> DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
+ ASN1 digestAlgorithms = signedData.Add (new ASN1 (0x31));
+ if (hashAlgorithm != null) {
+ string hashOid = CryptoConfig.MapNameToOID (hashAlgorithm);
+ digestAlgorithms.Add (AlgorithmIdentifier (hashOid));
+ }
+
+ // contentInfo ContentInfo,
+ ASN1 ci = contentInfo.ASN1;
+ signedData.Add (ci);
+ if (!signed && (hashAlgorithm != null)) {
+ if (mda) {
+ // Use authenticated attributes for signature
+
+ // Automatically add the contentType authenticated attribute
+ ASN1 ctattr = Attribute (Oid.contentType, ci[0]);
+ signerInfo.AuthenticatedAttributes.Add (ctattr);
+
+ // Automatically add the messageDigest authenticated attribute
+ HashAlgorithm ha = HashAlgorithm.Create (hashAlgorithm);
+ byte[] idcHash = ha.ComputeHash (ci[1][0].Value);
+ ASN1 md = new ASN1 (0x30);
+ ASN1 mdattr = Attribute (Oid.messageDigest, md.Add (new ASN1 (0x04, idcHash)));
+ signerInfo.AuthenticatedAttributes.Add (mdattr);
+ } else {
+ // Don't use authenticated attributes for signature -- signature is content
+ RSAPKCS1SignatureFormatter r = new RSAPKCS1SignatureFormatter (signerInfo.Key);
+ r.SetHashAlgorithm (hashAlgorithm);
+ HashAlgorithm ha = HashAlgorithm.Create (hashAlgorithm);
+ byte[] sig = ha.ComputeHash (ci[1][0].Value);
+ signerInfo.Signature = r.CreateSignature (sig);
+ }
+ signed = true;
+ }
+
+ // certificates [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL,
+ if (certs.Count > 0) {
+ ASN1 a0 = signedData.Add (new ASN1 (0xA0));
+ foreach (X509Certificate x in certs)
+ a0.Add (new ASN1 (x.RawData));
+ }
+ // crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
+ if (crls.Count > 0) {
+ ASN1 a1 = signedData.Add (new ASN1 (0xA1));
+ foreach (byte[] crl in crls)
+ a1.Add (new ASN1 (crl));
+ }
+ // signerInfos SignerInfos -> SignerInfos ::= SET OF SignerInfo
+ ASN1 signerInfos = signedData.Add (new ASN1 (0x31));
+ if (signerInfo.Key != null)
+ signerInfos.Add (signerInfo.ASN1);
+ return signedData;
+ }
+
+ public byte[] GetBytes ()
+ {
+ return GetASN1 ().GetBytes ();
+ }
+ }
+
+ /*
+ * SignerInfo ::= SEQUENCE {
+ * version Version,
+ * issuerAndSerialNumber IssuerAndSerialNumber,
+ * digestAlgorithm DigestAlgorithmIdentifier,
+ * authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL,
+ * digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
+ * encryptedDigest EncryptedDigest,
+ * unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL
+ * }
+ *
+ * For version == 3 issuerAndSerialNumber may be replaced by ...
+ */
+ public class SignerInfo {
+
+ private byte version;
+ private X509Certificate x509;
+ private string hashAlgorithm;
+ private AsymmetricAlgorithm key;
+ private ArrayList authenticatedAttributes;
+ private ArrayList unauthenticatedAttributes;
+ private byte[] signature;
+ private string issuer;
+ private byte[] serial;
+ private byte[] ski;
+
+ public SignerInfo ()
+ {
+ version = 1;
+ authenticatedAttributes = new ArrayList ();
+ unauthenticatedAttributes = new ArrayList ();
+ }
+
+ public SignerInfo (byte[] data)
+ : this (new ASN1 (data)) {}
+
+ // TODO: INCOMPLETE
+ public SignerInfo (ASN1 asn1) : this ()
+ {
+ if ((asn1[0].Tag != 0x30) || (asn1[0].Count < 5))
+ throw new ArgumentException ("Invalid SignedData");
+
+ // version Version
+ if (asn1[0][0].Tag != 0x02)
+ throw new ArgumentException ("Invalid version");
+ version = asn1[0][0].Value[0];
+
+ // issuerAndSerialNumber IssuerAndSerialNumber
+ ASN1 subjectIdentifierType = asn1 [0][1];
+ if ((subjectIdentifierType.Tag == 0x80) && (version == 3)) {
+ ski = subjectIdentifierType.Value;
+ }
+ else {
+ issuer = X501.ToString (subjectIdentifierType [0]);
+ serial = subjectIdentifierType [1].Value;
+ }
+
+ // digestAlgorithm DigestAlgorithmIdentifier
+ ASN1 digestAlgorithm = asn1 [0][2];
+ hashAlgorithm = ASN1Convert.ToOid (digestAlgorithm [0]);
+
+ // authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL
+ int n = 3;
+ ASN1 authAttributes = asn1 [0][n];
+ if (authAttributes.Tag == 0xA0) {
+ n++;
+ for (int i=0; i < authAttributes.Count; i++)
+ authenticatedAttributes.Add (authAttributes [i]);
+ }
+
+ // digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier
+ n++;
+ // ASN1 digestEncryptionAlgorithm = asn1 [0][n++];
+ // string digestEncryptionAlgorithmOid = ASN1Convert.ToOid (digestEncryptionAlgorithm [0]);
+
+ // encryptedDigest EncryptedDigest
+ ASN1 encryptedDigest = asn1 [0][n++];
+ if (encryptedDigest.Tag == 0x04)
+ signature = encryptedDigest.Value;
+
+ // unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL
+ ASN1 unauthAttributes = asn1 [0][n];
+ if ((unauthAttributes != null) && (unauthAttributes.Tag == 0xA1)) {
+ for (int i=0; i < unauthAttributes.Count; i++)
+ unauthenticatedAttributes.Add (unauthAttributes [i]);
+ }
+ }
+
+ public string IssuerName {
+ get { return issuer; }
+ }
+
+ public byte[] SerialNumber {
+ get {
+ if (serial == null)
+ return null;
+ return (byte[]) serial.Clone ();
+ }
+ }
+
+ public byte[] SubjectKeyIdentifier {
+ get {
+ if (ski == null)
+ return null;
+ return (byte[]) ski.Clone ();
+ }
+ }
+
+ public ASN1 ASN1 {
+ get { return GetASN1(); }
+ }
+
+ public ArrayList AuthenticatedAttributes {
+ get { return authenticatedAttributes; }
+ }
+
+ public X509Certificate Certificate {
+ get { return x509; }
+ set { x509 = value; }
+ }
+
+ public string HashName {
+ get { return hashAlgorithm; }
+ set { hashAlgorithm = value; }
+ }
+
+ public AsymmetricAlgorithm Key {
+ get { return key; }
+ set { key = value; }
+ }
+
+ public byte[] Signature {
+ get {
+ if (signature == null)
+ return null;
+ return (byte[]) signature.Clone ();
+ }
+
+ set {
+ if (value != null) {
+ signature = (byte[]) value.Clone ();
+ }
+ }
+ }
+
+ public ArrayList UnauthenticatedAttributes {
+ get { return unauthenticatedAttributes; }
+ }
+
+ public byte Version {
+ get { return version; }
+ set { version = value; }
+ }
+
+ internal ASN1 GetASN1 ()
+ {
+ if ((key == null) || (hashAlgorithm == null))
+ return null;
+ byte[] ver = { version };
+ ASN1 signerInfo = new ASN1 (0x30);
+ // version Version -> Version ::= INTEGER
+ signerInfo.Add (new ASN1 (0x02, ver));
+ // issuerAndSerialNumber IssuerAndSerialNumber,
+ signerInfo.Add (PKCS7.IssuerAndSerialNumber (x509));
+ // digestAlgorithm DigestAlgorithmIdentifier,
+ string hashOid = CryptoConfig.MapNameToOID (hashAlgorithm);
+ signerInfo.Add (AlgorithmIdentifier (hashOid));
+ // authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL,
+ ASN1 aa = null;
+ if (authenticatedAttributes.Count > 0) {
+ aa = signerInfo.Add (new ASN1 (0xA0));
+ authenticatedAttributes.Sort(new SortedSet ());
+ foreach (ASN1 attr in authenticatedAttributes)
+ aa.Add (attr);
+ }
+ // digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
+ if (key is RSA) {
+ signerInfo.Add (AlgorithmIdentifier (PKCS7.Oid.rsaEncryption));
+
+ if (aa != null) {
+ // Calculate the signature here; otherwise it must be set from SignedData
+ RSAPKCS1SignatureFormatter r = new RSAPKCS1SignatureFormatter (key);
+ r.SetHashAlgorithm (hashAlgorithm);
+ byte[] tbs = aa.GetBytes ();
+ tbs [0] = 0x31; // not 0xA0 for signature
+ HashAlgorithm ha = HashAlgorithm.Create (hashAlgorithm);
+ byte[] tbsHash = ha.ComputeHash (tbs);
+ signature = r.CreateSignature (tbsHash);
+ }
+ }
+ else if (key is DSA) {
+ throw new NotImplementedException ("not yet");
+ }
+ else
+ throw new CryptographicException ("Unknown assymetric algorithm");
+ // encryptedDigest EncryptedDigest,
+ signerInfo.Add (new ASN1 (0x04, signature));
+ // unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL
+ if (unauthenticatedAttributes.Count > 0) {
+ ASN1 ua = signerInfo.Add (new ASN1 (0xA1));
+ unauthenticatedAttributes.Sort(new SortedSet ());
+ foreach (ASN1 attr in unauthenticatedAttributes)
+ ua.Add (attr);
+ }
+ return signerInfo;
+ }
+
+ public byte[] GetBytes ()
+ {
+ return GetASN1 ().GetBytes ();
+ }
+ }
+
+ internal class SortedSet : IComparer {
+
+ public int Compare (object x, object y)
+ {
+ if (x == null)
+ return (y == null) ? 0 : -1;
+ else if (y == null)
+ return 1;
+
+ ASN1 xx = x as ASN1;
+ ASN1 yy = y as ASN1;
+
+ if ((xx == null) || (yy == null)) {
+ throw new ArgumentException (("Invalid objects."));
+ }
+
+ byte[] xb = xx.GetBytes ();
+ byte[] yb = yy.GetBytes ();
+
+ for (int i = 0; i < xb.Length; i++) {
+ if (i == yb.Length)
+ break;
+
+ if (xb[i] == yb[i])
+ continue;
+
+ return (xb[i] < yb[i]) ? -1 : 1;
+ }
+
+ // The arrays are equal up to the shortest of them.
+ if (xb.Length > yb.Length)
+ return 1;
+ else if (xb.Length < yb.Length)
+ return -1;
+
+ return 0;
+ }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/PKCS8.cs b/Emby.Server.Implementations/Cryptography/PKCS8.cs
new file mode 100644
index 000000000..7e9a27298
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/PKCS8.cs
@@ -0,0 +1,495 @@
+//
+// PKCS8.cs: PKCS #8 - Private-Key Information Syntax Standard
+// ftp://ftp.rsasecurity.com/pub/pkcs/doc/pkcs-8.doc
+//
+// Author:
+// Sebastien Pouliot <sebastien@xamarin.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2006 Novell Inc. (http://www.novell.com)
+// Copyright 2013 Xamarin Inc. (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Security.Cryptography;
+
+namespace Emby.Server.Core.Cryptography
+{
+
+ public sealed class PKCS8 {
+
+ public enum KeyInfo {
+ PrivateKey,
+ EncryptedPrivateKey,
+ Unknown
+ }
+
+ private PKCS8 ()
+ {
+ }
+
+ static public KeyInfo GetType (byte[] data)
+ {
+ if (data == null)
+ throw new ArgumentNullException ("data");
+
+ KeyInfo ki = KeyInfo.Unknown;
+ try {
+ ASN1 top = new ASN1 (data);
+ if ((top.Tag == 0x30) && (top.Count > 0)) {
+ ASN1 firstLevel = top [0];
+ switch (firstLevel.Tag) {
+ case 0x02:
+ ki = KeyInfo.PrivateKey;
+ break;
+ case 0x30:
+ ki = KeyInfo.EncryptedPrivateKey;
+ break;
+ }
+ }
+ }
+ catch {
+ throw new CryptographicException ("invalid ASN.1 data");
+ }
+ return ki;
+ }
+
+ /*
+ * PrivateKeyInfo ::= SEQUENCE {
+ * version Version,
+ * privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
+ * privateKey PrivateKey,
+ * attributes [0] IMPLICIT Attributes OPTIONAL
+ * }
+ *
+ * Version ::= INTEGER
+ *
+ * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * PrivateKey ::= OCTET STRING
+ *
+ * Attributes ::= SET OF Attribute
+ */
+ public class PrivateKeyInfo {
+
+ private int _version;
+ private string _algorithm;
+ private byte[] _key;
+ private ArrayList _list;
+
+ public PrivateKeyInfo ()
+ {
+ _version = 0;
+ _list = new ArrayList ();
+ }
+
+ public PrivateKeyInfo (byte[] data) : this ()
+ {
+ Decode (data);
+ }
+
+ // properties
+
+ public string Algorithm {
+ get { return _algorithm; }
+ set { _algorithm = value; }
+ }
+
+ public ArrayList Attributes {
+ get { return _list; }
+ }
+
+ public byte[] PrivateKey {
+ get {
+ if (_key == null)
+ return null;
+ return (byte[]) _key.Clone ();
+ }
+ set {
+ if (value == null)
+ throw new ArgumentNullException ("PrivateKey");
+ _key = (byte[]) value.Clone ();
+ }
+ }
+
+ public int Version {
+ get { return _version; }
+ set {
+ if (value < 0)
+ throw new ArgumentOutOfRangeException ("negative version");
+ _version = value;
+ }
+ }
+
+ // methods
+
+ private void Decode (byte[] data)
+ {
+ ASN1 privateKeyInfo = new ASN1 (data);
+ if (privateKeyInfo.Tag != 0x30)
+ throw new CryptographicException ("invalid PrivateKeyInfo");
+
+ ASN1 version = privateKeyInfo [0];
+ if (version.Tag != 0x02)
+ throw new CryptographicException ("invalid version");
+ _version = version.Value [0];
+
+ ASN1 privateKeyAlgorithm = privateKeyInfo [1];
+ if (privateKeyAlgorithm.Tag != 0x30)
+ throw new CryptographicException ("invalid algorithm");
+
+ ASN1 algorithm = privateKeyAlgorithm [0];
+ if (algorithm.Tag != 0x06)
+ throw new CryptographicException ("missing algorithm OID");
+ _algorithm = ASN1Convert.ToOid (algorithm);
+
+ ASN1 privateKey = privateKeyInfo [2];
+ _key = privateKey.Value;
+
+ // attributes [0] IMPLICIT Attributes OPTIONAL
+ if (privateKeyInfo.Count > 3) {
+ ASN1 attributes = privateKeyInfo [3];
+ for (int i=0; i < attributes.Count; i++) {
+ _list.Add (attributes [i]);
+ }
+ }
+ }
+
+ public byte[] GetBytes ()
+ {
+ ASN1 privateKeyAlgorithm = new ASN1 (0x30);
+ privateKeyAlgorithm.Add (ASN1Convert.FromOid (_algorithm));
+ privateKeyAlgorithm.Add (new ASN1 (0x05)); // ASN.1 NULL
+
+ ASN1 pki = new ASN1 (0x30);
+ pki.Add (new ASN1 (0x02, new byte [1] { (byte) _version }));
+ pki.Add (privateKeyAlgorithm);
+ pki.Add (new ASN1 (0x04, _key));
+
+ if (_list.Count > 0) {
+ ASN1 attributes = new ASN1 (0xA0);
+ foreach (ASN1 attribute in _list) {
+ attributes.Add (attribute);
+ }
+ pki.Add (attributes);
+ }
+
+ return pki.GetBytes ();
+ }
+
+ // static methods
+
+ static private byte[] RemoveLeadingZero (byte[] bigInt)
+ {
+ int start = 0;
+ int length = bigInt.Length;
+ if (bigInt [0] == 0x00) {
+ start = 1;
+ length--;
+ }
+ byte[] bi = new byte [length];
+ Buffer.BlockCopy (bigInt, start, bi, 0, length);
+ return bi;
+ }
+
+ static private byte[] Normalize (byte[] bigInt, int length)
+ {
+ if (bigInt.Length == length)
+ return bigInt;
+ else if (bigInt.Length > length)
+ return RemoveLeadingZero (bigInt);
+ else {
+ // pad with 0
+ byte[] bi = new byte [length];
+ Buffer.BlockCopy (bigInt, 0, bi, (length - bigInt.Length), bigInt.Length);
+ return bi;
+ }
+ }
+
+ /*
+ * RSAPrivateKey ::= SEQUENCE {
+ * version Version,
+ * modulus INTEGER, -- n
+ * publicExponent INTEGER, -- e
+ * privateExponent INTEGER, -- d
+ * prime1 INTEGER, -- p
+ * prime2 INTEGER, -- q
+ * exponent1 INTEGER, -- d mod (p-1)
+ * exponent2 INTEGER, -- d mod (q-1)
+ * coefficient INTEGER, -- (inverse of q) mod p
+ * otherPrimeInfos OtherPrimeInfos OPTIONAL
+ * }
+ */
+ static public RSA DecodeRSA (byte[] keypair)
+ {
+ ASN1 privateKey = new ASN1 (keypair);
+ if (privateKey.Tag != 0x30)
+ throw new CryptographicException ("invalid private key format");
+
+ ASN1 version = privateKey [0];
+ if (version.Tag != 0x02)
+ throw new CryptographicException ("missing version");
+
+ if (privateKey.Count < 9)
+ throw new CryptographicException ("not enough key parameters");
+
+ RSAParameters param = new RSAParameters ();
+ // note: MUST remove leading 0 - else MS wont import the key
+ param.Modulus = RemoveLeadingZero (privateKey [1].Value);
+ int keysize = param.Modulus.Length;
+ int keysize2 = (keysize >> 1); // half-size
+ // size must be normalized - else MS wont import the key
+ param.D = Normalize (privateKey [3].Value, keysize);
+ param.DP = Normalize (privateKey [6].Value, keysize2);
+ param.DQ = Normalize (privateKey [7].Value, keysize2);
+ param.Exponent = RemoveLeadingZero (privateKey [2].Value);
+ param.InverseQ = Normalize (privateKey [8].Value, keysize2);
+ param.P = Normalize (privateKey [4].Value, keysize2);
+ param.Q = Normalize (privateKey [5].Value, keysize2);
+
+ RSA rsa = null;
+ try {
+ rsa = RSA.Create ();
+ rsa.ImportParameters (param);
+ }
+ catch (CryptographicException) {
+ // this may cause problem when this code is run under
+ // the SYSTEM identity on Windows (e.g. ASP.NET). See
+ // http://bugzilla.ximian.com/show_bug.cgi?id=77559
+ CspParameters csp = new CspParameters();
+ csp.Flags = CspProviderFlags.UseMachineKeyStore;
+ rsa = new RSACryptoServiceProvider(csp);
+ rsa.ImportParameters(param);
+ }
+ return rsa;
+ }
+
+ /*
+ * RSAPrivateKey ::= SEQUENCE {
+ * version Version,
+ * modulus INTEGER, -- n
+ * publicExponent INTEGER, -- e
+ * privateExponent INTEGER, -- d
+ * prime1 INTEGER, -- p
+ * prime2 INTEGER, -- q
+ * exponent1 INTEGER, -- d mod (p-1)
+ * exponent2 INTEGER, -- d mod (q-1)
+ * coefficient INTEGER, -- (inverse of q) mod p
+ * otherPrimeInfos OtherPrimeInfos OPTIONAL
+ * }
+ */
+ static public byte[] Encode (RSA rsa)
+ {
+ RSAParameters param = rsa.ExportParameters (true);
+
+ ASN1 rsaPrivateKey = new ASN1 (0x30);
+ rsaPrivateKey.Add (new ASN1 (0x02, new byte [1] { 0x00 }));
+ rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.Modulus));
+ rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.Exponent));
+ rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.D));
+ rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.P));
+ rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.Q));
+ rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.DP));
+ rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.DQ));
+ rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.InverseQ));
+
+ return rsaPrivateKey.GetBytes ();
+ }
+
+ // DSA only encode it's X private key inside an ASN.1 INTEGER (Hint: Tag == 0x02)
+ // which isn't enough for rebuilding the keypair. The other parameters
+ // can be found (98% of the time) in the X.509 certificate associated
+ // with the private key or (2% of the time) the parameters are in it's
+ // issuer X.509 certificate (not supported in the .NET framework).
+ static public DSA DecodeDSA (byte[] privateKey, DSAParameters dsaParameters)
+ {
+ ASN1 pvk = new ASN1 (privateKey);
+ if (pvk.Tag != 0x02)
+ throw new CryptographicException ("invalid private key format");
+
+ // X is ALWAYS 20 bytes (no matter if the key length is 512 or 1024 bits)
+ dsaParameters.X = Normalize (pvk.Value, 20);
+ DSA dsa = DSA.Create ();
+ dsa.ImportParameters (dsaParameters);
+ return dsa;
+ }
+
+ static public byte[] Encode (DSA dsa)
+ {
+ DSAParameters param = dsa.ExportParameters (true);
+ return ASN1Convert.FromUnsignedBigInteger (param.X).GetBytes ();
+ }
+
+ static public byte[] Encode (AsymmetricAlgorithm aa)
+ {
+ if (aa is RSA)
+ return Encode ((RSA)aa);
+ else if (aa is DSA)
+ return Encode ((DSA)aa);
+ else
+ throw new CryptographicException ("Unknown asymmetric algorithm {0}", aa.ToString ());
+ }
+ }
+
+ /*
+ * EncryptedPrivateKeyInfo ::= SEQUENCE {
+ * encryptionAlgorithm EncryptionAlgorithmIdentifier,
+ * encryptedData EncryptedData
+ * }
+ *
+ * EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * EncryptedData ::= OCTET STRING
+ *
+ * --
+ * AlgorithmIdentifier ::= SEQUENCE {
+ * algorithm OBJECT IDENTIFIER,
+ * parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * -- from PKCS#5
+ * PBEParameter ::= SEQUENCE {
+ * salt OCTET STRING SIZE(8),
+ * iterationCount INTEGER
+ * }
+ */
+ public class EncryptedPrivateKeyInfo {
+
+ private string _algorithm;
+ private byte[] _salt;
+ private int _iterations;
+ private byte[] _data;
+
+ public EncryptedPrivateKeyInfo () {}
+
+ public EncryptedPrivateKeyInfo (byte[] data) : this ()
+ {
+ Decode (data);
+ }
+
+ // properties
+
+ public string Algorithm {
+ get { return _algorithm; }
+ set { _algorithm = value; }
+ }
+
+ public byte[] EncryptedData {
+ get { return (_data == null) ? null : (byte[]) _data.Clone (); }
+ set { _data = (value == null) ? null : (byte[]) value.Clone (); }
+ }
+
+ public byte[] Salt {
+ get {
+ if (_salt == null) {
+ RandomNumberGenerator rng = RandomNumberGenerator.Create ();
+ _salt = new byte [8];
+ rng.GetBytes (_salt);
+ }
+ return (byte[]) _salt.Clone ();
+ }
+ set { _salt = (byte[]) value.Clone (); }
+ }
+
+ public int IterationCount {
+ get { return _iterations; }
+ set {
+ if (value < 0)
+ throw new ArgumentOutOfRangeException ("IterationCount", "Negative");
+ _iterations = value;
+ }
+ }
+
+ // methods
+
+ private void Decode (byte[] data)
+ {
+ ASN1 encryptedPrivateKeyInfo = new ASN1 (data);
+ if (encryptedPrivateKeyInfo.Tag != 0x30)
+ throw new CryptographicException ("invalid EncryptedPrivateKeyInfo");
+
+ ASN1 encryptionAlgorithm = encryptedPrivateKeyInfo [0];
+ if (encryptionAlgorithm.Tag != 0x30)
+ throw new CryptographicException ("invalid encryptionAlgorithm");
+ ASN1 algorithm = encryptionAlgorithm [0];
+ if (algorithm.Tag != 0x06)
+ throw new CryptographicException ("invalid algorithm");
+ _algorithm = ASN1Convert.ToOid (algorithm);
+ // parameters ANY DEFINED BY algorithm OPTIONAL
+ if (encryptionAlgorithm.Count > 1) {
+ ASN1 parameters = encryptionAlgorithm [1];
+ if (parameters.Tag != 0x30)
+ throw new CryptographicException ("invalid parameters");
+
+ ASN1 salt = parameters [0];
+ if (salt.Tag != 0x04)
+ throw new CryptographicException ("invalid salt");
+ _salt = salt.Value;
+
+ ASN1 iterationCount = parameters [1];
+ if (iterationCount.Tag != 0x02)
+ throw new CryptographicException ("invalid iterationCount");
+ _iterations = ASN1Convert.ToInt32 (iterationCount);
+ }
+
+ ASN1 encryptedData = encryptedPrivateKeyInfo [1];
+ if (encryptedData.Tag != 0x04)
+ throw new CryptographicException ("invalid EncryptedData");
+ _data = encryptedData.Value;
+ }
+
+ // Note: PKCS#8 doesn't define how to generate the key required for encryption
+ // so you're on your own. Just don't try to copy the big guys too much ;)
+ // Netscape: http://www.cs.auckland.ac.nz/~pgut001/pubs/netscape.txt
+ // Microsoft: http://www.cs.auckland.ac.nz/~pgut001/pubs/breakms.txt
+ public byte[] GetBytes ()
+ {
+ if (_algorithm == null)
+ throw new CryptographicException ("No algorithm OID specified");
+
+ ASN1 encryptionAlgorithm = new ASN1 (0x30);
+ encryptionAlgorithm.Add (ASN1Convert.FromOid (_algorithm));
+
+ // parameters ANY DEFINED BY algorithm OPTIONAL
+ if ((_iterations > 0) || (_salt != null)) {
+ ASN1 salt = new ASN1 (0x04, _salt);
+ ASN1 iterations = ASN1Convert.FromInt32 (_iterations);
+
+ ASN1 parameters = new ASN1 (0x30);
+ parameters.Add (salt);
+ parameters.Add (iterations);
+ encryptionAlgorithm.Add (parameters);
+ }
+
+ // encapsulates EncryptedData into an OCTET STRING
+ ASN1 encryptedData = new ASN1 (0x04, _data);
+
+ ASN1 encryptedPrivateKeyInfo = new ASN1 (0x30);
+ encryptedPrivateKeyInfo.Add (encryptionAlgorithm);
+ encryptedPrivateKeyInfo.Add (encryptedData);
+
+ return encryptedPrivateKeyInfo.GetBytes ();
+ }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/PfxGenerator.cs b/Emby.Server.Implementations/Cryptography/PfxGenerator.cs
new file mode 100644
index 000000000..2d1dd649e
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/PfxGenerator.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections;
+using System.Security.Cryptography;
+
+namespace Emby.Server.Core.Cryptography
+{
+ public class PFXGenerator
+ {
+ // http://www.freekpaans.nl/2015/04/creating-self-signed-x-509-certificates-using-mono-security/
+ public static byte[] GeneratePfx(string certificateName, string password)
+ {
+ byte[] sn = GenerateSerialNumber();
+ string subject = string.Format("CN={0}", certificateName);
+
+ DateTime notBefore = DateTime.Now;
+ DateTime notAfter = DateTime.Now.AddYears(20);
+
+ RSA subjectKey = new RSACryptoServiceProvider(2048);
+
+
+ string hashName = "SHA256";
+
+ X509CertificateBuilder cb = new X509CertificateBuilder(3);
+ cb.SerialNumber = sn;
+ cb.IssuerName = subject;
+ cb.NotBefore = notBefore;
+ cb.NotAfter = notAfter;
+ cb.SubjectName = subject;
+ cb.SubjectPublicKey = subjectKey;
+ cb.Hash = hashName;
+
+ byte[] rawcert = cb.Sign(subjectKey);
+
+
+ PKCS12 p12 = new PKCS12();
+ p12.Password = password;
+
+ Hashtable attributes = GetAttributes();
+
+ p12.AddCertificate(new X509Certificate(rawcert), attributes);
+ p12.AddPkcs8ShroudedKeyBag(subjectKey, attributes);
+
+ return p12.GetBytes();
+ }
+
+ private static Hashtable GetAttributes()
+ {
+ ArrayList list = new ArrayList();
+ // we use a fixed array to avoid endianess issues
+ // (in case some tools requires the ID to be 1).
+ list.Add(new byte[4] { 1, 0, 0, 0 });
+ Hashtable attributes = new Hashtable(1);
+ attributes.Add(PKCS9.localKeyId, list);
+ return attributes;
+ }
+
+ private static byte[] GenerateSerialNumber()
+ {
+ byte[] sn = Guid.NewGuid().ToByteArray();
+
+ //must be positive
+ if ((sn[0] & 0x80) == 0x80)
+ sn[0] -= 0x80;
+ return sn;
+ }
+
+ public static byte[] GetCertificateForBytes(byte[] pfx, string password)
+ {
+ var pkcs = new PKCS12(pfx, password);
+ var cert = pkcs.GetCertificate(GetAttributes());
+
+ return cert.RawData;
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/X501Name.cs b/Emby.Server.Implementations/Cryptography/X501Name.cs
new file mode 100644
index 000000000..3318f95e2
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/X501Name.cs
@@ -0,0 +1,393 @@
+//
+// X501Name.cs: X.501 Distinguished Names stuff
+//
+// Author:
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Globalization;
+using System.Text;
+
+namespace Emby.Server.Core.Cryptography
+{
+
+ // References:
+ // 1. Information technology - Open Systems Interconnection - The Directory: Models
+ // http://www.itu.int/rec/recommendation.asp?type=items&lang=e&parent=T-REC-X.501-200102-I
+ // 2. RFC2253: Lightweight Directory Access Protocol (v3): UTF-8 String Representation of Distinguished Names
+ // http://www.ietf.org/rfc/rfc2253.txt
+
+ /*
+ * Name ::= CHOICE { RDNSequence }
+ *
+ * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+ *
+ * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
+ */
+ public sealed class X501 {
+
+ static byte[] countryName = { 0x55, 0x04, 0x06 };
+ static byte[] organizationName = { 0x55, 0x04, 0x0A };
+ static byte[] organizationalUnitName = { 0x55, 0x04, 0x0B };
+ static byte[] commonName = { 0x55, 0x04, 0x03 };
+ static byte[] localityName = { 0x55, 0x04, 0x07 };
+ static byte[] stateOrProvinceName = { 0x55, 0x04, 0x08 };
+ static byte[] streetAddress = { 0x55, 0x04, 0x09 };
+ //static byte[] serialNumber = { 0x55, 0x04, 0x05 };
+ static byte[] domainComponent = { 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x19 };
+ static byte[] userid = { 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x01 };
+ static byte[] email = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01 };
+ static byte[] dnQualifier = { 0x55, 0x04, 0x2E };
+ static byte[] title = { 0x55, 0x04, 0x0C };
+ static byte[] surname = { 0x55, 0x04, 0x04 };
+ static byte[] givenName = { 0x55, 0x04, 0x2A };
+ static byte[] initial = { 0x55, 0x04, 0x2B };
+
+ private X501 ()
+ {
+ }
+
+ static public string ToString (ASN1 seq)
+ {
+ StringBuilder sb = new StringBuilder ();
+ for (int i = 0; i < seq.Count; i++) {
+ ASN1 entry = seq [i];
+ AppendEntry (sb, entry, true);
+
+ // separator (not on last iteration)
+ if (i < seq.Count - 1)
+ sb.Append (", ");
+ }
+ return sb.ToString ();
+ }
+
+ static public string ToString (ASN1 seq, bool reversed, string separator, bool quotes)
+ {
+ StringBuilder sb = new StringBuilder ();
+
+ if (reversed) {
+ for (int i = seq.Count - 1; i >= 0; i--) {
+ ASN1 entry = seq [i];
+ AppendEntry (sb, entry, quotes);
+
+ // separator (not on last iteration)
+ if (i > 0)
+ sb.Append (separator);
+ }
+ } else {
+ for (int i = 0; i < seq.Count; i++) {
+ ASN1 entry = seq [i];
+ AppendEntry (sb, entry, quotes);
+
+ // separator (not on last iteration)
+ if (i < seq.Count - 1)
+ sb.Append (separator);
+ }
+ }
+ return sb.ToString ();
+ }
+
+ static private void AppendEntry (StringBuilder sb, ASN1 entry, bool quotes)
+ {
+ // multiple entries are valid
+ for (int k = 0; k < entry.Count; k++) {
+ ASN1 pair = entry [k];
+ ASN1 s = pair [1];
+ if (s == null)
+ continue;
+
+ ASN1 poid = pair [0];
+ if (poid == null)
+ continue;
+
+ if (poid.CompareValue (countryName))
+ sb.Append ("C=");
+ else if (poid.CompareValue (organizationName))
+ sb.Append ("O=");
+ else if (poid.CompareValue (organizationalUnitName))
+ sb.Append ("OU=");
+ else if (poid.CompareValue (commonName))
+ sb.Append ("CN=");
+ else if (poid.CompareValue (localityName))
+ sb.Append ("L=");
+ else if (poid.CompareValue (stateOrProvinceName))
+ sb.Append ("S="); // NOTE: RFC2253 uses ST=
+ else if (poid.CompareValue (streetAddress))
+ sb.Append ("STREET=");
+ else if (poid.CompareValue (domainComponent))
+ sb.Append ("DC=");
+ else if (poid.CompareValue (userid))
+ sb.Append ("UID=");
+ else if (poid.CompareValue (email))
+ sb.Append ("E="); // NOTE: Not part of RFC2253
+ else if (poid.CompareValue (dnQualifier))
+ sb.Append ("dnQualifier=");
+ else if (poid.CompareValue (title))
+ sb.Append ("T=");
+ else if (poid.CompareValue (surname))
+ sb.Append ("SN=");
+ else if (poid.CompareValue (givenName))
+ sb.Append ("G=");
+ else if (poid.CompareValue (initial))
+ sb.Append ("I=");
+ else {
+ // unknown OID
+ sb.Append ("OID."); // NOTE: Not present as RFC2253
+ sb.Append (ASN1Convert.ToOid (poid));
+ sb.Append ("=");
+ }
+
+ string sValue = null;
+ // 16bits or 8bits string ? TODO not complete (+special chars!)
+ if (s.Tag == 0x1E) {
+ // BMPSTRING
+ StringBuilder sb2 = new StringBuilder ();
+ for (int j = 1; j < s.Value.Length; j += 2)
+ sb2.Append ((char)s.Value[j]);
+ sValue = sb2.ToString ();
+ } else {
+ if (s.Tag == 0x14)
+ sValue = Encoding.UTF7.GetString (s.Value);
+ else
+ sValue = Encoding.UTF8.GetString (s.Value);
+ // in some cases we must quote (") the value
+ // Note: this doesn't seems to conform to RFC2253
+ char[] specials = { ',', '+', '"', '\\', '<', '>', ';' };
+ if (quotes) {
+ if ((sValue.IndexOfAny (specials, 0, sValue.Length) > 0) ||
+ sValue.StartsWith (" ") || (sValue.EndsWith (" ")))
+ sValue = "\"" + sValue + "\"";
+ }
+ }
+
+ sb.Append (sValue);
+
+ // separator (not on last iteration)
+ if (k < entry.Count - 1)
+ sb.Append (", ");
+ }
+ }
+
+ static private X520.AttributeTypeAndValue GetAttributeFromOid (string attributeType)
+ {
+ string s = attributeType.ToUpper (CultureInfo.InvariantCulture).Trim ();
+ switch (s) {
+ case "C":
+ return new X520.CountryName ();
+ case "O":
+ return new X520.OrganizationName ();
+ case "OU":
+ return new X520.OrganizationalUnitName ();
+ case "CN":
+ return new X520.CommonName ();
+ case "L":
+ return new X520.LocalityName ();
+ case "S": // Microsoft
+ case "ST": // RFC2253
+ return new X520.StateOrProvinceName ();
+ case "E": // NOTE: Not part of RFC2253
+ return new X520.EmailAddress ();
+ case "DC": // RFC2247
+ return new X520.DomainComponent ();
+ case "UID": // RFC1274
+ return new X520.UserId ();
+ case "DNQUALIFIER":
+ return new X520.DnQualifier ();
+ case "T":
+ return new X520.Title ();
+ case "SN":
+ return new X520.Surname ();
+ case "G":
+ return new X520.GivenName ();
+ case "I":
+ return new X520.Initial ();
+ default:
+ if (s.StartsWith ("OID.")) {
+ // MUST support it but it OID may be without it
+ return new X520.Oid (s.Substring (4));
+ } else {
+ if (IsOid (s))
+ return new X520.Oid (s);
+ else
+ return null;
+ }
+ }
+ }
+
+ static private bool IsOid (string oid)
+ {
+ try {
+ ASN1 asn = ASN1Convert.FromOid (oid);
+ return (asn.Tag == 0x06);
+ }
+ catch {
+ return false;
+ }
+ }
+
+ // no quote processing
+ static private X520.AttributeTypeAndValue ReadAttribute (string value, ref int pos)
+ {
+ while ((value[pos] == ' ') && (pos < value.Length))
+ pos++;
+
+ // get '=' position in substring
+ int equal = value.IndexOf ('=', pos);
+ if (equal == -1) {
+ string msg = ("No attribute found.");
+ throw new FormatException (msg);
+ }
+
+ string s = value.Substring (pos, equal - pos);
+ X520.AttributeTypeAndValue atv = GetAttributeFromOid (s);
+ if (atv == null) {
+ string msg = ("Unknown attribute '{0}'.");
+ throw new FormatException (String.Format (msg, s));
+ }
+ pos = equal + 1; // skip the '='
+ return atv;
+ }
+
+ static private bool IsHex (char c)
+ {
+ if (Char.IsDigit (c))
+ return true;
+ char up = Char.ToUpper (c, CultureInfo.InvariantCulture);
+ return ((up >= 'A') && (up <= 'F'));
+ }
+
+ static string ReadHex (string value, ref int pos)
+ {
+ StringBuilder sb = new StringBuilder ();
+ // it is (at least an) 8 bits char
+ sb.Append (value[pos++]);
+ sb.Append (value[pos]);
+ // look ahead for a 16 bits char
+ if ((pos < value.Length - 4) && (value[pos+1] == '\\') && IsHex (value[pos+2])) {
+ pos += 2; // pass last char and skip \
+ sb.Append (value[pos++]);
+ sb.Append (value[pos]);
+ }
+ byte[] data = CryptoConvert.FromHex (sb.ToString ());
+ return Encoding.UTF8.GetString (data);
+ }
+
+ static private int ReadEscaped (StringBuilder sb, string value, int pos)
+ {
+ switch (value[pos]) {
+ case '\\':
+ case '"':
+ case '=':
+ case ';':
+ case '<':
+ case '>':
+ case '+':
+ case '#':
+ case ',':
+ sb.Append (value[pos]);
+ return pos;
+ default:
+ if (pos >= value.Length - 2) {
+ string msg = ("Malformed escaped value '{0}'.");
+ throw new FormatException (string.Format (msg, value.Substring (pos)));
+ }
+ // it's either a 8 bits or 16 bits char
+ sb.Append (ReadHex (value, ref pos));
+ return pos;
+ }
+ }
+
+ static private int ReadQuoted (StringBuilder sb, string value, int pos)
+ {
+ int original = pos;
+ while (pos <= value.Length) {
+ switch (value[pos]) {
+ case '"':
+ return pos;
+ case '\\':
+ return ReadEscaped (sb, value, pos);
+ default:
+ sb.Append (value[pos]);
+ pos++;
+ break;
+ }
+ }
+ string msg = ("Malformed quoted value '{0}'.");
+ throw new FormatException (string.Format (msg, value.Substring (original)));
+ }
+
+ static private string ReadValue (string value, ref int pos)
+ {
+ int original = pos;
+ StringBuilder sb = new StringBuilder ();
+ while (pos < value.Length) {
+ switch (value [pos]) {
+ case '\\':
+ pos = ReadEscaped (sb, value, ++pos);
+ break;
+ case '"':
+ pos = ReadQuoted (sb, value, ++pos);
+ break;
+ case '=':
+ case ';':
+ case '<':
+ case '>':
+ string msg =("Malformed value '{0}' contains '{1}' outside quotes.");
+ throw new FormatException (string.Format (msg, value.Substring (original), value[pos]));
+ case '+':
+ case '#':
+ throw new NotImplementedException ();
+ case ',':
+ pos++;
+ return sb.ToString ();
+ default:
+ sb.Append (value[pos]);
+ break;
+ }
+ pos++;
+ }
+ return sb.ToString ();
+ }
+
+ static public ASN1 FromString (string rdn)
+ {
+ if (rdn == null)
+ throw new ArgumentNullException ("rdn");
+
+ int pos = 0;
+ ASN1 asn1 = new ASN1 (0x30);
+ while (pos < rdn.Length) {
+ X520.AttributeTypeAndValue atv = ReadAttribute (rdn, ref pos);
+ atv.Value = ReadValue (rdn, ref pos);
+
+ ASN1 sequence = new ASN1 (0x31);
+ sequence.Add (atv.GetASN1 ());
+ asn1.Add (sequence);
+ }
+ return asn1;
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/X509Builder.cs b/Emby.Server.Implementations/Cryptography/X509Builder.cs
new file mode 100644
index 000000000..a2e292350
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/X509Builder.cs
@@ -0,0 +1,153 @@
+//
+// X509Builder.cs: Abstract builder class for X509 objects
+//
+// Author:
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// (C) 2004 Novell (http://www.novell.com)
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Globalization;
+using System.Security.Cryptography;
+
+namespace Emby.Server.Core.Cryptography
+{
+
+ public abstract class X509Builder {
+
+ private const string defaultHash = "SHA1";
+ private string hashName;
+
+ protected X509Builder ()
+ {
+ hashName = defaultHash;
+ }
+
+ protected abstract ASN1 ToBeSigned (string hashName);
+
+ // move to PKCS1
+ protected string GetOid (string hashName)
+ {
+ switch (hashName.ToLower (CultureInfo.InvariantCulture)) {
+ case "md2":
+ // md2withRSAEncryption (1 2 840 113549 1 1 2)
+ return "1.2.840.113549.1.1.2";
+ case "md4":
+ // md4withRSAEncryption (1 2 840 113549 1 1 3)
+ return "1.2.840.113549.1.1.3";
+ case "md5":
+ // md5withRSAEncryption (1 2 840 113549 1 1 4)
+ return "1.2.840.113549.1.1.4";
+ case "sha1":
+ // sha1withRSAEncryption (1 2 840 113549 1 1 5)
+ return "1.2.840.113549.1.1.5";
+ case "sha256":
+ // sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 }
+ return "1.2.840.113549.1.1.11";
+ case "sha384":
+ // sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 }
+ return "1.2.840.113549.1.1.12";
+ case "sha512":
+ // sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 }
+ return "1.2.840.113549.1.1.13";
+ default:
+ throw new NotSupportedException ("Unknown hash algorithm " + hashName);
+ }
+ }
+
+ public string Hash {
+ get { return hashName; }
+ set {
+ if (hashName == null)
+ hashName = defaultHash;
+ else
+ hashName = value;
+ }
+ }
+
+ public virtual byte[] Sign (AsymmetricAlgorithm aa)
+ {
+ if (aa is RSA)
+ return Sign (aa as RSA);
+ else if (aa is DSA)
+ return Sign (aa as DSA);
+ else
+ throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString());
+ }
+
+ private byte[] Build (ASN1 tbs, string hashoid, byte[] signature)
+ {
+ ASN1 builder = new ASN1 (0x30);
+ builder.Add (tbs);
+ builder.Add (PKCS7.AlgorithmIdentifier (hashoid));
+ // first byte of BITSTRING is the number of unused bits in the first byte
+ byte[] bitstring = new byte [signature.Length + 1];
+ Buffer.BlockCopy (signature, 0, bitstring, 1, signature.Length);
+ builder.Add (new ASN1 (0x03, bitstring));
+ return builder.GetBytes ();
+ }
+
+ public virtual byte[] Sign (RSA key)
+ {
+ string oid = GetOid (hashName);
+ ASN1 tbs = ToBeSigned (oid);
+ HashAlgorithm ha = HashAlgorithm.Create (hashName);
+ byte[] hash = ha.ComputeHash (tbs.GetBytes ());
+
+ RSAPKCS1SignatureFormatter pkcs1 = new RSAPKCS1SignatureFormatter (key);
+ pkcs1.SetHashAlgorithm (hashName);
+ byte[] signature = pkcs1.CreateSignature (hash);
+
+ return Build (tbs, oid, signature);
+ }
+
+ public virtual byte[] Sign (DSA key)
+ {
+ string oid = "1.2.840.10040.4.3";
+ ASN1 tbs = ToBeSigned (oid);
+ HashAlgorithm ha = HashAlgorithm.Create (hashName);
+ if (!(ha is SHA1))
+ throw new NotSupportedException ("Only SHA-1 is supported for DSA");
+ byte[] hash = ha.ComputeHash (tbs.GetBytes ());
+
+ DSASignatureFormatter dsa = new DSASignatureFormatter (key);
+ dsa.SetHashAlgorithm (hashName);
+ byte[] rs = dsa.CreateSignature (hash);
+
+ // split R and S
+ byte[] r = new byte [20];
+ Buffer.BlockCopy (rs, 0, r, 0, 20);
+ byte[] s = new byte [20];
+ Buffer.BlockCopy (rs, 20, s, 0, 20);
+ ASN1 signature = new ASN1 (0x30);
+ signature.Add (new ASN1 (0x02, r));
+ signature.Add (new ASN1 (0x02, s));
+
+ // dsaWithSha1 (1 2 840 10040 4 3)
+ return Build (tbs, oid, signature.GetBytes ());
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/X509Certificate.cs b/Emby.Server.Implementations/Cryptography/X509Certificate.cs
new file mode 100644
index 000000000..3de58cfee
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/X509Certificate.cs
@@ -0,0 +1,563 @@
+//
+// X509Certificates.cs: Handles X.509 certificates.
+//
+// Author:
+// Sebastien Pouliot <sebastien@xamarin.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
+// Copyright 2013 Xamarin Inc. (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Runtime.Serialization;
+using System.Security.Cryptography;
+using System.Security.Permissions;
+using System.Text;
+
+namespace Emby.Server.Core.Cryptography
+{
+
+ // References:
+ // a. Internet X.509 Public Key Infrastructure Certificate and CRL Profile
+ // http://www.ietf.org/rfc/rfc3280.txt
+ // b. ITU ASN.1 standards (free download)
+ // http://www.itu.int/ITU-T/studygroups/com17/languages/
+
+ public class X509Certificate : ISerializable
+ {
+
+ private ASN1 decoder;
+
+ private byte[] m_encodedcert;
+ private DateTime m_from;
+ private DateTime m_until;
+ private ASN1 issuer;
+ private string m_issuername;
+ private string m_keyalgo;
+ private byte[] m_keyalgoparams;
+ private ASN1 subject;
+ private string m_subject;
+ private byte[] m_publickey;
+ private byte[] signature;
+ private string m_signaturealgo;
+ private byte[] m_signaturealgoparams;
+ private byte[] certhash;
+ private RSA _rsa;
+ private DSA _dsa;
+
+ // from http://msdn.microsoft.com/en-gb/library/ff635835.aspx
+ private const string OID_DSA = "1.2.840.10040.4.1";
+ private const string OID_RSA = "1.2.840.113549.1.1.1";
+
+ // from http://www.ietf.org/rfc/rfc2459.txt
+ //
+ //Certificate ::= SEQUENCE {
+ // tbsCertificate TBSCertificate,
+ // signatureAlgorithm AlgorithmIdentifier,
+ // signature BIT STRING }
+ //
+ //TBSCertificate ::= SEQUENCE {
+ // version [0] Version DEFAULT v1,
+ // serialNumber CertificateSerialNumber,
+ // signature AlgorithmIdentifier,
+ // issuer Name,
+ // validity Validity,
+ // subject Name,
+ // subjectPublicKeyInfo SubjectPublicKeyInfo,
+ // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
+ // -- If present, version shall be v2 or v3
+ // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
+ // -- If present, version shall be v2 or v3
+ // extensions [3] Extensions OPTIONAL
+ // -- If present, version shall be v3 -- }
+ private int version;
+ private byte[] serialnumber;
+
+ private byte[] issuerUniqueID;
+ private byte[] subjectUniqueID;
+ private X509ExtensionCollection extensions;
+
+ private static string encoding_error = ("Input data cannot be coded as a valid certificate.");
+
+
+ // that's were the real job is!
+ private void Parse (byte[] data)
+ {
+ try {
+ decoder = new ASN1 (data);
+ // Certificate
+ if (decoder.Tag != 0x30)
+ throw new CryptographicException (encoding_error);
+ // Certificate / TBSCertificate
+ if (decoder [0].Tag != 0x30)
+ throw new CryptographicException (encoding_error);
+
+ ASN1 tbsCertificate = decoder [0];
+
+ int tbs = 0;
+ // Certificate / TBSCertificate / Version
+ ASN1 v = decoder [0][tbs];
+ version = 1; // DEFAULT v1
+ if ((v.Tag == 0xA0) && (v.Count > 0)) {
+ // version (optional) is present only in v2+ certs
+ version += v [0].Value [0]; // zero based
+ tbs++;
+ }
+
+ // Certificate / TBSCertificate / CertificateSerialNumber
+ ASN1 sn = decoder [0][tbs++];
+ if (sn.Tag != 0x02)
+ throw new CryptographicException (encoding_error);
+ serialnumber = sn.Value;
+ Array.Reverse (serialnumber, 0, serialnumber.Length);
+
+ // Certificate / TBSCertificate / AlgorithmIdentifier
+ tbs++;
+ // ASN1 signatureAlgo = tbsCertificate.Element (tbs++, 0x30);
+
+ issuer = tbsCertificate.Element (tbs++, 0x30);
+ m_issuername = X501.ToString (issuer);
+
+ ASN1 validity = tbsCertificate.Element (tbs++, 0x30);
+ ASN1 notBefore = validity [0];
+ m_from = ASN1Convert.ToDateTime (notBefore);
+ ASN1 notAfter = validity [1];
+ m_until = ASN1Convert.ToDateTime (notAfter);
+
+ subject = tbsCertificate.Element (tbs++, 0x30);
+ m_subject = X501.ToString (subject);
+
+ ASN1 subjectPublicKeyInfo = tbsCertificate.Element (tbs++, 0x30);
+
+ ASN1 algorithm = subjectPublicKeyInfo.Element (0, 0x30);
+ ASN1 algo = algorithm.Element (0, 0x06);
+ m_keyalgo = ASN1Convert.ToOid (algo);
+ // parameters ANY DEFINED BY algorithm OPTIONAL
+ // so we dont ask for a specific (Element) type and return DER
+ ASN1 parameters = algorithm [1];
+ m_keyalgoparams = ((algorithm.Count > 1) ? parameters.GetBytes () : null);
+
+ ASN1 subjectPublicKey = subjectPublicKeyInfo.Element (1, 0x03);
+ // we must drop th first byte (which is the number of unused bits
+ // in the BITSTRING)
+ int n = subjectPublicKey.Length - 1;
+ m_publickey = new byte [n];
+ Buffer.BlockCopy (subjectPublicKey.Value, 1, m_publickey, 0, n);
+
+ // signature processing
+ byte[] bitstring = decoder [2].Value;
+ // first byte contains unused bits in first byte
+ signature = new byte [bitstring.Length - 1];
+ Buffer.BlockCopy (bitstring, 1, signature, 0, signature.Length);
+
+ algorithm = decoder [1];
+ algo = algorithm.Element (0, 0x06);
+ m_signaturealgo = ASN1Convert.ToOid (algo);
+ parameters = algorithm [1];
+ if (parameters != null)
+ m_signaturealgoparams = parameters.GetBytes ();
+ else
+ m_signaturealgoparams = null;
+
+ // Certificate / TBSCertificate / issuerUniqueID
+ ASN1 issuerUID = tbsCertificate.Element (tbs, 0x81);
+ if (issuerUID != null) {
+ tbs++;
+ issuerUniqueID = issuerUID.Value;
+ }
+
+ // Certificate / TBSCertificate / subjectUniqueID
+ ASN1 subjectUID = tbsCertificate.Element (tbs, 0x82);
+ if (subjectUID != null) {
+ tbs++;
+ subjectUniqueID = subjectUID.Value;
+ }
+
+ // Certificate / TBSCertificate / Extensions
+ ASN1 extns = tbsCertificate.Element (tbs, 0xA3);
+ if ((extns != null) && (extns.Count == 1))
+ extensions = new X509ExtensionCollection (extns [0]);
+ else
+ extensions = new X509ExtensionCollection (null);
+
+ // keep a copy of the original data
+ m_encodedcert = (byte[]) data.Clone ();
+ }
+ catch (Exception ex) {
+ throw new CryptographicException (encoding_error, ex);
+ }
+ }
+
+ // constructors
+
+ public X509Certificate (byte[] data)
+ {
+ if (data != null) {
+ // does it looks like PEM ?
+ if ((data.Length > 0) && (data [0] != 0x30)) {
+ try {
+ data = PEM ("CERTIFICATE", data);
+ }
+ catch (Exception ex) {
+ throw new CryptographicException (encoding_error, ex);
+ }
+ }
+ Parse (data);
+ }
+ }
+
+ private byte[] GetUnsignedBigInteger (byte[] integer)
+ {
+ if (integer [0] == 0x00) {
+ // this first byte is added so we're sure it's an unsigned integer
+ // however we can't feed it into RSAParameters or DSAParameters
+ int length = integer.Length - 1;
+ byte[] uinteger = new byte [length];
+ Buffer.BlockCopy (integer, 1, uinteger, 0, length);
+ return uinteger;
+ }
+ else
+ return integer;
+ }
+
+ // public methods
+
+ public DSA DSA {
+ get {
+ if (m_keyalgoparams == null)
+ throw new CryptographicException ("Missing key algorithm parameters.");
+
+ if (_dsa == null && m_keyalgo == OID_DSA) {
+ DSAParameters dsaParams = new DSAParameters ();
+ // for DSA m_publickey contains 1 ASN.1 integer - Y
+ ASN1 pubkey = new ASN1 (m_publickey);
+ if ((pubkey == null) || (pubkey.Tag != 0x02))
+ return null;
+ dsaParams.Y = GetUnsignedBigInteger (pubkey.Value);
+
+ ASN1 param = new ASN1 (m_keyalgoparams);
+ if ((param == null) || (param.Tag != 0x30) || (param.Count < 3))
+ return null;
+ if ((param [0].Tag != 0x02) || (param [1].Tag != 0x02) || (param [2].Tag != 0x02))
+ return null;
+ dsaParams.P = GetUnsignedBigInteger (param [0].Value);
+ dsaParams.Q = GetUnsignedBigInteger (param [1].Value);
+ dsaParams.G = GetUnsignedBigInteger (param [2].Value);
+
+ // BUG: MS BCL 1.0 can't import a key which
+ // isn't the same size as the one present in
+ // the container.
+ _dsa = (DSA) new DSACryptoServiceProvider (dsaParams.Y.Length << 3);
+ _dsa.ImportParameters (dsaParams);
+ }
+ return _dsa;
+ }
+
+ set {
+ _dsa = value;
+ if (value != null)
+ _rsa = null;
+ }
+ }
+
+ public X509ExtensionCollection Extensions {
+ get { return extensions; }
+ }
+
+ public byte[] Hash {
+ get {
+ if (certhash == null) {
+ if ((decoder == null) || (decoder.Count < 1))
+ return null;
+ string algo = PKCS1.HashNameFromOid (m_signaturealgo, false);
+ if (algo == null)
+ return null;
+ byte[] toBeSigned = decoder [0].GetBytes ();
+ using (var hash = PKCS1.CreateFromName (algo))
+ certhash = hash.ComputeHash (toBeSigned, 0, toBeSigned.Length);
+ }
+ return (byte[]) certhash.Clone ();
+ }
+ }
+
+ public virtual string IssuerName {
+ get { return m_issuername; }
+ }
+
+ public virtual string KeyAlgorithm {
+ get { return m_keyalgo; }
+ }
+
+ public virtual byte[] KeyAlgorithmParameters {
+ get {
+ if (m_keyalgoparams == null)
+ return null;
+ return (byte[]) m_keyalgoparams.Clone ();
+ }
+ set { m_keyalgoparams = value; }
+ }
+
+ public virtual byte[] PublicKey {
+ get {
+ if (m_publickey == null)
+ return null;
+ return (byte[]) m_publickey.Clone ();
+ }
+ }
+
+ public virtual RSA RSA {
+ get {
+ if (_rsa == null && m_keyalgo == OID_RSA) {
+ RSAParameters rsaParams = new RSAParameters ();
+ // for RSA m_publickey contains 2 ASN.1 integers
+ // the modulus and the public exponent
+ ASN1 pubkey = new ASN1 (m_publickey);
+ ASN1 modulus = pubkey [0];
+ if ((modulus == null) || (modulus.Tag != 0x02))
+ return null;
+ ASN1 exponent = pubkey [1];
+ if (exponent.Tag != 0x02)
+ return null;
+
+ rsaParams.Modulus = GetUnsignedBigInteger (modulus.Value);
+ rsaParams.Exponent = exponent.Value;
+
+ // BUG: MS BCL 1.0 can't import a key which
+ // isn't the same size as the one present in
+ // the container.
+ int keySize = (rsaParams.Modulus.Length << 3);
+ _rsa = (RSA) new RSACryptoServiceProvider (keySize);
+ _rsa.ImportParameters (rsaParams);
+ }
+ return _rsa;
+ }
+
+ set {
+ if (value != null)
+ _dsa = null;
+ _rsa = value;
+ }
+ }
+
+ public virtual byte[] RawData {
+ get {
+ if (m_encodedcert == null)
+ return null;
+ return (byte[]) m_encodedcert.Clone ();
+ }
+ }
+
+ public virtual byte[] SerialNumber {
+ get {
+ if (serialnumber == null)
+ return null;
+ return (byte[]) serialnumber.Clone ();
+ }
+ }
+
+ public virtual byte[] Signature {
+ get {
+ if (signature == null)
+ return null;
+
+ switch (m_signaturealgo) {
+ case "1.2.840.113549.1.1.2": // MD2 with RSA encryption
+ case "1.2.840.113549.1.1.3": // MD4 with RSA encryption
+ case "1.2.840.113549.1.1.4": // MD5 with RSA encryption
+ case "1.2.840.113549.1.1.5": // SHA-1 with RSA Encryption
+ case "1.3.14.3.2.29": // SHA1 with RSA signature
+ case "1.2.840.113549.1.1.11": // SHA-256 with RSA Encryption
+ case "1.2.840.113549.1.1.12": // SHA-384 with RSA Encryption
+ case "1.2.840.113549.1.1.13": // SHA-512 with RSA Encryption
+ case "1.3.36.3.3.1.2": // RIPEMD160 with RSA Encryption
+ return (byte[]) signature.Clone ();
+
+ case "1.2.840.10040.4.3": // SHA-1 with DSA
+ ASN1 sign = new ASN1 (signature);
+ if ((sign == null) || (sign.Count != 2))
+ return null;
+ byte[] part1 = sign [0].Value;
+ byte[] part2 = sign [1].Value;
+ byte[] sig = new byte [40];
+ // parts may be less than 20 bytes (i.e. first bytes were 0x00)
+ // parts may be more than 20 bytes (i.e. first byte > 0x80, negative)
+ int s1 = System.Math.Max (0, part1.Length - 20);
+ int e1 = System.Math.Max (0, 20 - part1.Length);
+ Buffer.BlockCopy (part1, s1, sig, e1, part1.Length - s1);
+ int s2 = System.Math.Max (0, part2.Length - 20);
+ int e2 = System.Math.Max (20, 40 - part2.Length);
+ Buffer.BlockCopy (part2, s2, sig, e2, part2.Length - s2);
+ return sig;
+
+ default:
+ throw new CryptographicException ("Unsupported hash algorithm: " + m_signaturealgo);
+ }
+ }
+ }
+
+ public virtual string SignatureAlgorithm {
+ get { return m_signaturealgo; }
+ }
+
+ public virtual byte[] SignatureAlgorithmParameters {
+ get {
+ if (m_signaturealgoparams == null)
+ return m_signaturealgoparams;
+ return (byte[]) m_signaturealgoparams.Clone ();
+ }
+ }
+
+ public virtual string SubjectName {
+ get { return m_subject; }
+ }
+
+ public virtual DateTime ValidFrom {
+ get { return m_from; }
+ }
+
+ public virtual DateTime ValidUntil {
+ get { return m_until; }
+ }
+
+ public int Version {
+ get { return version; }
+ }
+
+ public bool IsCurrent {
+ get { return WasCurrent (DateTime.UtcNow); }
+ }
+
+ public bool WasCurrent (DateTime instant)
+ {
+ return ((instant > ValidFrom) && (instant <= ValidUntil));
+ }
+
+ // uncommon v2 "extension"
+ public byte[] IssuerUniqueIdentifier {
+ get {
+ if (issuerUniqueID == null)
+ return null;
+ return (byte[]) issuerUniqueID.Clone ();
+ }
+ }
+
+ // uncommon v2 "extension"
+ public byte[] SubjectUniqueIdentifier {
+ get {
+ if (subjectUniqueID == null)
+ return null;
+ return (byte[]) subjectUniqueID.Clone ();
+ }
+ }
+
+ internal bool VerifySignature (DSA dsa)
+ {
+ // signatureOID is check by both this.Hash and this.Signature
+ DSASignatureDeformatter v = new DSASignatureDeformatter (dsa);
+ // only SHA-1 is supported
+ v.SetHashAlgorithm ("SHA1");
+ return v.VerifySignature (this.Hash, this.Signature);
+ }
+
+ internal bool VerifySignature (RSA rsa)
+ {
+ // SHA1-1 with DSA
+ if (m_signaturealgo == "1.2.840.10040.4.3")
+ return false;
+ RSAPKCS1SignatureDeformatter v = new RSAPKCS1SignatureDeformatter (rsa);
+ v.SetHashAlgorithm (PKCS1.HashNameFromOid (m_signaturealgo));
+ return v.VerifySignature (this.Hash, this.Signature);
+ }
+
+ public bool VerifySignature (AsymmetricAlgorithm aa)
+ {
+ if (aa == null)
+ throw new ArgumentNullException ("aa");
+
+ if (aa is RSA)
+ return VerifySignature (aa as RSA);
+ else if (aa is DSA)
+ return VerifySignature (aa as DSA);
+ else
+ throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString ());
+ }
+
+ public bool CheckSignature (byte[] hash, string hashAlgorithm, byte[] signature)
+ {
+ RSACryptoServiceProvider r = (RSACryptoServiceProvider) RSA;
+ return r.VerifyHash (hash, hashAlgorithm, signature);
+ }
+
+ public bool IsSelfSigned {
+ get {
+ if (m_issuername != m_subject)
+ return false;
+
+ try {
+ if (RSA != null)
+ return VerifySignature (RSA);
+ else if (DSA != null)
+ return VerifySignature (DSA);
+ else
+ return false; // e.g. a certificate with only DSA parameters
+ }
+ catch (CryptographicException) {
+ return false;
+ }
+ }
+ }
+
+ public ASN1 GetIssuerName ()
+ {
+ return issuer;
+ }
+
+ public ASN1 GetSubjectName ()
+ {
+ return subject;
+ }
+
+ protected X509Certificate (SerializationInfo info, StreamingContext context)
+ {
+ Parse ((byte[]) info.GetValue ("raw", typeof (byte[])));
+ }
+
+ [SecurityPermission (SecurityAction.Demand, SerializationFormatter = true)]
+ public virtual void GetObjectData (SerializationInfo info, StreamingContext context)
+ {
+ info.AddValue ("raw", m_encodedcert);
+ // note: we NEVER serialize the private key
+ }
+
+ static byte[] PEM (string type, byte[] data)
+ {
+ string pem = Encoding.ASCII.GetString (data);
+ string header = String.Format ("-----BEGIN {0}-----", type);
+ string footer = String.Format ("-----END {0}-----", type);
+ int start = pem.IndexOf (header) + header.Length;
+ int end = pem.IndexOf (footer, start);
+ string base64 = pem.Substring (start, (end - start));
+ return Convert.FromBase64String (base64);
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/X509CertificateBuilder.cs b/Emby.Server.Implementations/Cryptography/X509CertificateBuilder.cs
new file mode 100644
index 000000000..cecff6578
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/X509CertificateBuilder.cs
@@ -0,0 +1,245 @@
+//
+// X509CertificateBuilder.cs: Handles building of X.509 certificates.
+//
+// Author:
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// (C) 2004 Novell (http://www.novell.com)
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Security.Cryptography;
+
+namespace Emby.Server.Core.Cryptography
+{
+ // From RFC3280
+ /*
+ * Certificate ::= SEQUENCE {
+ * tbsCertificate TBSCertificate,
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signature BIT STRING
+ * }
+ * TBSCertificate ::= SEQUENCE {
+ * version [0] Version DEFAULT v1,
+ * serialNumber CertificateSerialNumber,
+ * signature AlgorithmIdentifier,
+ * issuer Name,
+ * validity Validity,
+ * subject Name,
+ * subjectPublicKeyInfo SubjectPublicKeyInfo,
+ * issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
+ * -- If present, version MUST be v2 or v3
+ * subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
+ * -- If present, version MUST be v2 or v3
+ * extensions [3] Extensions OPTIONAL
+ * -- If present, version MUST be v3 --
+ * }
+ * Version ::= INTEGER { v1(0), v2(1), v3(2) }
+ * CertificateSerialNumber ::= INTEGER
+ * Validity ::= SEQUENCE {
+ * notBefore Time,
+ * notAfter Time
+ * }
+ * Time ::= CHOICE {
+ * utcTime UTCTime,
+ * generalTime GeneralizedTime
+ * }
+ */
+ public class X509CertificateBuilder : X509Builder {
+
+ private byte version;
+ private byte[] sn;
+ private string issuer;
+ private DateTime notBefore;
+ private DateTime notAfter;
+ private string subject;
+ private AsymmetricAlgorithm aa;
+ private byte[] issuerUniqueID;
+ private byte[] subjectUniqueID;
+ private X509ExtensionCollection extensions;
+
+ public X509CertificateBuilder () : this (3) {}
+
+ public X509CertificateBuilder (byte version)
+ {
+ if (version > 3)
+ throw new ArgumentException ("Invalid certificate version");
+ this.version = version;
+ extensions = new X509ExtensionCollection ();
+ }
+
+ public byte Version {
+ get { return version; }
+ set { version = value; }
+ }
+
+ public byte[] SerialNumber {
+ get { return sn; }
+ set { sn = value; }
+ }
+
+ public string IssuerName {
+ get { return issuer; }
+ set { issuer = value; }
+ }
+
+ public DateTime NotBefore {
+ get { return notBefore; }
+ set { notBefore = value; }
+ }
+
+ public DateTime NotAfter {
+ get { return notAfter; }
+ set { notAfter = value; }
+ }
+
+ public string SubjectName {
+ get { return subject; }
+ set { subject = value; }
+ }
+
+ public AsymmetricAlgorithm SubjectPublicKey {
+ get { return aa; }
+ set { aa = value; }
+ }
+
+ public byte[] IssuerUniqueId {
+ get { return issuerUniqueID; }
+ set { issuerUniqueID = value; }
+ }
+
+ public byte[] SubjectUniqueId {
+ get { return subjectUniqueID; }
+ set { subjectUniqueID = value; }
+ }
+
+ public X509ExtensionCollection Extensions {
+ get { return extensions; }
+ }
+
+
+ /* SubjectPublicKeyInfo ::= SEQUENCE {
+ * algorithm AlgorithmIdentifier,
+ * subjectPublicKey BIT STRING }
+ */
+ private ASN1 SubjectPublicKeyInfo ()
+ {
+ ASN1 keyInfo = new ASN1 (0x30);
+ if (aa is RSA) {
+ keyInfo.Add (PKCS7.AlgorithmIdentifier ("1.2.840.113549.1.1.1"));
+ RSAParameters p = (aa as RSA).ExportParameters (false);
+ /* RSAPublicKey ::= SEQUENCE {
+ * modulus INTEGER, -- n
+ * publicExponent INTEGER } -- e
+ */
+ ASN1 key = new ASN1 (0x30);
+ key.Add (ASN1Convert.FromUnsignedBigInteger (p.Modulus));
+ key.Add (ASN1Convert.FromUnsignedBigInteger (p.Exponent));
+ keyInfo.Add (new ASN1 (UniqueIdentifier (key.GetBytes ())));
+ }
+ else if (aa is DSA) {
+ DSAParameters p = (aa as DSA).ExportParameters (false);
+ /* Dss-Parms ::= SEQUENCE {
+ * p INTEGER,
+ * q INTEGER,
+ * g INTEGER }
+ */
+ ASN1 param = new ASN1 (0x30);
+ param.Add (ASN1Convert.FromUnsignedBigInteger (p.P));
+ param.Add (ASN1Convert.FromUnsignedBigInteger (p.Q));
+ param.Add (ASN1Convert.FromUnsignedBigInteger (p.G));
+ keyInfo.Add (PKCS7.AlgorithmIdentifier ("1.2.840.10040.4.1", param));
+ ASN1 key = keyInfo.Add (new ASN1 (0x03));
+ // DSAPublicKey ::= INTEGER -- public key, y
+ key.Add (ASN1Convert.FromUnsignedBigInteger (p.Y));
+ }
+ else
+ throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString ());
+ return keyInfo;
+ }
+
+ private byte[] UniqueIdentifier (byte[] id)
+ {
+ // UniqueIdentifier ::= BIT STRING
+ ASN1 uid = new ASN1 (0x03);
+ // first byte in a BITSTRING is the number of unused bits in the first byte
+ byte[] v = new byte [id.Length + 1];
+ Buffer.BlockCopy (id, 0, v, 1, id.Length);
+ uid.Value = v;
+ return uid.GetBytes ();
+ }
+
+ protected override ASN1 ToBeSigned (string oid)
+ {
+ // TBSCertificate
+ ASN1 tbsCert = new ASN1 (0x30);
+
+ if (version > 1) {
+ // TBSCertificate / [0] Version DEFAULT v1,
+ byte[] ver = { (byte)(version - 1) };
+ ASN1 v = tbsCert.Add (new ASN1 (0xA0));
+ v.Add (new ASN1 (0x02, ver));
+ }
+
+ // TBSCertificate / CertificateSerialNumber,
+ tbsCert.Add (new ASN1 (0x02, sn));
+
+ // TBSCertificate / AlgorithmIdentifier,
+ tbsCert.Add (PKCS7.AlgorithmIdentifier (oid));
+
+ // TBSCertificate / Name
+ tbsCert.Add (X501.FromString (issuer));
+
+ // TBSCertificate / Validity
+ ASN1 validity = tbsCert.Add (new ASN1 (0x30));
+ // TBSCertificate / Validity / Time
+ validity.Add (ASN1Convert.FromDateTime (notBefore));
+ // TBSCertificate / Validity / Time
+ validity.Add (ASN1Convert.FromDateTime (notAfter));
+
+ // TBSCertificate / Name
+ tbsCert.Add (X501.FromString (subject));
+
+ // TBSCertificate / SubjectPublicKeyInfo
+ tbsCert.Add (SubjectPublicKeyInfo ());
+
+ if (version > 1) {
+ // TBSCertificate / [1] IMPLICIT UniqueIdentifier OPTIONAL
+ if (issuerUniqueID != null)
+ tbsCert.Add (new ASN1 (0xA1, UniqueIdentifier (issuerUniqueID)));
+
+ // TBSCertificate / [2] IMPLICIT UniqueIdentifier OPTIONAL
+ if (subjectUniqueID != null)
+ tbsCert.Add (new ASN1 (0xA1, UniqueIdentifier (subjectUniqueID)));
+
+ // TBSCertificate / [3] Extensions OPTIONAL
+ if ((version > 2) && (extensions.Count > 0))
+ tbsCert.Add (new ASN1 (0xA3, extensions.GetBytes ()));
+ }
+
+ return tbsCert;
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/X509CertificateCollection.cs b/Emby.Server.Implementations/Cryptography/X509CertificateCollection.cs
new file mode 100644
index 000000000..a129bfc1a
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/X509CertificateCollection.cs
@@ -0,0 +1,201 @@
+//
+// Based on System.Security.Cryptography.X509Certificates.X509CertificateCollection
+// in System assembly
+//
+// Authors:
+// Lawrence Pit (loz@cable.a2000.nl)
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+
+namespace Emby.Server.Core.Cryptography
+{
+
+ [Serializable]
+ public class X509CertificateCollection : CollectionBase, IEnumerable {
+
+ public X509CertificateCollection ()
+ {
+ }
+
+ public X509CertificateCollection (X509Certificate [] value)
+ {
+ AddRange (value);
+ }
+
+ public X509CertificateCollection (X509CertificateCollection value)
+ {
+ AddRange (value);
+ }
+
+ // Properties
+
+ public X509Certificate this [int index] {
+ get { return (X509Certificate) InnerList [index]; }
+ set { InnerList [index] = value; }
+ }
+
+ // Methods
+
+ public int Add (X509Certificate value)
+ {
+ if (value == null)
+ throw new ArgumentNullException ("value");
+
+ return InnerList.Add (value);
+ }
+
+ public void AddRange (X509Certificate [] value)
+ {
+ if (value == null)
+ throw new ArgumentNullException ("value");
+
+ for (int i = 0; i < value.Length; i++)
+ InnerList.Add (value [i]);
+ }
+
+ public void AddRange (X509CertificateCollection value)
+ {
+ if (value == null)
+ throw new ArgumentNullException ("value");
+
+ for (int i = 0; i < value.InnerList.Count; i++)
+ InnerList.Add (value [i]);
+ }
+
+ public bool Contains (X509Certificate value)
+ {
+ return (IndexOf (value) != -1);
+ }
+
+ public void CopyTo (X509Certificate[] array, int index)
+ {
+ InnerList.CopyTo (array, index);
+ }
+
+ public new X509CertificateEnumerator GetEnumerator ()
+ {
+ return new X509CertificateEnumerator (this);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator ()
+ {
+ return InnerList.GetEnumerator ();
+ }
+
+ public override int GetHashCode ()
+ {
+ return InnerList.GetHashCode ();
+ }
+
+ public int IndexOf (X509Certificate value)
+ {
+ if (value == null)
+ throw new ArgumentNullException ("value");
+
+ byte[] hash = value.Hash;
+ for (int i=0; i < InnerList.Count; i++) {
+ X509Certificate x509 = (X509Certificate) InnerList [i];
+ if (Compare (x509.Hash, hash))
+ return i;
+ }
+ return -1;
+ }
+
+ public void Insert (int index, X509Certificate value)
+ {
+ InnerList.Insert (index, value);
+ }
+
+ public void Remove (X509Certificate value)
+ {
+ InnerList.Remove (value);
+ }
+
+ // private stuff
+
+ private bool Compare (byte[] array1, byte[] array2)
+ {
+ if ((array1 == null) && (array2 == null))
+ return true;
+ if ((array1 == null) || (array2 == null))
+ return false;
+ if (array1.Length != array2.Length)
+ return false;
+ for (int i=0; i < array1.Length; i++) {
+ if (array1 [i] != array2 [i])
+ return false;
+ }
+ return true;
+ }
+
+ // Inner Class
+
+ public class X509CertificateEnumerator : IEnumerator {
+
+ private IEnumerator enumerator;
+
+ // Constructors
+
+ public X509CertificateEnumerator (X509CertificateCollection mappings)
+ {
+ enumerator = ((IEnumerable) mappings).GetEnumerator ();
+ }
+
+ // Properties
+
+ public X509Certificate Current {
+ get { return (X509Certificate) enumerator.Current; }
+ }
+
+ object IEnumerator.Current {
+ get { return enumerator.Current; }
+ }
+
+ // Methods
+
+ bool IEnumerator.MoveNext ()
+ {
+ return enumerator.MoveNext ();
+ }
+
+ void IEnumerator.Reset ()
+ {
+ enumerator.Reset ();
+ }
+
+ public bool MoveNext ()
+ {
+ return enumerator.MoveNext ();
+ }
+
+ public void Reset ()
+ {
+ enumerator.Reset ();
+ }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/X509Extension.cs b/Emby.Server.Implementations/Cryptography/X509Extension.cs
new file mode 100644
index 000000000..36b17deba
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/X509Extension.cs
@@ -0,0 +1,208 @@
+//
+// X509Extension.cs: Base class for all X.509 extensions.
+//
+// Author:
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Globalization;
+using System.Text;
+
+namespace Emby.Server.Core.Cryptography
+{
+ /*
+ * Extension ::= SEQUENCE {
+ * extnID OBJECT IDENTIFIER,
+ * critical BOOLEAN DEFAULT FALSE,
+ * extnValue OCTET STRING
+ * }
+ */
+ public class X509Extension {
+
+ protected string extnOid;
+ protected bool extnCritical;
+ protected ASN1 extnValue;
+
+ protected X509Extension ()
+ {
+ extnCritical = false;
+ }
+
+ public X509Extension (ASN1 asn1)
+ {
+ if ((asn1.Tag != 0x30) || (asn1.Count < 2))
+ throw new ArgumentException (("Invalid X.509 extension."));
+ if (asn1[0].Tag != 0x06)
+ throw new ArgumentException (("Invalid X.509 extension."));
+
+ extnOid = ASN1Convert.ToOid (asn1[0]);
+ extnCritical = ((asn1[1].Tag == 0x01) && (asn1[1].Value[0] == 0xFF));
+ // last element is an octet string which may need to be decoded
+ extnValue = asn1 [asn1.Count - 1];
+ if ((extnValue.Tag == 0x04) && (extnValue.Length > 0) && (extnValue.Count == 0)) {
+ try {
+ ASN1 encapsulated = new ASN1 (extnValue.Value);
+ extnValue.Value = null;
+ extnValue.Add (encapsulated);
+ }
+ catch {
+ // data isn't ASN.1
+ }
+ }
+ Decode ();
+ }
+
+ public X509Extension (X509Extension extension)
+ {
+ if (extension == null)
+ throw new ArgumentNullException ("extension");
+ if ((extension.Value == null) || (extension.Value.Tag != 0x04) || (extension.Value.Count != 1))
+ throw new ArgumentException (("Invalid X.509 extension."));
+
+ extnOid = extension.Oid;
+ extnCritical = extension.Critical;
+ extnValue = extension.Value;
+ Decode ();
+ }
+
+ // encode the extension *into* an OCTET STRING
+ protected virtual void Decode ()
+ {
+ }
+
+ // decode the extension from *inside* an OCTET STRING
+ protected virtual void Encode ()
+ {
+ }
+
+ public ASN1 ASN1 {
+ get {
+ ASN1 extension = new ASN1 (0x30);
+ extension.Add (ASN1Convert.FromOid (extnOid));
+ if (extnCritical)
+ extension.Add (new ASN1 (0x01, new byte [1] { 0xFF }));
+ Encode ();
+ extension.Add (extnValue);
+ return extension;
+ }
+ }
+
+ public string Oid {
+ get { return extnOid; }
+ }
+
+ public bool Critical {
+ get { return extnCritical; }
+ set { extnCritical = value; }
+ }
+
+ // this gets overrided with more meaningful names
+ public virtual string Name {
+ get { return extnOid; }
+ }
+
+ public ASN1 Value {
+ get {
+ if (extnValue == null) {
+ Encode ();
+ }
+ return extnValue;
+ }
+ }
+
+ public override bool Equals (object obj)
+ {
+ if (obj == null)
+ return false;
+
+ X509Extension ex = (obj as X509Extension);
+ if (ex == null)
+ return false;
+
+ if (extnCritical != ex.extnCritical)
+ return false;
+ if (extnOid != ex.extnOid)
+ return false;
+ if (extnValue.Length != ex.extnValue.Length)
+ return false;
+
+ for (int i=0; i < extnValue.Length; i++) {
+ if (extnValue [i] != ex.extnValue [i])
+ return false;
+ }
+ return true;
+ }
+
+ public byte[] GetBytes ()
+ {
+ return ASN1.GetBytes ();
+ }
+
+ public override int GetHashCode ()
+ {
+ // OID should be unique in a collection of extensions
+ return extnOid.GetHashCode ();
+ }
+
+ private void WriteLine (StringBuilder sb, int n, int pos)
+ {
+ byte[] value = extnValue.Value;
+ int p = pos;
+ for (int j=0; j < 8; j++) {
+ if (j < n) {
+ sb.Append (value [p++].ToString ("X2", CultureInfo.InvariantCulture));
+ sb.Append (" ");
+ }
+ else
+ sb.Append (" ");
+ }
+ sb.Append (" ");
+ p = pos;
+ for (int j=0; j < n; j++) {
+ byte b = value [p++];
+ if (b < 0x20)
+ sb.Append (".");
+ else
+ sb.Append (Convert.ToChar (b));
+ }
+ sb.Append (Environment.NewLine);
+ }
+
+ public override string ToString ()
+ {
+ StringBuilder sb = new StringBuilder ();
+ int div = (extnValue.Length >> 3);
+ int rem = (extnValue.Length - (div << 3));
+ int x = 0;
+ for (int i=0; i < div; i++) {
+ WriteLine (sb, 8, x);
+ x += 8;
+ }
+ WriteLine (sb, rem, x);
+ return sb.ToString ();
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/X509Extensions.cs b/Emby.Server.Implementations/Cryptography/X509Extensions.cs
new file mode 100644
index 000000000..018a04d79
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/X509Extensions.cs
@@ -0,0 +1,195 @@
+//
+// X509Extensions.cs: Handles X.509 extensions.
+//
+// Author:
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// (C) 2004 Novell (http://www.novell.com)
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+
+namespace Emby.Server.Core.Cryptography
+{
+ /*
+ * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
+ *
+ * Note: 1..MAX -> There shouldn't be 0 Extensions in the ASN1 structure
+ */
+ public sealed class X509ExtensionCollection : CollectionBase, IEnumerable {
+
+ private bool readOnly;
+
+ public X509ExtensionCollection () : base ()
+ {
+ }
+
+ public X509ExtensionCollection (ASN1 asn1) : this ()
+ {
+ readOnly = true;
+ if (asn1 == null)
+ return;
+ if (asn1.Tag != 0x30)
+ throw new Exception ("Invalid extensions format");
+ for (int i=0; i < asn1.Count; i++) {
+ X509Extension extension = new X509Extension (asn1 [i]);
+ InnerList.Add (extension);
+ }
+ }
+
+ public int Add (X509Extension extension)
+ {
+ if (extension == null)
+ throw new ArgumentNullException ("extension");
+ if (readOnly)
+ throw new NotSupportedException ("Extensions are read only");
+
+ return InnerList.Add (extension);
+ }
+
+ public void AddRange (X509Extension[] extension)
+ {
+ if (extension == null)
+ throw new ArgumentNullException ("extension");
+ if (readOnly)
+ throw new NotSupportedException ("Extensions are read only");
+
+ for (int i = 0; i < extension.Length; i++)
+ InnerList.Add (extension [i]);
+ }
+
+ public void AddRange (X509ExtensionCollection collection)
+ {
+ if (collection == null)
+ throw new ArgumentNullException ("collection");
+ if (readOnly)
+ throw new NotSupportedException ("Extensions are read only");
+
+ for (int i = 0; i < collection.InnerList.Count; i++)
+ InnerList.Add (collection [i]);
+ }
+
+ public bool Contains (X509Extension extension)
+ {
+ return (IndexOf (extension) != -1);
+ }
+
+ public bool Contains (string oid)
+ {
+ return (IndexOf (oid) != -1);
+ }
+
+ public void CopyTo (X509Extension[] extensions, int index)
+ {
+ if (extensions == null)
+ throw new ArgumentNullException ("extensions");
+
+ InnerList.CopyTo (extensions, index);
+ }
+
+ public int IndexOf (X509Extension extension)
+ {
+ if (extension == null)
+ throw new ArgumentNullException ("extension");
+
+ for (int i=0; i < InnerList.Count; i++) {
+ X509Extension ex = (X509Extension) InnerList [i];
+ if (ex.Equals (extension))
+ return i;
+ }
+ return -1;
+ }
+
+ public int IndexOf (string oid)
+ {
+ if (oid == null)
+ throw new ArgumentNullException ("oid");
+
+ for (int i=0; i < InnerList.Count; i++) {
+ X509Extension ex = (X509Extension) InnerList [i];
+ if (ex.Oid == oid)
+ return i;
+ }
+ return -1;
+ }
+
+ public void Insert (int index, X509Extension extension)
+ {
+ if (extension == null)
+ throw new ArgumentNullException ("extension");
+
+ InnerList.Insert (index, extension);
+ }
+
+ public void Remove (X509Extension extension)
+ {
+ if (extension == null)
+ throw new ArgumentNullException ("extension");
+
+ InnerList.Remove (extension);
+ }
+
+ public void Remove (string oid)
+ {
+ if (oid == null)
+ throw new ArgumentNullException ("oid");
+
+ int index = IndexOf (oid);
+ if (index != -1)
+ InnerList.RemoveAt (index);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator ()
+ {
+ return InnerList.GetEnumerator ();
+ }
+
+ public X509Extension this [int index] {
+ get { return (X509Extension) InnerList [index]; }
+ }
+
+ public X509Extension this [string oid] {
+ get {
+ int index = IndexOf (oid);
+ if (index == -1)
+ return null;
+ return (X509Extension) InnerList [index];
+ }
+ }
+
+ public byte[] GetBytes ()
+ {
+ if (InnerList.Count < 1)
+ return null;
+ ASN1 sequence = new ASN1 (0x30);
+ for (int i=0; i < InnerList.Count; i++) {
+ X509Extension x = (X509Extension) InnerList [i];
+ sequence.Add (x.ASN1);
+ }
+ return sequence.GetBytes ();
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/X520Attributes.cs b/Emby.Server.Implementations/Cryptography/X520Attributes.cs
new file mode 100644
index 000000000..da7fd2b82
--- /dev/null
+++ b/Emby.Server.Implementations/Cryptography/X520Attributes.cs
@@ -0,0 +1,346 @@
+//
+// X520.cs: X.520 related stuff (attributes, RDN)
+//
+// Author:
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Text;
+
+namespace Emby.Server.Core.Cryptography
+{
+
+ // References:
+ // 1. Information technology - Open Systems Interconnection - The Directory: Selected attribute types
+ // http://www.itu.int/rec/recommendation.asp?type=folders&lang=e&parent=T-REC-X.520
+ // 2. Internet X.509 Public Key Infrastructure Certificate and CRL Profile
+ // http://www.ietf.org/rfc/rfc3280.txt
+ // 3. A Summary of the X.500(96) User Schema for use with LDAPv3
+ // http://www.faqs.org/rfcs/rfc2256.html
+ // 4. RFC 2247 - Using Domains in LDAP/X.500 Distinguished Names
+ // http://www.faqs.org/rfcs/rfc2247.html
+
+ /*
+ * AttributeTypeAndValue ::= SEQUENCE {
+ * type AttributeType,
+ * value AttributeValue
+ * }
+ *
+ * AttributeType ::= OBJECT IDENTIFIER
+ *
+ * AttributeValue ::= ANY DEFINED BY AttributeType
+ */
+ public class X520 {
+
+ public abstract class AttributeTypeAndValue {
+ private string oid;
+ private string attrValue;
+ private int upperBound;
+ private byte encoding;
+
+ protected AttributeTypeAndValue (string oid, int upperBound)
+ {
+ this.oid = oid;
+ this.upperBound = upperBound;
+ this.encoding = 0xFF;
+ }
+
+ protected AttributeTypeAndValue (string oid, int upperBound, byte encoding)
+ {
+ this.oid = oid;
+ this.upperBound = upperBound;
+ this.encoding = encoding;
+ }
+
+ public string Value {
+ get { return attrValue; }
+ set {
+ if ((attrValue != null) && (attrValue.Length > upperBound)) {
+ string msg = ("Value length bigger than upperbound ({0}).");
+ throw new FormatException (String.Format (msg, upperBound));
+ }
+ attrValue = value;
+ }
+ }
+
+ public ASN1 ASN1 {
+ get { return GetASN1 (); }
+ }
+
+ internal ASN1 GetASN1 (byte encoding)
+ {
+ byte encode = encoding;
+ if (encode == 0xFF)
+ encode = SelectBestEncoding ();
+
+ ASN1 asn1 = new ASN1 (0x30);
+ asn1.Add (ASN1Convert.FromOid (oid));
+ switch (encode) {
+ case 0x13:
+ // PRINTABLESTRING
+ asn1.Add (new ASN1 (0x13, Encoding.ASCII.GetBytes (attrValue)));
+ break;
+ case 0x16:
+ // IA5STRING
+ asn1.Add (new ASN1 (0x16, Encoding.ASCII.GetBytes (attrValue)));
+ break;
+ case 0x1E:
+ // BMPSTRING
+ asn1.Add (new ASN1 (0x1E, Encoding.BigEndianUnicode.GetBytes (attrValue)));
+ break;
+ }
+ return asn1;
+ }
+
+ internal ASN1 GetASN1 ()
+ {
+ return GetASN1 (encoding);
+ }
+
+ public byte[] GetBytes (byte encoding)
+ {
+ return GetASN1 (encoding) .GetBytes ();
+ }
+
+ public byte[] GetBytes ()
+ {
+ return GetASN1 () .GetBytes ();
+ }
+
+ private byte SelectBestEncoding ()
+ {
+ foreach (char c in attrValue) {
+ switch (c) {
+ case '@':
+ case '_':
+ return 0x1E; // BMPSTRING
+ default:
+ if (c > 127)
+ return 0x1E; // BMPSTRING
+ break;
+ }
+ }
+ return 0x13; // PRINTABLESTRING
+ }
+ }
+
+ public class Name : AttributeTypeAndValue {
+
+ public Name () : base ("2.5.4.41", 32768)
+ {
+ }
+ }
+
+ public class CommonName : AttributeTypeAndValue {
+
+ public CommonName () : base ("2.5.4.3", 64)
+ {
+ }
+ }
+
+ // RFC2256, Section 5.6
+ public class SerialNumber : AttributeTypeAndValue {
+
+ // max length 64 bytes, Printable String only
+ public SerialNumber ()
+ : base ("2.5.4.5", 64, 0x13)
+ {
+ }
+ }
+
+ public class LocalityName : AttributeTypeAndValue {
+
+ public LocalityName () : base ("2.5.4.7", 128)
+ {
+ }
+ }
+
+ public class StateOrProvinceName : AttributeTypeAndValue {
+
+ public StateOrProvinceName () : base ("2.5.4.8", 128)
+ {
+ }
+ }
+
+ public class OrganizationName : AttributeTypeAndValue {
+
+ public OrganizationName () : base ("2.5.4.10", 64)
+ {
+ }
+ }
+
+ public class OrganizationalUnitName : AttributeTypeAndValue {
+
+ public OrganizationalUnitName () : base ("2.5.4.11", 64)
+ {
+ }
+ }
+
+ // NOTE: Not part of RFC2253
+ public class EmailAddress : AttributeTypeAndValue {
+
+ public EmailAddress () : base ("1.2.840.113549.1.9.1", 128, 0x16)
+ {
+ }
+ }
+
+ // RFC2247, Section 4
+ public class DomainComponent : AttributeTypeAndValue {
+
+ // no maximum length defined
+ public DomainComponent ()
+ : base ("0.9.2342.19200300.100.1.25", Int32.MaxValue, 0x16)
+ {
+ }
+ }
+
+ // RFC1274, Section 9.3.1
+ public class UserId : AttributeTypeAndValue {
+
+ public UserId ()
+ : base ("0.9.2342.19200300.100.1.1", 256)
+ {
+ }
+ }
+
+ public class Oid : AttributeTypeAndValue {
+
+ public Oid (string oid)
+ : base (oid, Int32.MaxValue)
+ {
+ }
+ }
+
+ /* -- Naming attributes of type X520Title
+ * id-at-title AttributeType ::= { id-at 12 }
+ *
+ * X520Title ::= CHOICE {
+ * teletexString TeletexString (SIZE (1..ub-title)),
+ * printableString PrintableString (SIZE (1..ub-title)),
+ * universalString UniversalString (SIZE (1..ub-title)),
+ * utf8String UTF8String (SIZE (1..ub-title)),
+ * bmpString BMPString (SIZE (1..ub-title))
+ * }
+ */
+ public class Title : AttributeTypeAndValue {
+
+ public Title () : base ("2.5.4.12", 64)
+ {
+ }
+ }
+
+ public class CountryName : AttributeTypeAndValue {
+
+ // (0x13) PRINTABLESTRING
+ public CountryName () : base ("2.5.4.6", 2, 0x13)
+ {
+ }
+ }
+
+ public class DnQualifier : AttributeTypeAndValue {
+
+ // (0x13) PRINTABLESTRING
+ public DnQualifier () : base ("2.5.4.46", 2, 0x13)
+ {
+ }
+ }
+
+ public class Surname : AttributeTypeAndValue {
+
+ public Surname () : base ("2.5.4.4", 32768)
+ {
+ }
+ }
+
+ public class GivenName : AttributeTypeAndValue {
+
+ public GivenName () : base ("2.5.4.42", 16)
+ {
+ }
+ }
+
+ public class Initial : AttributeTypeAndValue {
+
+ public Initial () : base ("2.5.4.43", 5)
+ {
+ }
+ }
+
+ }
+
+ /* From RFC3280
+ * -- specifications of Upper Bounds MUST be regarded as mandatory
+ * -- from Annex B of ITU-T X.411 Reference Definition of MTS Parameter
+ *
+ * -- Upper Bounds
+ *
+ * ub-name INTEGER ::= 32768
+ * ub-common-name INTEGER ::= 64
+ * ub-locality-name INTEGER ::= 128
+ * ub-state-name INTEGER ::= 128
+ * ub-organization-name INTEGER ::= 64
+ * ub-organizational-unit-name INTEGER ::= 64
+ * ub-title INTEGER ::= 64
+ * ub-serial-number INTEGER ::= 64
+ * ub-match INTEGER ::= 128
+ * ub-emailaddress-length INTEGER ::= 128
+ * ub-common-name-length INTEGER ::= 64
+ * ub-country-name-alpha-length INTEGER ::= 2
+ * ub-country-name-numeric-length INTEGER ::= 3
+ * ub-domain-defined-attributes INTEGER ::= 4
+ * ub-domain-defined-attribute-type-length INTEGER ::= 8
+ * ub-domain-defined-attribute-value-length INTEGER ::= 128
+ * ub-domain-name-length INTEGER ::= 16
+ * ub-extension-attributes INTEGER ::= 256
+ * ub-e163-4-number-length INTEGER ::= 15
+ * ub-e163-4-sub-address-length INTEGER ::= 40
+ * ub-generation-qualifier-length INTEGER ::= 3
+ * ub-given-name-length INTEGER ::= 16
+ * ub-initials-length INTEGER ::= 5
+ * ub-integer-options INTEGER ::= 256
+ * ub-numeric-user-id-length INTEGER ::= 32
+ * ub-organization-name-length INTEGER ::= 64
+ * ub-organizational-unit-name-length INTEGER ::= 32
+ * ub-organizational-units INTEGER ::= 4
+ * ub-pds-name-length INTEGER ::= 16
+ * ub-pds-parameter-length INTEGER ::= 30
+ * ub-pds-physical-address-lines INTEGER ::= 6
+ * ub-postal-code-length INTEGER ::= 16
+ * ub-pseudonym INTEGER ::= 128
+ * ub-surname-length INTEGER ::= 40
+ * ub-terminal-id-length INTEGER ::= 24
+ * ub-unformatted-address-length INTEGER ::= 180
+ * ub-x121-address-length INTEGER ::= 16
+ *
+ * -- Note - upper bounds on string types, such as TeletexString, are
+ * -- measured in characters. Excepting PrintableString or IA5String, a
+ * -- significantly greater number of octets will be required to hold
+ * -- such a value. As a minimum, 16 octets, or twice the specified
+ * -- upper bound, whichever is the larger, should be allowed for
+ * -- TeletexString. For UTF8String or UniversalString at least four
+ * -- times the upper bound should be allowed.
+ */
+}
diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
index 72c069073..a34c90cb4 100644
--- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
+++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
@@ -136,24 +136,6 @@ namespace Emby.Server.Implementations.Data
queries.Add("PRAGMA temp_store = file");
}
- ////foreach (var query in queries)
- ////{
- //// db.Execute(query);
- ////}
-
- //Logger.Info("synchronous: " + db.Query("PRAGMA synchronous").SelectScalarString().First());
- //Logger.Info("temp_store: " + db.Query("PRAGMA temp_store").SelectScalarString().First());
-
- /*if (!string.Equals(_defaultWal, "wal", StringComparison.OrdinalIgnoreCase))
- {
- queries.Add("PRAGMA journal_mode=WAL");
-
- using (WriteLock.Write())
- {
- db.ExecuteAll(string.Join(";", queries.ToArray()));
- }
- }
- else*/
foreach (var query in queries)
{
db.Execute(query);
@@ -212,6 +194,13 @@ namespace Emby.Server.Implementations.Data
"pragma temp_store = memory"
});
}
+ else
+ {
+ queries.AddRange(new List<string>
+ {
+ "pragma temp_store = file"
+ });
+ }
db.ExecuteAll(string.Join(";", queries.ToArray()));
Logger.Info("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First());
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 44b2cd10b..23d46e821 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -31,6 +31,7 @@ using MediaBrowser.Model.Reflection;
using SQLitePCL.pretty;
using MediaBrowser.Model.System;
using MediaBrowser.Model.Threading;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.Data
{
@@ -836,7 +837,7 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.TryBind("@IsInMixedFolder", item.IsInMixedFolder);
- if (item.LockedFields.Count > 0)
+ if (item.LockedFields.Length > 0)
{
saveItemStatement.TryBind("@LockedFields", string.Join("|", item.LockedFields.Select(i => i.ToString()).ToArray()));
}
@@ -845,7 +846,7 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.TryBindNull("@LockedFields");
}
- if (item.Studios.Count > 0)
+ if (item.Studios.Length > 0)
{
saveItemStatement.TryBind("@Studios", string.Join("|", item.Studios.ToArray()));
}
@@ -865,9 +866,9 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.TryBind("@ExternalServiceId", item.ServiceName);
- if (item.Tags.Count > 0)
+ if (item.Tags.Length > 0)
{
- saveItemStatement.TryBind("@Tags", string.Join("|", item.Tags.ToArray()));
+ saveItemStatement.TryBind("@Tags", string.Join("|", item.Tags));
}
else
{
@@ -984,16 +985,16 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item));
saveItemStatement.TryBind("@Images", SerializeImages(item));
- if (item.ProductionLocations.Count > 0)
+ if (item.ProductionLocations.Length > 0)
{
- saveItemStatement.TryBind("@ProductionLocations", string.Join("|", item.ProductionLocations.ToArray()));
+ saveItemStatement.TryBind("@ProductionLocations", string.Join("|", item.ProductionLocations));
}
else
{
saveItemStatement.TryBindNull("@ProductionLocations");
}
- if (item.ThemeSongIds.Count > 0)
+ if (item.ThemeSongIds.Length > 0)
{
saveItemStatement.TryBind("@ThemeSongIds", string.Join("|", item.ThemeSongIds.ToArray()));
}
@@ -1002,7 +1003,7 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.TryBindNull("@ThemeSongIds");
}
- if (item.ThemeVideoIds.Count > 0)
+ if (item.ThemeVideoIds.Length > 0)
{
saveItemStatement.TryBind("@ThemeVideoIds", string.Join("|", item.ThemeVideoIds.ToArray()));
}
@@ -1089,9 +1090,9 @@ namespace Emby.Server.Implementations.Data
private string SerializeImages(BaseItem item)
{
- var images = item.ImageInfos.ToList();
+ var images = item.ImageInfos;
- if (images.Count == 0)
+ if (images.Length == 0)
{
return null;
}
@@ -1108,22 +1109,24 @@ namespace Emby.Server.Implementations.Data
return;
}
- if (item.ImageInfos.Count > 0)
+ if (item.ImageInfos.Length > 0)
{
return;
}
var parts = value.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
-
+ var list = new List<ItemImageInfo>();
foreach (var part in parts)
{
var image = ItemImageInfoFromValueString(part);
if (image != null)
{
- item.ImageInfos.Add(image);
+ list.Add(image);
}
}
+
+ item.ImageInfos = list.ToArray(list.Count);
}
public string ToValueString(ItemImageInfo image)
@@ -1678,7 +1681,7 @@ namespace Emby.Server.Implementations.Data
return parsedValue;
}
return (MetadataFields?)null;
- }).Where(i => i.HasValue).Select(i => i.Value).ToList();
+ }).Where(i => i.HasValue).Select(i => i.Value).ToArray();
}
index++;
}
@@ -1687,7 +1690,7 @@ namespace Emby.Server.Implementations.Data
{
if (!reader.IsDBNull(index))
{
- item.Studios = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+ item.Studios = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
}
index++;
}
@@ -1696,7 +1699,7 @@ namespace Emby.Server.Implementations.Data
{
if (!reader.IsDBNull(index))
{
- item.Tags = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+ item.Tags = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
}
index++;
}
@@ -1873,7 +1876,7 @@ namespace Emby.Server.Implementations.Data
{
if (!reader.IsDBNull(index))
{
- item.ProductionLocations = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+ item.ProductionLocations = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
}
index++;
}
@@ -1882,7 +1885,7 @@ namespace Emby.Server.Implementations.Data
{
if (!reader.IsDBNull(index))
{
- item.ThemeSongIds = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => new Guid(i)).ToList();
+ item.ThemeSongIds = SplitToGuids(reader.GetString(index));
}
index++;
}
@@ -1891,7 +1894,7 @@ namespace Emby.Server.Implementations.Data
{
if (!reader.IsDBNull(index))
{
- item.ThemeVideoIds = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => new Guid(i)).ToList();
+ item.ThemeVideoIds = SplitToGuids(reader.GetString(index));
}
index++;
}
@@ -1950,12 +1953,26 @@ namespace Emby.Server.Implementations.Data
return item;
}
+ private Guid[] SplitToGuids(string value)
+ {
+ var ids = value.Split('|');
+
+ var result = new Guid[ids.Length];
+
+ for (var i = 0; i < result.Length; i++)
+ {
+ result[i] = new Guid(ids[i]);
+ }
+
+ return result;
+ }
+
/// <summary>
/// Gets the critic reviews.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <returns>Task{IEnumerable{ItemReview}}.</returns>
- public IEnumerable<ItemReview> GetCriticReviews(Guid itemId)
+ public List<ItemReview> GetCriticReviews(Guid itemId)
{
return new List<ItemReview>();
}
@@ -2206,7 +2223,7 @@ namespace Emby.Server.Implementations.Data
return false;
}
- private List<ItemFields> allFields = Enum.GetNames(typeof(ItemFields))
+ private readonly List<ItemFields> allFields = Enum.GetNames(typeof(ItemFields))
.Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
.ToList();
@@ -2548,11 +2565,11 @@ namespace Emby.Server.Implementations.Data
}
}
- query.ExcludeItemIds = excludeIds.ToArray();
+ query.ExcludeItemIds = excludeIds.ToArray(excludeIds.Count);
query.ExcludeProviderIds = item.ProviderIds;
}
- return list.ToArray();
+ return list.ToArray(list.Count);
}
private void BindSimilarParams(InternalItemsQuery query, IStatement statement)
@@ -2595,7 +2612,7 @@ namespace Emby.Server.Implementations.Data
if (groups.Count > 0)
{
- return " Group by " + string.Join(",", groups.ToArray());
+ return " Group by " + string.Join(",", groups.ToArray(groups.Count));
}
return string.Empty;
@@ -2632,7 +2649,7 @@ namespace Emby.Server.Implementations.Data
var whereText = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count));
commandText += whereText;
@@ -2689,7 +2706,7 @@ namespace Emby.Server.Implementations.Data
var whereText = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count));
commandText += whereText;
@@ -2842,7 +2859,7 @@ namespace Emby.Server.Implementations.Data
var returnList = GetItemList(query);
return new QueryResult<BaseItem>
{
- Items = returnList.ToArray(),
+ Items = returnList.ToArray(returnList.Count),
TotalRecordCount = returnList.Count
};
}
@@ -2865,7 +2882,7 @@ namespace Emby.Server.Implementations.Data
var whereText = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count));
var whereTextWithoutPaging = whereText;
@@ -2926,8 +2943,7 @@ namespace Emby.Server.Implementations.Data
return connection.RunInTransaction(db =>
{
var result = new QueryResult<BaseItem>();
- var statements = PrepareAllSafe(db, statementTexts)
- .ToList();
+ var statements = PrepareAllSafe(db, statementTexts);
if (!isReturningZeroItems)
{
@@ -2981,7 +2997,7 @@ namespace Emby.Server.Implementations.Data
LogQueryTime("GetItems", commandText, now);
- result.Items = list.ToArray();
+ result.Items = list.ToArray(list.Count);
return result;
}, ReadTransactionMode);
@@ -3133,7 +3149,7 @@ namespace Emby.Server.Implementations.Data
var whereText = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count));
commandText += whereText;
@@ -3204,7 +3220,7 @@ namespace Emby.Server.Implementations.Data
var whereText = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count));
commandText += whereText;
@@ -3277,7 +3293,7 @@ namespace Emby.Server.Implementations.Data
var returnList = GetItemIdsList(query);
return new QueryResult<Guid>
{
- Items = returnList.ToArray(),
+ Items = returnList.ToArray(returnList.Count),
TotalRecordCount = returnList.Count
};
}
@@ -3292,7 +3308,7 @@ namespace Emby.Server.Implementations.Data
var whereText = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count));
var whereTextWithoutPaging = whereText;
@@ -3355,8 +3371,7 @@ namespace Emby.Server.Implementations.Data
{
var result = new QueryResult<Guid>();
- var statements = PrepareAllSafe(db, statementTexts)
- .ToList();
+ var statements = PrepareAllSafe(db, statementTexts);
if (!isReturningZeroItems)
{
@@ -3399,7 +3414,7 @@ namespace Emby.Server.Implementations.Data
LogQueryTime("GetItemIds", commandText, now);
- result.Items = list.ToArray();
+ result.Items = list.ToArray(list.Count);
return result;
}, ReadTransactionMode);
@@ -3604,7 +3619,7 @@ namespace Emby.Server.Implementations.Data
}
if (programAttribtues.Count > 0)
{
- whereClauses.Add("(" + string.Join(" OR ", programAttribtues.ToArray()) + ")");
+ whereClauses.Add("(" + string.Join(" OR ", programAttribtues.ToArray(programAttribtues.Count)) + ")");
}
}
@@ -5129,9 +5144,9 @@ namespace Emby.Server.Implementations.Data
var itemCountColumns = new List<Tuple<string, string>>();
- var typesToCount = query.IncludeItemTypes.ToList();
+ var typesToCount = query.IncludeItemTypes;
- if (typesToCount.Count > 0)
+ if (typesToCount.Length > 0)
{
var itemCountColumnQuery = "select group_concat(type, '|')" + GetFromText("B");
@@ -5191,7 +5206,7 @@ namespace Emby.Server.Implementations.Data
var whereText = " where Type=@SelectType";
- if (typesToCount.Count == 0)
+ if (typesToCount.Length == 0)
{
whereText += " And CleanName In (Select CleanValue from ItemValues where " + typeClause + " AND ItemId in (select guid from TypedBaseItems" + innerWhereText + "))";
}
@@ -5269,8 +5284,7 @@ namespace Emby.Server.Implementations.Data
var list = new List<Tuple<BaseItem, ItemCounts>>();
var result = new QueryResult<Tuple<BaseItem, ItemCounts>>();
- var statements = PrepareAllSafe(db, statementTexts)
- .ToList();
+ var statements = PrepareAllSafe(db, statementTexts);
if (!isReturningZeroItems)
{
@@ -5345,7 +5359,7 @@ namespace Emby.Server.Implementations.Data
{
result.TotalRecordCount = list.Count;
}
- result.Items = list.ToArray();
+ result.Items = list.ToArray(list.Count);
return result;
@@ -5354,11 +5368,11 @@ namespace Emby.Server.Implementations.Data
}
}
- private ItemCounts GetItemCounts(IReadOnlyList<IResultSetValue> reader, int countStartColumn, List<string> typesToCount)
+ private ItemCounts GetItemCounts(IReadOnlyList<IResultSetValue> reader, int countStartColumn, string[] typesToCount)
{
var counts = new ItemCounts();
- if (typesToCount.Count == 0)
+ if (typesToCount.Length == 0)
{
return counts;
}
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 80576742e..098e11720 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -649,12 +649,12 @@ namespace Emby.Server.Implementations.Dto
dto.GameSystem = item.GameSystemName;
}
- private List<string> GetImageTags(BaseItem item, List<ItemImageInfo> images)
+ private string[] GetImageTags(BaseItem item, List<ItemImageInfo> images)
{
return images
.Select(p => GetImageCacheTag(item, p))
.Where(i => i != null)
- .ToList();
+ .ToArray();
}
private string GetImageCacheTag(BaseItem item, ImageType type)
@@ -766,7 +766,7 @@ namespace Emby.Server.Implementations.Dto
}
}
- dto.People = list.ToArray();
+ dto.People = list.ToArray(list.Count);
}
/// <summary>
@@ -1049,12 +1049,12 @@ namespace Emby.Server.Implementations.Dto
{
if (!string.IsNullOrWhiteSpace(item.Tagline))
{
- dto.Taglines = new List<string> { item.Tagline };
+ dto.Taglines = new string[] { item.Tagline };
}
if (dto.Taglines == null)
{
- dto.Taglines = new List<string>();
+ dto.Taglines = new string[]{};
}
}
@@ -1430,9 +1430,9 @@ namespace Emby.Server.Implementations.Dto
if (fields.Contains(ItemFields.ProductionLocations))
{
- if (item.ProductionLocations.Count > 0 || item is Movie)
+ if (item.ProductionLocations.Length > 0 || item is Movie)
{
- dto.ProductionLocations = item.ProductionLocations.ToArray();
+ dto.ProductionLocations = item.ProductionLocations;
}
}
@@ -1581,12 +1581,12 @@ namespace Emby.Server.Implementations.Dto
/// <param name="dto">The dto.</param>
/// <param name="item">The item.</param>
/// <returns>Task.</returns>
- public void AttachPrimaryImageAspectRatio(IItemDto dto, IHasImages item)
+ public void AttachPrimaryImageAspectRatio(IItemDto dto, IHasMetadata item)
{
dto.PrimaryImageAspectRatio = GetPrimaryImageAspectRatio(item);
}
- public double? GetPrimaryImageAspectRatio(IHasImages item)
+ public double? GetPrimaryImageAspectRatio(IHasMetadata item)
{
var imageInfo = item.GetImageInfo(ImageType.Primary, 0);
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index ed69c9755..62f23bb53 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -21,6 +21,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -40,6 +41,8 @@
<Compile Include="AppBase\BaseApplicationPaths.cs" />
<Compile Include="AppBase\BaseConfigurationManager.cs" />
<Compile Include="AppBase\ConfigurationHelper.cs" />
+ <Compile Include="ApplicationHost.cs" />
+ <Compile Include="ApplicationPathHelper.cs" />
<Compile Include="Branding\BrandingConfigurationFactory.cs" />
<Compile Include="Browser\BrowserLauncher.cs" />
<Compile Include="Channels\ChannelConfigurations.cs" />
@@ -52,6 +55,24 @@
<Compile Include="Collections\CollectionManager.cs" />
<Compile Include="Collections\CollectionsDynamicFolder.cs" />
<Compile Include="Configuration\ServerConfigurationManager.cs" />
+ <Compile Include="Cryptography\ASN1.cs" />
+ <Compile Include="Cryptography\ASN1Convert.cs" />
+ <Compile Include="Cryptography\BitConverterLE.cs" />
+ <Compile Include="Cryptography\CertificateGenerator.cs" />
+ <Compile Include="Cryptography\CryptoConvert.cs" />
+ <Compile Include="Cryptography\PfxGenerator.cs" />
+ <Compile Include="Cryptography\PKCS1.cs" />
+ <Compile Include="Cryptography\PKCS12.cs" />
+ <Compile Include="Cryptography\PKCS7.cs" />
+ <Compile Include="Cryptography\PKCS8.cs" />
+ <Compile Include="Cryptography\X501Name.cs" />
+ <Compile Include="Cryptography\X509Builder.cs" />
+ <Compile Include="Cryptography\X509Certificate.cs" />
+ <Compile Include="Cryptography\X509CertificateBuilder.cs" />
+ <Compile Include="Cryptography\X509CertificateCollection.cs" />
+ <Compile Include="Cryptography\X509Extension.cs" />
+ <Compile Include="Cryptography\X509Extensions.cs" />
+ <Compile Include="Cryptography\X520Attributes.cs" />
<Compile Include="Data\ManagedConnection.cs" />
<Compile Include="Data\SqliteDisplayPreferencesRepository.cs" />
<Compile Include="Data\SqliteItemRepository.cs" />
@@ -63,6 +84,7 @@
<Compile Include="Devices\DeviceRepository.cs" />
<Compile Include="Dto\DtoService.cs" />
<Compile Include="EntryPoints\AutomaticRestartEntryPoint.cs" />
+ <Compile Include="EntryPoints\ExternalPortForwarding.cs" />
<Compile Include="EntryPoints\KeepServerAwake.cs" />
<Compile Include="EntryPoints\LibraryChangedNotifier.cs" />
<Compile Include="EntryPoints\LoadRegistrations.cs" />
@@ -78,6 +100,7 @@
<Compile Include="FFMpeg\FFMpegInfo.cs" />
<Compile Include="FFMpeg\FFMpegInstallInfo.cs" />
<Compile Include="FFMpeg\FFMpegLoader.cs" />
+ <Compile Include="HttpServerFactory.cs" />
<Compile Include="HttpServer\FileWriter.cs" />
<Compile Include="HttpServer\HttpListenerHost.cs" />
<Compile Include="HttpServer\HttpResultFactory.cs" />
@@ -99,7 +122,9 @@
<Compile Include="Images\BaseDynamicImageProvider.cs" />
<Compile Include="IO\AsyncStreamCopier.cs" />
<Compile Include="IO\FileRefresher.cs" />
+ <Compile Include="IO\LibraryMonitor.cs" />
<Compile Include="IO\MbLinkShortcutHandler.cs" />
+ <Compile Include="IO\MemoryStreamProvider.cs" />
<Compile Include="IO\ThrottledStream.cs" />
<Compile Include="Library\CoreResolutionIgnoreRule.cs" />
<Compile Include="Library\LibraryManager.cs" />
@@ -170,6 +195,8 @@
<Compile Include="LiveTv\TunerHosts\MulticastStream.cs" />
<Compile Include="LiveTv\TunerHosts\QueueStream.cs" />
<Compile Include="Localization\LocalizationManager.cs" />
+ <Compile Include="Localization\TextLocalizer.cs" />
+ <Compile Include="Logging\ConsoleLogger.cs" />
<Compile Include="Logging\UnhandledExceptionWriter.cs" />
<Compile Include="MediaEncoder\EncodingManager.cs" />
<Compile Include="Migrations\IVersionMigration.cs" />
@@ -249,6 +276,7 @@
<Compile Include="Sorting\StartDateComparer.cs" />
<Compile Include="Sorting\StudioComparer.cs" />
<Compile Include="StartupOptions.cs" />
+ <Compile Include="SystemEvents.cs" />
<Compile Include="TV\SeriesPostScanTask.cs" />
<Compile Include="TV\TVSeriesManager.cs" />
<Compile Include="Udp\UdpServer.cs" />
@@ -260,6 +288,26 @@
<EmbeddedResource Include="Localization\iso6392.txt" />
</ItemGroup>
<ItemGroup>
+ <ProjectReference Include="..\Emby.Common.Implementations\Emby.Common.Implementations.csproj">
+ <Project>{1e37a338-9f57-4b70-bd6d-bb9c591e319b}</Project>
+ <Name>Emby.Common.Implementations</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj">
+ <Project>{805844ab-e92f-45e6-9d99-4f6d48d129a5}</Project>
+ <Name>Emby.Dlna</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Emby.Drawing\Emby.Drawing.csproj">
+ <Project>{08fff49b-f175-4807-a2b5-73b0ebd9f716}</Project>
+ <Name>Emby.Drawing</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj">
+ <Project>{89ab4548-770d-41fd-a891-8daff44f452c}</Project>
+ <Name>Emby.Photos</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj">
+ <Project>{4fd51ac5-2c16-4308-a993-c3a84f3b4582}</Project>
+ <Name>MediaBrowser.Api</Name>
+ </ProjectReference>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
<Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
<Name>MediaBrowser.Common</Name>
@@ -268,6 +316,10 @@
<Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
<Name>MediaBrowser.Controller</Name>
</ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj">
+ <Project>{7ef9f3e0-697d-42f3-a08f-19deb5f84392}</Project>
+ <Name>MediaBrowser.LocalMetadata</Name>
+ </ProjectReference>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
<Name>MediaBrowser.Model</Name>
@@ -280,10 +332,29 @@
<Project>{2e781478-814d-4a48-9d80-bff206441a65}</Project>
<Name>MediaBrowser.Server.Implementations</Name>
</ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj">
+ <Project>{5624b7b5-b5a7-41d8-9f10-cc5611109619}</Project>
+ <Name>MediaBrowser.WebDashboard</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj">
+ <Project>{23499896-b135-4527-8574-c26e926ea99e}</Project>
+ <Name>MediaBrowser.XbmcMetadata</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj">
+ <Project>{cb7f2326-6497-4a3d-ba03-48513b17a7be}</Project>
+ <Name>Mono.Nat</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\OpenSubtitlesHandler\OpenSubtitlesHandler.csproj">
+ <Project>{4a4402d4-e910-443b-b8fc-2c18286a2ca0}</Project>
+ <Name>OpenSubtitlesHandler</Name>
+ </ProjectReference>
<ProjectReference Include="..\SocketHttpListener\SocketHttpListener.csproj">
<Project>{1d74413b-e7cf-455b-b021-f52bdf881542}</Project>
<Name>SocketHttpListener</Name>
</ProjectReference>
+ <Reference Include="Emby.Server.MediaEncoding">
+ <HintPath>..\ThirdParty\emby\Emby.Server.MediaEncoding.dll</HintPath>
+ </Reference>
<Reference Include="Emby.XmlTv, Version=1.0.6387.29335, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Emby.XmlTv.1.0.9\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath>
<Private>True</Private>
@@ -292,6 +363,15 @@
<HintPath>..\packages\MediaBrowser.Naming.1.0.5\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath>
<Private>True</Private>
</Reference>
+ <Reference Include="Microsoft.IO.RecyclableMemoryStream, Version=1.2.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <HintPath>..\packages\Microsoft.IO.RecyclableMemoryStream.1.2.2\lib\net45\Microsoft.IO.RecyclableMemoryStream.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack.Text, Version=4.5.12.0, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\ServiceStack.Text.4.5.12\lib\net45\ServiceStack.Text.dll</HintPath>
+ </Reference>
+ <Reference Include="SimpleInjector, Version=4.0.8.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
+ <HintPath>..\packages\SimpleInjector.4.0.8\lib\net45\SimpleInjector.dll</HintPath>
+ </Reference>
<Reference Include="SQLitePCL.pretty, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\SQLitePCL.pretty.1.1.0\lib\portable-net45+netcore45+wpa81+wp8\SQLitePCL.pretty.dll</HintPath>
<Private>True</Private>
@@ -299,10 +379,10 @@
</ItemGroup>
<ItemGroup>
<Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
- <HintPath>..\packages\SQLitePCLRaw.core.1.1.7\lib\net45\SQLitePCLRaw.core.dll</HintPath>
- <Private>True</Private>
+ <HintPath>..\packages\SQLitePCLRaw.core.1.1.8\lib\net45\SQLitePCLRaw.core.dll</HintPath>
</Reference>
<Reference Include="System" />
+ <Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Xml.Linq" />
diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
new file mode 100644
index 000000000..c96799b2f
--- /dev/null
+++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
@@ -0,0 +1,335 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Threading;
+using Mono.Nat;
+using MediaBrowser.Model.Extensions;
+
+namespace Emby.Server.Implementations.EntryPoints
+{
+ public class ExternalPortForwarding : IServerEntryPoint
+ {
+ private readonly IServerApplicationHost _appHost;
+ private readonly ILogger _logger;
+ private readonly IHttpClient _httpClient;
+ private readonly IServerConfigurationManager _config;
+ private readonly IDeviceDiscovery _deviceDiscovery;
+
+ private ITimer _timer;
+ private bool _isStarted;
+ private readonly ITimerFactory _timerFactory;
+
+ public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, ITimerFactory timerFactory)
+ {
+ _logger = logmanager.GetLogger("PortMapper");
+ _appHost = appHost;
+ _config = config;
+ _deviceDiscovery = deviceDiscovery;
+ _httpClient = httpClient;
+ _timerFactory = timerFactory;
+ }
+
+ private string _lastConfigIdentifier;
+ private string GetConfigIdentifier()
+ {
+ var values = new List<string>();
+ var config = _config.Configuration;
+
+ values.Add(config.EnableUPnP.ToString());
+ values.Add(config.PublicPort.ToString(CultureInfo.InvariantCulture));
+ values.Add(_appHost.HttpPort.ToString(CultureInfo.InvariantCulture));
+ values.Add(_appHost.HttpsPort.ToString(CultureInfo.InvariantCulture));
+ values.Add(config.EnableHttps.ToString());
+ values.Add(_appHost.EnableHttps.ToString());
+
+ return string.Join("|", values.ToArray(values.Count));
+ }
+
+ void _config_ConfigurationUpdated(object sender, EventArgs e)
+ {
+ if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
+ {
+ if (_isStarted)
+ {
+ DisposeNat();
+ }
+
+ Run();
+ }
+ }
+
+ public void Run()
+ {
+ NatUtility.Logger = _logger;
+ NatUtility.HttpClient = _httpClient;
+
+ if (_config.Configuration.EnableUPnP)
+ {
+ Start();
+ }
+
+ _config.ConfigurationUpdated -= _config_ConfigurationUpdated;
+ _config.ConfigurationUpdated += _config_ConfigurationUpdated;
+ }
+
+ private void Start()
+ {
+ _logger.Debug("Starting NAT discovery");
+ NatUtility.EnabledProtocols = new List<NatProtocol>
+ {
+ NatProtocol.Pmp
+ };
+ NatUtility.DeviceFound += NatUtility_DeviceFound;
+
+ // Mono.Nat does never rise this event. The event is there however it is useless.
+ // You could remove it with no risk.
+ NatUtility.DeviceLost += NatUtility_DeviceLost;
+
+
+ NatUtility.StartDiscovery();
+
+ _timer = _timerFactory.Create(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
+
+ _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
+
+ _lastConfigIdentifier = GetConfigIdentifier();
+
+ _isStarted = true;
+ }
+
+ private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ var info = e.Argument;
+
+ string usn;
+ if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
+
+ string nt;
+ if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
+
+ // Filter device type
+ if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
+ nt.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
+ usn.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
+ nt.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1)
+ {
+ return;
+ }
+
+ var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn;
+
+ if (info.Location == null)
+ {
+ return;
+ }
+
+ lock (_usnsHandled)
+ {
+ if (_usnsHandled.Contains(identifier))
+ {
+ return;
+ }
+ _usnsHandled.Add(identifier);
+ }
+
+ _logger.Debug("Found NAT device: " + identifier);
+
+ IPAddress address;
+ if (IPAddress.TryParse(info.Location.Host, out address))
+ {
+ // The Handle method doesn't need the port
+ var endpoint = new IPEndPoint(address, info.Location.Port);
+
+ IPAddress localAddress = null;
+
+ try
+ {
+ var localAddressString = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
+
+ Uri uri;
+ if (Uri.TryCreate(localAddressString, UriKind.Absolute, out uri))
+ {
+ localAddressString = uri.Host;
+
+ if (!IPAddress.TryParse(localAddressString, out localAddress))
+ {
+ return;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ return;
+ }
+
+ if (_disposed)
+ {
+ return;
+ }
+
+ _logger.Debug("Calling Nat.Handle on " + identifier);
+ NatUtility.Handle(localAddress, info, endpoint, NatProtocol.Upnp);
+ }
+ }
+
+ private void ClearCreatedRules(object state)
+ {
+ lock (_createdRules)
+ {
+ _createdRules.Clear();
+ }
+ lock (_usnsHandled)
+ {
+ _usnsHandled.Clear();
+ }
+ }
+
+ void NatUtility_DeviceFound(object sender, DeviceEventArgs e)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ try
+ {
+ var device = e.Device;
+ _logger.Debug("NAT device found: {0}", device.LocalAddress.ToString());
+
+ CreateRules(device);
+ }
+ catch
+ {
+ // I think it could be a good idea to log the exception because
+ // you are using permanent portmapping here (never expire) and that means that next time
+ // CreatePortMap is invoked it can fails with a 718-ConflictInMappingEntry or not. That depends
+ // on the router's upnp implementation (specs says it should fail however some routers don't do it)
+ // It also can fail with others like 727-ExternalPortOnlySupportsWildcard, 728-NoPortMapsAvailable
+ // and those errors (upnp errors) could be useful for diagnosting.
+
+ // Commenting out because users are reporting problems out of our control
+ //_logger.ErrorException("Error creating port forwarding rules", ex);
+ }
+ }
+
+ private List<string> _createdRules = new List<string>();
+ private List<string> _usnsHandled = new List<string>();
+ private async void CreateRules(INatDevice device)
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException("PortMapper");
+ }
+
+ // On some systems the device discovered event seems to fire repeatedly
+ // This check will help ensure we're not trying to port map the same device over and over
+
+ var address = device.LocalAddress.ToString();
+
+ lock (_createdRules)
+ {
+ if (!_createdRules.Contains(address))
+ {
+ _createdRules.Add(address);
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ var success = await CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort).ConfigureAwait(false);
+
+ if (success)
+ {
+ await CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort).ConfigureAwait(false);
+ }
+ }
+
+ private async Task<bool> CreatePortMap(INatDevice device, int privatePort, int publicPort)
+ {
+ _logger.Debug("Creating port map on port {0}", privatePort);
+
+ try
+ {
+ await device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort)
+ {
+ Description = _appHost.Name
+
+ }).ConfigureAwait(false);
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ _logger.Error("Error creating port map: " + ex.Message);
+
+ return false;
+ }
+ }
+
+ // As I said before, this method will be never invoked. You can remove it.
+ void NatUtility_DeviceLost(object sender, DeviceEventArgs e)
+ {
+ var device = e.Device;
+ _logger.Debug("NAT device lost: {0}", device.LocalAddress.ToString());
+ }
+
+ private bool _disposed = false;
+ public void Dispose()
+ {
+ _disposed = true;
+ DisposeNat();
+ }
+
+ private void DisposeNat()
+ {
+ _logger.Debug("Stopping NAT discovery");
+
+ if (_timer != null)
+ {
+ _timer.Dispose();
+ _timer = null;
+ }
+
+ _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
+
+ try
+ {
+ // This is not a significant improvement
+ NatUtility.StopDiscovery();
+ NatUtility.DeviceFound -= NatUtility_DeviceFound;
+ NatUtility.DeviceLost -= NatUtility_DeviceLost;
+ }
+ // Statements in try-block will no fail because StopDiscovery is a one-line
+ // method that was no chances to fail.
+ // public static void StopDiscovery ()
+ // {
+ // searching.Reset();
+ // }
+ // IMO you could remove the catch-block
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error stopping NAT Discovery", ex);
+ }
+ finally
+ {
+ _isStarted = false;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs
index 9fbe06673..99d39ffe0 100644
--- a/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs
+++ b/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs
@@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.EntryPoints
{
@@ -58,7 +59,7 @@ namespace Emby.Server.Implementations.EntryPoints
session.ApplicationVersion
};
- var key = string.Join("_", keys.ToArray()).GetMD5();
+ var key = string.Join("_", keys.ToArray(keys.Count)).GetMD5();
_apps.GetOrAdd(key, guid => GetNewClientInfo(session));
}
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
index 05f78eba9..f150e4785 100644
--- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -125,13 +125,6 @@ namespace Emby.Server.Implementations.HttpServer
return _appHost.CreateInstance(type);
}
- private ServiceController CreateServiceController()
- {
- var types = _restServices.Select(r => r.GetType()).ToArray();
-
- return new ServiceController(() => types);
- }
-
/// <summary>
/// Applies the request filters. Returns whether or not the request has been handled
/// and no more processing should be done.
@@ -186,7 +179,7 @@ namespace Emby.Server.Implementations.HttpServer
attributes.Sort((x, y) => x.Priority - y.Priority);
- return attributes.ToArray();
+ return attributes.ToArray(attributes.Count);
}
/// <summary>
@@ -697,11 +690,13 @@ namespace Emby.Server.Implementations.HttpServer
{
_restServices.AddRange(services);
- ServiceController = CreateServiceController();
+ ServiceController = new ServiceController();
_logger.Info("Calling ServiceStack AppHost.Init");
- ServiceController.Init(this);
+ var types = _restServices.Select(r => r.GetType()).ToArray();
+
+ ServiceController.Init(this, types);
var requestFilters = _appHost.GetExports<IRequestFilter>().ToList();
foreach (var filter in requestFilters)
@@ -741,7 +736,7 @@ namespace Emby.Server.Implementations.HttpServer
});
}
- return routes.ToArray();
+ return routes.ToArray(routes.Count);
}
public Func<string, object> GetParseFn(Type propertyType)
diff --git a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs
index f0e75eea4..de30dc30a 100644
--- a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs
+++ b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs
@@ -4,6 +4,7 @@ using System.Globalization;
using System.Linq;
using MediaBrowser.Model.Services;
using SocketHttpListener.Net;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.HttpServer
{
@@ -29,7 +30,7 @@ namespace Emby.Server.Implementations.HttpServer
}
else
{
- var headerText = string.Join(", ", headers.Select(i => i.Name + "=" + i.Value).ToArray());
+ var headerText = string.Join(", ", headers.Select(i => i.Name + "=" + i.Value).ToArray(headers.Count));
logger.Info("HTTP {0} {1}. {2}", method, url, headerText);
}
diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/HttpUtility.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/HttpUtility.cs
index 4fbe0ed94..4e8dd7362 100644
--- a/Emby.Server.Implementations/HttpServer/SocketSharp/HttpUtility.cs
+++ b/Emby.Server.Implementations/HttpServer/SocketSharp/HttpUtility.cs
@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Text;
using MediaBrowser.Model.Services;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.HttpServer.SocketSharp
{
@@ -585,7 +586,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
WriteCharBytes(bytes, ch, e);
}
- byte[] buf = bytes.ToArray();
+ byte[] buf = bytes.ToArray(bytes.Count);
bytes = null;
return e.GetString(buf, 0, buf.Length);
diff --git a/Emby.Server.Implementations/HttpServerFactory.cs b/Emby.Server.Implementations/HttpServerFactory.cs
new file mode 100644
index 000000000..b1d78e6f4
--- /dev/null
+++ b/Emby.Server.Implementations/HttpServerFactory.cs
@@ -0,0 +1,110 @@
+using System;
+using System.IO;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
+using Emby.Common.Implementations.Net;
+using Emby.Server.Implementations.HttpServer;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Cryptography;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
+using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.System;
+using MediaBrowser.Model.Text;
+using ServiceStack.Text.Jsv;
+using SocketHttpListener.Primitives;
+
+namespace Emby.Server.Implementations
+{
+ /// <summary>
+ /// Class ServerFactory
+ /// </summary>
+ public static class HttpServerFactory
+ {
+ /// <summary>
+ /// Creates the server.
+ /// </summary>
+ /// <returns>IHttpServer.</returns>
+ public static IHttpServer CreateServer(IServerApplicationHost applicationHost,
+ ILogManager logManager,
+ IServerConfigurationManager config,
+ INetworkManager networkmanager,
+ IMemoryStreamFactory streamProvider,
+ string serverName,
+ string defaultRedirectpath,
+ ITextEncoding textEncoding,
+ ISocketFactory socketFactory,
+ ICryptoProvider cryptoProvider,
+ IJsonSerializer json,
+ IXmlSerializer xml,
+ IEnvironmentInfo environment,
+ ICertificate certificate,
+ IFileSystem fileSystem,
+ bool enableDualModeSockets)
+ {
+ var logger = logManager.GetLogger("HttpServer");
+
+ return new HttpListenerHost(applicationHost,
+ logger,
+ config,
+ serverName,
+ defaultRedirectpath,
+ networkmanager,
+ streamProvider,
+ textEncoding,
+ socketFactory,
+ cryptoProvider,
+ json,
+ xml,
+ environment,
+ certificate,
+ new StreamFactory(),
+ GetParseFn,
+ enableDualModeSockets,
+ fileSystem);
+ }
+
+ private static Func<string, object> GetParseFn(Type propertyType)
+ {
+ return s => JsvReader.GetParseFn(propertyType)(s);
+ }
+ }
+
+ public class StreamFactory : IStreamFactory
+ {
+ public Stream CreateNetworkStream(IAcceptSocket acceptSocket, bool ownsSocket)
+ {
+ var netSocket = (NetAcceptSocket)acceptSocket;
+
+ return new SocketStream(netSocket.Socket, ownsSocket);
+ }
+
+ public Task AuthenticateSslStreamAsServer(Stream stream, ICertificate certificate)
+ {
+ var sslStream = (SslStream)stream;
+ var cert = (Certificate)certificate;
+
+ return sslStream.AuthenticateAsServerAsync(cert.X509Certificate);
+ }
+
+ public Stream CreateSslStream(Stream innerStream, bool leaveInnerStreamOpen)
+ {
+ return new SslStream(innerStream, leaveInnerStreamOpen);
+ }
+ }
+
+ public class Certificate : ICertificate
+ {
+ public Certificate(X509Certificate x509Certificate)
+ {
+ X509Certificate = x509Certificate;
+ }
+
+ public X509Certificate X509Certificate { get; private set; }
+ }
+}
diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs
new file mode 100644
index 000000000..c452c01be
--- /dev/null
+++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs
@@ -0,0 +1,652 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.System;
+using MediaBrowser.Model.Tasks;
+using MediaBrowser.Model.Threading;
+
+namespace Emby.Server.Implementations.IO
+{
+ public class LibraryMonitor : ILibraryMonitor
+ {
+ /// <summary>
+ /// The file system watchers
+ /// </summary>
+ private readonly ConcurrentDictionary<string, FileSystemWatcher> _fileSystemWatchers = new ConcurrentDictionary<string, FileSystemWatcher>(StringComparer.OrdinalIgnoreCase);
+ /// <summary>
+ /// The affected paths
+ /// </summary>
+ private readonly List<FileRefresher> _activeRefreshers = new List<FileRefresher>();
+
+ /// <summary>
+ /// A dynamic list of paths that should be ignored. Added to during our own file sytem modifications.
+ /// </summary>
+ private readonly ConcurrentDictionary<string, string> _tempIgnoredPaths = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+ /// <summary>
+ /// Any file name ending in any of these will be ignored by the watchers
+ /// </summary>
+ private readonly IReadOnlyList<string> _alwaysIgnoreFiles = new List<string>
+ {
+ "small.jpg",
+ "albumart.jpg",
+
+ // WMC temp recording directories that will constantly be written to
+ "TempRec",
+ "TempSBE"
+ };
+
+ private readonly IReadOnlyList<string> _alwaysIgnoreSubstrings = new List<string>
+ {
+ // Synology
+ "eaDir",
+ "#recycle",
+ ".wd_tv",
+ ".actors"
+ };
+
+ private readonly IReadOnlyList<string> _alwaysIgnoreExtensions = new List<string>
+ {
+ // thumbs.db
+ ".db",
+
+ // bts sync files
+ ".bts",
+ ".sync"
+ };
+
+ /// <summary>
+ /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ private void TemporarilyIgnore(string path)
+ {
+ _tempIgnoredPaths[path] = path;
+ }
+
+ public void ReportFileSystemChangeBeginning(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ TemporarilyIgnore(path);
+ }
+
+ public bool IsPathLocked(string path)
+ {
+ var lockedPaths = _tempIgnoredPaths.Keys.ToList();
+ return lockedPaths.Any(i => _fileSystem.AreEqual(i, path) || _fileSystem.ContainsSubPath(i, path));
+ }
+
+ public async void ReportFileSystemChangeComplete(string path, bool refreshPath)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ // This is an arbitraty amount of time, but delay it because file system writes often trigger events long after the file was actually written to.
+ // Seeing long delays in some situations, especially over the network, sometimes up to 45 seconds
+ // But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata
+ await Task.Delay(45000).ConfigureAwait(false);
+
+ string val;
+ _tempIgnoredPaths.TryRemove(path, out val);
+
+ if (refreshPath)
+ {
+ try
+ {
+ ReportFileSystemChanged(path);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error in ReportFileSystemChanged for {0}", ex, path);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the logger.
+ /// </summary>
+ /// <value>The logger.</value>
+ private ILogger Logger { get; set; }
+
+ /// <summary>
+ /// Gets or sets the task manager.
+ /// </summary>
+ /// <value>The task manager.</value>
+ private ITaskManager TaskManager { get; set; }
+
+ private ILibraryManager LibraryManager { get; set; }
+ private IServerConfigurationManager ConfigurationManager { get; set; }
+
+ private readonly IFileSystem _fileSystem;
+ private readonly ITimerFactory _timerFactory;
+ private readonly IEnvironmentInfo _environmentInfo;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="LibraryMonitor" /> class.
+ /// </summary>
+ public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ITimerFactory timerFactory, ISystemEvents systemEvents, IEnvironmentInfo environmentInfo)
+ {
+ if (taskManager == null)
+ {
+ throw new ArgumentNullException("taskManager");
+ }
+
+ LibraryManager = libraryManager;
+ TaskManager = taskManager;
+ Logger = logManager.GetLogger(GetType().Name);
+ ConfigurationManager = configurationManager;
+ _fileSystem = fileSystem;
+ _timerFactory = timerFactory;
+ _environmentInfo = environmentInfo;
+
+ systemEvents.Resume += _systemEvents_Resume;
+ }
+
+ private void _systemEvents_Resume(object sender, EventArgs e)
+ {
+ Restart();
+ }
+
+ private void Restart()
+ {
+ Stop();
+
+ if (!_disposed)
+ {
+ Start();
+ }
+ }
+
+ private bool IsLibraryMonitorEnabaled(BaseItem item)
+ {
+ if (item is BasePluginFolder)
+ {
+ return false;
+ }
+
+ var options = LibraryManager.GetLibraryOptions(item);
+
+ if (options != null)
+ {
+ return options.EnableRealtimeMonitor;
+ }
+
+ return false;
+ }
+
+ public void Start()
+ {
+ LibraryManager.ItemAdded += LibraryManager_ItemAdded;
+ LibraryManager.ItemRemoved += LibraryManager_ItemRemoved;
+
+ var pathsToWatch = new List<string> { };
+
+ var paths = LibraryManager
+ .RootFolder
+ .Children
+ .Where(IsLibraryMonitorEnabaled)
+ .OfType<Folder>()
+ .SelectMany(f => f.PhysicalLocations)
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .OrderBy(i => i)
+ .ToList();
+
+ foreach (var path in paths)
+ {
+ if (!ContainsParentFolder(pathsToWatch, path))
+ {
+ pathsToWatch.Add(path);
+ }
+ }
+
+ foreach (var path in pathsToWatch)
+ {
+ StartWatchingPath(path);
+ }
+ }
+
+ private void StartWatching(BaseItem item)
+ {
+ if (IsLibraryMonitorEnabaled(item))
+ {
+ StartWatchingPath(item.Path);
+ }
+ }
+
+ /// <summary>
+ /// Handles the ItemRemoved event of the LibraryManager control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param>
+ void LibraryManager_ItemRemoved(object sender, ItemChangeEventArgs e)
+ {
+ if (e.Item.GetParent() is AggregateFolder)
+ {
+ StopWatchingPath(e.Item.Path);
+ }
+ }
+
+ /// <summary>
+ /// Handles the ItemAdded event of the LibraryManager control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param>
+ void LibraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
+ {
+ if (e.Item.GetParent() is AggregateFolder)
+ {
+ StartWatching(e.Item);
+ }
+ }
+
+ /// <summary>
+ /// Examine a list of strings assumed to be file paths to see if it contains a parent of
+ /// the provided path.
+ /// </summary>
+ /// <param name="lst">The LST.</param>
+ /// <param name="path">The path.</param>
+ /// <returns><c>true</c> if [contains parent folder] [the specified LST]; otherwise, <c>false</c>.</returns>
+ /// <exception cref="System.ArgumentNullException">path</exception>
+ private static bool ContainsParentFolder(IEnumerable<string> lst, string path)
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ path = path.TrimEnd(Path.DirectorySeparatorChar);
+
+ return lst.Any(str =>
+ {
+ //this should be a little quicker than examining each actual parent folder...
+ var compare = str.TrimEnd(Path.DirectorySeparatorChar);
+
+ return path.Equals(compare, StringComparison.OrdinalIgnoreCase) || (path.StartsWith(compare, StringComparison.OrdinalIgnoreCase) && path[compare.Length] == Path.DirectorySeparatorChar);
+ });
+ }
+
+ /// <summary>
+ /// Starts the watching path.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ private void StartWatchingPath(string path)
+ {
+ if (!_fileSystem.DirectoryExists(path))
+ {
+ // Seeing a crash in the mono runtime due to an exception being thrown on a different thread
+ Logger.Info("Skipping realtime monitor for {0} because the path does not exist", path);
+ return;
+ }
+
+ if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
+ {
+ if (path.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase) || path.StartsWith("smb://", StringComparison.OrdinalIgnoreCase))
+ {
+ // not supported
+ return;
+ }
+ }
+
+ // Already being watched
+ if (_fileSystemWatchers.ContainsKey(path))
+ {
+ return;
+ }
+
+ // Creating a FileSystemWatcher over the LAN can take hundreds of milliseconds, so wrap it in a Task to do them all in parallel
+ Task.Run(() =>
+ {
+ try
+ {
+ var newWatcher = new FileSystemWatcher(path, "*")
+ {
+ IncludeSubdirectories = true
+ };
+
+ if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows)
+ {
+ newWatcher.InternalBufferSize = 32767;
+ }
+
+ newWatcher.NotifyFilter = NotifyFilters.CreationTime |
+ NotifyFilters.DirectoryName |
+ NotifyFilters.FileName |
+ NotifyFilters.LastWrite |
+ NotifyFilters.Size |
+ NotifyFilters.Attributes;
+
+ newWatcher.Created += watcher_Changed;
+
+ // Seeing mono crashes on background threads we can't catch, testing if this might help
+ if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows)
+ {
+ newWatcher.Deleted += watcher_Changed;
+ }
+
+ newWatcher.Renamed += watcher_Changed;
+ newWatcher.Changed += watcher_Changed;
+
+ newWatcher.Error += watcher_Error;
+
+ if (_fileSystemWatchers.TryAdd(path, newWatcher))
+ {
+ newWatcher.EnableRaisingEvents = true;
+ Logger.Info("Watching directory " + path);
+ }
+ else
+ {
+ newWatcher.Dispose();
+ }
+
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error watching path: {0}", ex, path);
+ }
+ });
+ }
+
+ /// <summary>
+ /// Stops the watching path.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ private void StopWatchingPath(string path)
+ {
+ FileSystemWatcher watcher;
+
+ if (_fileSystemWatchers.TryGetValue(path, out watcher))
+ {
+ DisposeWatcher(watcher);
+ }
+ }
+
+ /// <summary>
+ /// Disposes the watcher.
+ /// </summary>
+ /// <param name="watcher">The watcher.</param>
+ private void DisposeWatcher(FileSystemWatcher watcher)
+ {
+ try
+ {
+ using (watcher)
+ {
+ Logger.Info("Stopping directory watching for path {0}", watcher.Path);
+
+ watcher.EnableRaisingEvents = false;
+ }
+ }
+ catch
+ {
+
+ }
+ finally
+ {
+ RemoveWatcherFromList(watcher);
+ }
+ }
+
+ /// <summary>
+ /// Removes the watcher from list.
+ /// </summary>
+ /// <param name="watcher">The watcher.</param>
+ private void RemoveWatcherFromList(FileSystemWatcher watcher)
+ {
+ FileSystemWatcher removed;
+
+ _fileSystemWatchers.TryRemove(watcher.Path, out removed);
+ }
+
+ /// <summary>
+ /// Handles the Error event of the watcher control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="ErrorEventArgs" /> instance containing the event data.</param>
+ void watcher_Error(object sender, ErrorEventArgs e)
+ {
+ var ex = e.GetException();
+ var dw = (FileSystemWatcher)sender;
+
+ Logger.ErrorException("Error in Directory watcher for: " + dw.Path, ex);
+
+ DisposeWatcher(dw);
+ }
+
+ /// <summary>
+ /// Handles the Changed event of the watcher control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="FileSystemEventArgs" /> instance containing the event data.</param>
+ void watcher_Changed(object sender, FileSystemEventArgs e)
+ {
+ try
+ {
+ //Logger.Debug("Changed detected of type " + e.ChangeType + " to " + e.FullPath);
+
+ var path = e.FullPath;
+
+ ReportFileSystemChanged(path);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Exception in ReportFileSystemChanged. Path: {0}", ex, e.FullPath);
+ }
+ }
+
+ public void ReportFileSystemChanged(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ var filename = Path.GetFileName(path);
+
+ var monitorPath = !string.IsNullOrEmpty(filename) &&
+ !_alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase) &&
+ !_alwaysIgnoreExtensions.Contains(Path.GetExtension(path) ?? string.Empty, StringComparer.OrdinalIgnoreCase) &&
+ _alwaysIgnoreSubstrings.All(i => path.IndexOf(i, StringComparison.OrdinalIgnoreCase) == -1);
+
+ // Ignore certain files
+ var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList();
+
+ // If the parent of an ignored path has a change event, ignore that too
+ if (tempIgnorePaths.Any(i =>
+ {
+ if (_fileSystem.AreEqual(i, path))
+ {
+ Logger.Debug("Ignoring change to {0}", path);
+ return true;
+ }
+
+ if (_fileSystem.ContainsSubPath(i, path))
+ {
+ Logger.Debug("Ignoring change to {0}", path);
+ return true;
+ }
+
+ // Go up a level
+ var parent = _fileSystem.GetDirectoryName(i);
+ if (!string.IsNullOrEmpty(parent))
+ {
+ if (_fileSystem.AreEqual(parent, path))
+ {
+ Logger.Debug("Ignoring change to {0}", path);
+ return true;
+ }
+ }
+
+ return false;
+
+ }))
+ {
+ monitorPath = false;
+ }
+
+ if (monitorPath)
+ {
+ // Avoid implicitly captured closure
+ CreateRefresher(path);
+ }
+ }
+
+ private void CreateRefresher(string path)
+ {
+ var parentPath = _fileSystem.GetDirectoryName(path);
+
+ lock (_activeRefreshers)
+ {
+ var refreshers = _activeRefreshers.ToList();
+ foreach (var refresher in refreshers)
+ {
+ // Path is already being refreshed
+ if (_fileSystem.AreEqual(path, refresher.Path))
+ {
+ refresher.RestartTimer();
+ return;
+ }
+
+ // Parent folder is already being refreshed
+ if (_fileSystem.ContainsSubPath(refresher.Path, path))
+ {
+ refresher.AddPath(path);
+ return;
+ }
+
+ // New path is a parent
+ if (_fileSystem.ContainsSubPath(path, refresher.Path))
+ {
+ refresher.ResetPath(path, null);
+ return;
+ }
+
+ // They are siblings. Rebase the refresher to the parent folder.
+ if (string.Equals(parentPath, _fileSystem.GetDirectoryName(refresher.Path), StringComparison.Ordinal))
+ {
+ refresher.ResetPath(parentPath, path);
+ return;
+ }
+ }
+
+ var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _timerFactory, _environmentInfo, LibraryManager);
+ newRefresher.Completed += NewRefresher_Completed;
+ _activeRefreshers.Add(newRefresher);
+ }
+ }
+
+ private void NewRefresher_Completed(object sender, EventArgs e)
+ {
+ var refresher = (FileRefresher)sender;
+ DisposeRefresher(refresher);
+ }
+
+ /// <summary>
+ /// Stops this instance.
+ /// </summary>
+ public void Stop()
+ {
+ LibraryManager.ItemAdded -= LibraryManager_ItemAdded;
+ LibraryManager.ItemRemoved -= LibraryManager_ItemRemoved;
+
+ foreach (var watcher in _fileSystemWatchers.Values.ToList())
+ {
+ watcher.Created -= watcher_Changed;
+ watcher.Deleted -= watcher_Changed;
+ watcher.Renamed -= watcher_Changed;
+ watcher.Changed -= watcher_Changed;
+
+ try
+ {
+ watcher.EnableRaisingEvents = false;
+ }
+ catch (InvalidOperationException)
+ {
+ // Seeing this under mono on linux sometimes
+ // Collection was modified; enumeration operation may not execute.
+ }
+
+ watcher.Dispose();
+ }
+
+ _fileSystemWatchers.Clear();
+ DisposeRefreshers();
+ }
+
+ private void DisposeRefresher(FileRefresher refresher)
+ {
+ lock (_activeRefreshers)
+ {
+ refresher.Dispose();
+ _activeRefreshers.Remove(refresher);
+ }
+ }
+
+ private void DisposeRefreshers()
+ {
+ lock (_activeRefreshers)
+ {
+ foreach (var refresher in _activeRefreshers.ToList())
+ {
+ refresher.Dispose();
+ }
+ _activeRefreshers.Clear();
+ }
+ }
+
+ private bool _disposed;
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ public void Dispose()
+ {
+ _disposed = true;
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected virtual void Dispose(bool dispose)
+ {
+ if (dispose)
+ {
+ Stop();
+ }
+ }
+ }
+
+ public class LibraryMonitorStartup : IServerEntryPoint
+ {
+ private readonly ILibraryMonitor _monitor;
+
+ public LibraryMonitorStartup(ILibraryMonitor monitor)
+ {
+ _monitor = monitor;
+ }
+
+ public void Run()
+ {
+ _monitor.Start();
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/IO/MemoryStreamProvider.cs b/Emby.Server.Implementations/IO/MemoryStreamProvider.cs
new file mode 100644
index 000000000..eca76203c
--- /dev/null
+++ b/Emby.Server.Implementations/IO/MemoryStreamProvider.cs
@@ -0,0 +1,56 @@
+using System.IO;
+using MediaBrowser.Model.IO;
+using Microsoft.IO;
+
+namespace Emby.Server.Implementations.IO
+{
+ public class RecyclableMemoryStreamProvider : IMemoryStreamFactory
+ {
+ readonly RecyclableMemoryStreamManager _manager = new RecyclableMemoryStreamManager();
+
+ public MemoryStream CreateNew()
+ {
+ return _manager.GetStream();
+ }
+
+ public MemoryStream CreateNew(int capacity)
+ {
+ return _manager.GetStream("RecyclableMemoryStream", capacity);
+ }
+
+ public MemoryStream CreateNew(byte[] buffer)
+ {
+ return _manager.GetStream("RecyclableMemoryStream", buffer, 0, buffer.Length);
+ }
+
+ public bool TryGetBuffer(MemoryStream stream, out byte[] buffer)
+ {
+ buffer = stream.GetBuffer();
+ return true;
+ }
+ }
+
+ public class MemoryStreamProvider : IMemoryStreamFactory
+ {
+ public MemoryStream CreateNew()
+ {
+ return new MemoryStream();
+ }
+
+ public MemoryStream CreateNew(int capacity)
+ {
+ return new MemoryStream(capacity);
+ }
+
+ public MemoryStream CreateNew(byte[] buffer)
+ {
+ return new MemoryStream(buffer);
+ }
+
+ public bool TryGetBuffer(MemoryStream stream, out byte[] buffer)
+ {
+ buffer = stream.GetBuffer();
+ return true;
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
index b8ce23a42..3f9ea79c6 100644
--- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
+++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
@@ -37,12 +37,12 @@ namespace Emby.Server.Implementations.Images
ImageProcessor = imageProcessor;
}
- protected virtual bool Supports(IHasImages item)
+ protected virtual bool Supports(IHasMetadata item)
{
return true;
}
- public virtual IEnumerable<ImageType> GetSupportedImages(IHasImages item)
+ public virtual IEnumerable<ImageType> GetSupportedImages(IHasMetadata item)
{
return new List<ImageType>
{
@@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.Images
};
}
- private IEnumerable<ImageType> GetEnabledImages(IHasImages item)
+ private IEnumerable<ImageType> GetEnabledImages(IHasMetadata item)
{
//var options = ProviderManager.GetMetadataOptions(item);
@@ -84,7 +84,7 @@ namespace Emby.Server.Implementations.Images
return updateType;
}
- protected async Task<ItemUpdateType> FetchAsync(IHasImages item, ImageType imageType, MetadataRefreshOptions options, CancellationToken cancellationToken)
+ protected async Task<ItemUpdateType> FetchAsync(IHasMetadata item, ImageType imageType, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
var image = item.GetImageInfo(imageType, 0);
@@ -106,7 +106,7 @@ namespace Emby.Server.Implementations.Images
return await FetchToFileInternal(item, items, imageType, cancellationToken).ConfigureAwait(false);
}
- protected async Task<ItemUpdateType> FetchToFileInternal(IHasImages item,
+ protected async Task<ItemUpdateType> FetchToFileInternal(IHasMetadata item,
List<BaseItem> itemsWithImages,
ImageType imageType,
CancellationToken cancellationToken)
@@ -132,14 +132,14 @@ namespace Emby.Server.Implementations.Images
return ItemUpdateType.ImageUpdate;
}
- protected abstract List<BaseItem> GetItemsWithImages(IHasImages item);
+ protected abstract List<BaseItem> GetItemsWithImages(IHasMetadata item);
- protected string CreateThumbCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath)
+ protected string CreateThumbCollage(IHasMetadata primaryItem, List<BaseItem> items, string outputPath)
{
return CreateCollage(primaryItem, items, outputPath, 640, 360);
}
- protected virtual IEnumerable<string> GetStripCollageImagePaths(IHasImages primaryItem, IEnumerable<BaseItem> items)
+ protected virtual IEnumerable<string> GetStripCollageImagePaths(IHasMetadata primaryItem, IEnumerable<BaseItem> items)
{
return items
.Select(i =>
@@ -161,22 +161,22 @@ namespace Emby.Server.Implementations.Images
.Where(i => !string.IsNullOrWhiteSpace(i));
}
- protected string CreatePosterCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath)
+ protected string CreatePosterCollage(IHasMetadata primaryItem, List<BaseItem> items, string outputPath)
{
return CreateCollage(primaryItem, items, outputPath, 400, 600);
}
- protected string CreateSquareCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath)
+ protected string CreateSquareCollage(IHasMetadata primaryItem, List<BaseItem> items, string outputPath)
{
return CreateCollage(primaryItem, items, outputPath, 600, 600);
}
- protected string CreateThumbCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath, int width, int height)
+ protected string CreateThumbCollage(IHasMetadata primaryItem, List<BaseItem> items, string outputPath, int width, int height)
{
return CreateCollage(primaryItem, items, outputPath, width, height);
}
- private string CreateCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath, int width, int height)
+ private string CreateCollage(IHasMetadata primaryItem, List<BaseItem> items, string outputPath, int width, int height)
{
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPath));
@@ -207,7 +207,7 @@ namespace Emby.Server.Implementations.Images
get { return "Dynamic Image Provider"; }
}
- protected virtual string CreateImage(IHasImages item,
+ protected virtual string CreateImage(IHasMetadata item,
List<BaseItem> itemsWithImages,
string outputPathWithoutExtension,
ImageType imageType,
@@ -267,7 +267,7 @@ namespace Emby.Server.Implementations.Images
return false;
}
- protected bool HasChanged(IHasImages item, ImageType type)
+ protected bool HasChanged(IHasMetadata item, ImageType type)
{
var image = item.GetImageInfo(type, 0);
@@ -293,20 +293,16 @@ namespace Emby.Server.Implementations.Images
return true;
}
- protected List<BaseItem> GetFinalItems(List<BaseItem> items)
+ protected List<BaseItem> GetFinalItems(IEnumerable<BaseItem> items)
{
return GetFinalItems(items, 4);
}
- protected virtual List<BaseItem> GetFinalItems(List<BaseItem> items, int limit)
+ protected virtual List<BaseItem> GetFinalItems(IEnumerable<BaseItem> items, int limit)
{
- // Rotate the images once every x days
- var random = DateTime.Now.DayOfYear % MaxImageAgeDays;
-
return items
- .OrderBy(i => (random + string.Empty + items.IndexOf(i)).GetMD5())
+ .OrderBy(i => Guid.NewGuid())
.Take(limit)
- .OrderBy(i => i.Name)
.ToList();
}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 9588b648e..271dac153 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -136,14 +136,6 @@ namespace Emby.Server.Implementations.Library
/// <value>The configuration manager.</value>
private IServerConfigurationManager ConfigurationManager { get; set; }
- /// <summary>
- /// A collection of items that may be referenced from multiple physical places in the library
- /// (typically, multiple user roots). We store them here and be sure they all reference a
- /// single instance.
- /// </summary>
- /// <value>The by reference items.</value>
- private ConcurrentDictionary<Guid, BaseItem> ByReferenceItems { get; set; }
-
private readonly Func<ILibraryMonitor> _libraryMonitorFactory;
private readonly Func<IProviderManager> _providerManagerFactory;
private readonly Func<IUserViewManager> _userviewManager;
@@ -186,7 +178,6 @@ namespace Emby.Server.Implementations.Library
_fileSystem = fileSystem;
_providerManagerFactory = providerManagerFactory;
_userviewManager = userviewManager;
- ByReferenceItems = new ConcurrentDictionary<Guid, BaseItem>();
_libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated;
@@ -560,22 +551,6 @@ namespace Emby.Server.Implementations.Library
return key.GetMD5();
}
- /// <summary>
- /// Ensure supplied item has only one instance throughout
- /// </summary>
- /// <param name="item">The item.</param>
- /// <returns>The proper instance to the item</returns>
- public BaseItem GetOrAddByReferenceItem(BaseItem item)
- {
- // Add this item to our list if not there already
- if (!ByReferenceItems.TryAdd(item.Id, item))
- {
- // Already there - return the existing reference
- item = ByReferenceItems[item.Id];
- }
- return item;
- }
-
public BaseItem ResolvePath(FileSystemMetadata fileInfo,
Folder parent = null)
{
@@ -1298,7 +1273,7 @@ namespace Emby.Server.Implementations.Library
return item;
}
- public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent)
+ public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent)
{
if (query.Recursive && query.ParentId.HasValue)
{
@@ -1317,7 +1292,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetItemList(query);
}
- public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query)
+ public List<BaseItem> GetItemList(InternalItemsQuery query)
{
return GetItemList(query, true);
}
@@ -1341,7 +1316,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetCount(query);
}
- public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents)
+ public List<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents)
{
SetTopParentIdsOrAncestors(query, parents);
@@ -1515,9 +1490,11 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetItems(query);
}
+ var list = ItemRepository.GetItemList(query);
+
return new QueryResult<BaseItem>
{
- Items = ItemRepository.GetItemList(query).ToArray()
+ Items = list.ToArray(list.Count)
};
}
@@ -2610,7 +2587,7 @@ namespace Emby.Server.Implementations.Library
var resolvers = new IItemResolver[]
{
- new GenericVideoResolver<Trailer>(this)
+ new GenericVideoResolver<Trailer>(this, _fileSystem)
};
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers)
@@ -2856,7 +2833,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.UpdatePeople(item.Id, people);
}
- public async Task<ItemImageInfo> ConvertImageToLocal(IHasImages item, ItemImageInfo image, int imageIndex)
+ public async Task<ItemImageInfo> ConvertImageToLocal(IHasMetadata item, ItemImageInfo image, int imageIndex)
{
foreach (var url in image.Path.Split('|'))
{
diff --git a/Emby.Server.Implementations/Library/LocalTrailerPostScanTask.cs b/Emby.Server.Implementations/Library/LocalTrailerPostScanTask.cs
index e64980dff..83bee89e0 100644
--- a/Emby.Server.Implementations/Library/LocalTrailerPostScanTask.cs
+++ b/Emby.Server.Implementations/Library/LocalTrailerPostScanTask.cs
@@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -45,7 +46,7 @@ namespace Emby.Server.Implementations.Library
Recursive = true,
DtoOptions = new DtoOptions(false)
- }).ToArray();
+ });
var numComplete = 0;
@@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.Library
progress.Report(100);
}
- private async Task AssignTrailers(IHasTrailers item, BaseItem[] channelTrailers)
+ private async Task AssignTrailers(IHasTrailers item, IEnumerable<BaseItem> channelTrailers)
{
if (item is Game)
{
diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs
index f0d07cc3c..7f77170ad 100644
--- a/Emby.Server.Implementations/Library/MusicManager.cs
+++ b/Emby.Server.Implementations/Library/MusicManager.cs
@@ -19,27 +19,27 @@ namespace Emby.Server.Implementations.Library
_libraryManager = libraryManager;
}
- public IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user, DtoOptions dtoOptions)
+ public List<BaseItem> GetInstantMixFromSong(Audio item, User user, DtoOptions dtoOptions)
{
var list = new List<Audio>
{
item
};
- return list.Concat(GetInstantMixFromGenres(item.Genres, user, dtoOptions));
+ return list.Concat(GetInstantMixFromGenres(item.Genres, user, dtoOptions)).ToList();
}
- public IEnumerable<Audio> GetInstantMixFromArtist(MusicArtist item, User user, DtoOptions dtoOptions)
+ public List<BaseItem> GetInstantMixFromArtist(MusicArtist item, User user, DtoOptions dtoOptions)
{
return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
}
- public IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user, DtoOptions dtoOptions)
+ public List<BaseItem> GetInstantMixFromAlbum(MusicAlbum item, User user, DtoOptions dtoOptions)
{
return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
}
- public IEnumerable<Audio> GetInstantMixFromFolder(Folder item, User user, DtoOptions dtoOptions)
+ public List<BaseItem> GetInstantMixFromFolder(Folder item, User user, DtoOptions dtoOptions)
{
var genres = item
.GetRecursiveChildren(user, new InternalItemsQuery(user)
@@ -55,12 +55,12 @@ namespace Emby.Server.Implementations.Library
return GetInstantMixFromGenres(genres, user, dtoOptions);
}
- public IEnumerable<Audio> GetInstantMixFromPlaylist(Playlist item, User user, DtoOptions dtoOptions)
+ public List<BaseItem> GetInstantMixFromPlaylist(Playlist item, User user, DtoOptions dtoOptions)
{
return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
}
- public IEnumerable<Audio> GetInstantMixFromGenres(IEnumerable<string> genres, User user, DtoOptions dtoOptions)
+ public List<BaseItem> GetInstantMixFromGenres(IEnumerable<string> genres, User user, DtoOptions dtoOptions)
{
var genreIds = genres.DistinctNames().Select(i =>
{
@@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.Library
return GetInstantMixFromGenreIds(genreIds, user, dtoOptions);
}
- public IEnumerable<Audio> GetInstantMixFromGenreIds(IEnumerable<string> genreIds, User user, DtoOptions dtoOptions)
+ public List<BaseItem> GetInstantMixFromGenreIds(IEnumerable<string> genreIds, User user, DtoOptions dtoOptions)
{
return _libraryManager.GetItemList(new InternalItemsQuery(user)
{
@@ -92,10 +92,10 @@ namespace Emby.Server.Implementations.Library
DtoOptions = dtoOptions
- }).Cast<Audio>();
+ });
}
- public IEnumerable<Audio> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
+ public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
{
var genre = item as MusicGenre;
if (genre != null)
@@ -133,7 +133,7 @@ namespace Emby.Server.Implementations.Library
return GetInstantMixFromFolder(folder, user, dtoOptions);
}
- return new Audio[] { };
+ return new List<BaseItem>();
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
index ef32eccea..fa4f026f4 100644
--- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
@@ -6,6 +6,7 @@ using System;
using System.IO;
using System.Linq;
using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
namespace Emby.Server.Implementations.Library.Resolvers
@@ -18,9 +19,11 @@ namespace Emby.Server.Implementations.Library.Resolvers
where T : Video, new()
{
protected readonly ILibraryManager LibraryManager;
+ protected readonly IFileSystem FileSystem;
- protected BaseVideoResolver(ILibraryManager libraryManager)
+ protected BaseVideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem)
{
+ FileSystem = fileSystem;
LibraryManager = libraryManager;
}
@@ -271,7 +274,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
return false;
}
- return directoryService.GetFilePaths(fullPath).Any(i => string.Equals(Path.GetExtension(i), ".vob", StringComparison.OrdinalIgnoreCase));
+ return FileSystem.GetFilePaths(fullPath).Any(i => string.Equals(Path.GetExtension(i), ".vob", StringComparison.OrdinalIgnoreCase));
}
/// <summary>
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 69563e5a0..d88076b99 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -23,11 +23,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// </summary>
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
{
- public MovieResolver(ILibraryManager libraryManager)
- : base(libraryManager)
- {
- }
-
/// <summary>
/// Gets the priority.
/// </summary>
@@ -452,8 +447,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
var folderPaths = multiDiscFolders.Select(i => i.FullName).Where(i =>
{
- var subFileEntries = directoryService.GetFileSystemEntries(i)
- .ToList();
+ var subFileEntries = directoryService.GetFileSystemEntries(i);
var subfolders = subFileEntries
.Where(e => e.IsDirectory)
@@ -547,5 +541,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return !validCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase);
}
+
+ public MovieResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem)
+ {
+ }
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
index 8bbda3b62..04312f277 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
@@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
var filename = Path.GetFileNameWithoutExtension(args.Path);
// Make sure the image doesn't belong to a video file
- if (args.DirectoryService.GetFilePaths(_fileSystem.GetDirectoryName(args.Path)).Any(i => IsOwnedByMedia(args.GetLibraryOptions(), i, filename)))
+ if (_fileSystem.GetFilePaths(_fileSystem.GetDirectoryName(args.Path)).Any(i => IsOwnedByMedia(args.GetLibraryOptions(), i, filename)))
{
return null;
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index bdab8552a..7d1c4d65a 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
@@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using System.Linq;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers.TV
{
@@ -11,10 +12,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// </summary>
public class EpisodeResolver : BaseVideoResolver<Episode>
{
- public EpisodeResolver(ILibraryManager libraryManager) : base(libraryManager)
- {
- }
-
/// <summary>
/// Resolves the specified args.
/// </summary>
@@ -76,5 +73,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
+
+ public EpisodeResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem)
+ {
+ }
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
index b5e1bf5f7..5c7a528f5 100644
--- a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers
{
@@ -9,11 +10,6 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// </summary>
public class VideoResolver : BaseVideoResolver<Video>
{
- public VideoResolver(ILibraryManager libraryManager)
- : base(libraryManager)
- {
- }
-
protected override Video Resolve(ItemResolveArgs args)
{
if (args.Parent != null)
@@ -33,12 +29,16 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
get { return ResolverPriority.Last; }
}
+
+ public VideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem)
+ {
+ }
}
public class GenericVideoResolver<T> : BaseVideoResolver<T>
where T : Video, new ()
{
- public GenericVideoResolver(ILibraryManager libraryManager) : base(libraryManager)
+ public GenericVideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem)
{
}
}
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index 6f63322c8..658558ec0 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -10,6 +10,7 @@ using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Extensions;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.Library
{
@@ -163,8 +164,8 @@ namespace Emby.Server.Implementations.Library
var mediaItems = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
NameContains = searchTerm,
- ExcludeItemTypes = excludeItemTypes.ToArray(),
- IncludeItemTypes = includeItemTypes.ToArray(),
+ ExcludeItemTypes = excludeItemTypes.ToArray(excludeItemTypes.Count),
+ IncludeItemTypes = includeItemTypes.ToArray(includeItemTypes.Count),
Limit = query.Limit,
IncludeItemsByName = string.IsNullOrWhiteSpace(query.ParentId),
ParentId = string.IsNullOrWhiteSpace(query.ParentId) ? (Guid?)null : new Guid(query.ParentId),
diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs
index a277b693a..0d4303b16 100644
--- a/Emby.Server.Implementations/Library/UserViewManager.cs
+++ b/Emby.Server.Implementations/Library/UserViewManager.cs
@@ -15,6 +15,7 @@ using System.Threading.Tasks;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.Library
{
@@ -231,7 +232,7 @@ namespace Emby.Server.Implementations.Library
return list;
}
- private IEnumerable<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options)
+ private List<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options)
{
var parentId = request.ParentId;
@@ -325,7 +326,7 @@ namespace Emby.Server.Implementations.Library
Limit = limit * 5,
IsPlayed = isPlayed,
DtoOptions = options,
- MediaTypes = mediaTypes.ToArray()
+ MediaTypes = mediaTypes.ToArray(mediaTypes.Count)
};
if (parents.Count == 0)
diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
index c2eb30ee4..39630cf96 100644
--- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
@@ -55,28 +55,21 @@ namespace Emby.Server.Implementations.Library.Validators
/// <returns>Task.</returns>
public async Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
{
- var people = _libraryManager.GetPeople(new InternalPeopleQuery());
-
- var dict = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
-
- foreach (var person in people)
- {
- dict[person.Name] = true;
- }
+ var people = _libraryManager.GetPeopleNames(new InternalPeopleQuery());
var numComplete = 0;
- _logger.Debug("Will refresh {0} people", dict.Count);
+ var numPeople = people.Count;
- var numPeople = dict.Count;
+ _logger.Debug("Will refresh {0} people", numPeople);
- foreach (var person in dict)
+ foreach (var person in people)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
- var item = _libraryManager.GetPerson(person.Key);
+ var item = _libraryManager.GetPerson(person);
var options = new MetadataRefreshOptions(_fileSystem)
{
diff --git a/Emby.Server.Implementations/LiveTv/ChannelImageProvider.cs b/Emby.Server.Implementations/LiveTv/ChannelImageProvider.cs
index 95cefd999..0c8049d8b 100644
--- a/Emby.Server.Implementations/LiveTv/ChannelImageProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/ChannelImageProvider.cs
@@ -28,12 +28,12 @@ namespace Emby.Server.Implementations.LiveTv
_appHost = appHost;
}
- public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
+ public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item)
{
return new[] { ImageType.Primary };
}
- public async Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken)
+ public async Task<DynamicImageResponse> GetImage(IHasMetadata item, ImageType type, CancellationToken cancellationToken)
{
var liveTvItem = (LiveTvChannel)item;
@@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.LiveTv
get { return "Live TV Service Provider"; }
}
- public bool Supports(IHasImages item)
+ public bool Supports(IHasMetadata item)
{
return item is LiveTvChannel;
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 6173068cc..90121b496 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return "ts";
}
- return "mp4";
+ return "mkv";
}
}
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
index 5c5072192..619d2378d 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
@@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.LiveTv
{
try
{
- dto.ParentBackdropImageTags = new List<string>
+ dto.ParentBackdropImageTags = new string[]
{
_imageProcessor.GetImageCacheTag(librarySeries, image)
};
@@ -218,14 +218,14 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- if (dto.ParentBackdropImageTags == null || dto.ParentBackdropImageTags.Count == 0)
+ if (dto.ParentBackdropImageTags == null || dto.ParentBackdropImageTags.Length == 0)
{
image = program.GetImageInfo(ImageType.Backdrop, 0);
if (image != null)
{
try
{
- dto.ParentBackdropImageTags = new List<string>
+ dto.ParentBackdropImageTags = new string[]
{
_imageProcessor.GetImageCacheTag(program, image)
};
@@ -406,7 +406,7 @@ namespace Emby.Server.Implementations.LiveTv
return dto;
}
- internal string GetImageTag(IHasImages info)
+ internal string GetImageTag(IHasMetadata info)
{
try
{
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index 9b832bd4a..3fbbc8390 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -173,7 +173,7 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- public async Task<QueryResult<LiveTvChannel>> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
+ public async Task<QueryResult<BaseItem>> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
{
var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
@@ -208,15 +208,7 @@ namespace Emby.Server.Implementations.LiveTv
internalQuery.OrderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending));
}
- var channelResult = _libraryManager.GetItemsResult(internalQuery);
-
- var result = new QueryResult<LiveTvChannel>
- {
- Items = channelResult.Items.Cast<LiveTvChannel>().ToArray(),
- TotalRecordCount = channelResult.TotalRecordCount
- };
-
- return result;
+ return _libraryManager.GetItemsResult(internalQuery);
}
public LiveTvChannel GetInternalChannel(string id)
@@ -993,7 +985,9 @@ namespace Emby.Server.Implementations.LiveTv
var queryResult = _libraryManager.QueryItems(internalQuery);
- var returnArray = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user).ConfigureAwait(false)).ToArray();
+ var returnList = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user)
+ .ConfigureAwait(false));
+ var returnArray = returnList.ToArray(returnList.Count);
var result = new QueryResult<BaseItemDto>
{
@@ -1077,7 +1071,9 @@ namespace Emby.Server.Implementations.LiveTv
var user = _userManager.GetUserById(query.UserId);
- var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray();
+ var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user)
+ .ConfigureAwait(false));
+ var returnArray = returnList.ToArray(returnList.Count);
var result = new QueryResult<BaseItemDto>
{
@@ -1639,7 +1635,7 @@ namespace Emby.Server.Implementations.LiveTv
{
MediaTypes = new[] { MediaType.Video },
Recursive = true,
- AncestorIds = folderIds.Select(i => i.ToString("N")).ToArray(),
+ AncestorIds = folderIds.Select(i => i.ToString("N")).ToArray(folderIds.Count),
IsFolder = false,
IsVirtualItem = false,
Limit = query.Limit,
@@ -1647,9 +1643,9 @@ namespace Emby.Server.Implementations.LiveTv
SortBy = new[] { ItemSortBy.DateCreated },
SortOrder = SortOrder.Descending,
EnableTotalRecordCount = query.EnableTotalRecordCount,
- IncludeItemTypes = includeItemTypes.ToArray(),
- ExcludeItemTypes = excludeItemTypes.ToArray(),
- Genres = genres.ToArray(),
+ IncludeItemTypes = includeItemTypes.ToArray(includeItemTypes.Count),
+ ExcludeItemTypes = excludeItemTypes.ToArray(excludeItemTypes.Count),
+ Genres = genres.ToArray(genres.Count),
DtoOptions = dtoOptions
});
}
@@ -1695,17 +1691,20 @@ namespace Emby.Server.Implementations.LiveTv
var internalResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
{
Recursive = true,
- AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(),
+ AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(folders.Count),
Limit = query.Limit,
SortBy = new[] { ItemSortBy.DateCreated },
SortOrder = SortOrder.Descending,
EnableTotalRecordCount = query.EnableTotalRecordCount,
- IncludeItemTypes = includeItemTypes.ToArray(),
- ExcludeItemTypes = excludeItemTypes.ToArray(),
+ IncludeItemTypes = includeItemTypes.ToArray(includeItemTypes.Count),
+ ExcludeItemTypes = excludeItemTypes.ToArray(excludeItemTypes.Count),
DtoOptions = options
});
- var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray();
+ var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user)
+ .ConfigureAwait(false));
+
+ var returnArray = returnList.ToArray(returnList.Count);
return new QueryResult<BaseItemDto>
{
@@ -1996,7 +1995,9 @@ namespace Emby.Server.Implementations.LiveTv
var internalResult = await GetInternalRecordings(query, options, cancellationToken).ConfigureAwait(false);
- var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray();
+ var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user)
+ .ConfigureAwait(false));
+ var returnArray = returnList.ToArray(returnList.Count);
return new QueryResult<BaseItemDto>
{
@@ -2084,7 +2085,7 @@ namespace Emby.Server.Implementations.LiveTv
var returnArray = returnList
.OrderBy(i => i.StartDate)
- .ToArray();
+ .ToArray(returnList.Count);
return new QueryResult<TimerInfoDto>
{
@@ -2341,7 +2342,7 @@ namespace Emby.Server.Implementations.LiveTv
TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Result.Id.ToString("N") },
DtoOptions = options
- }).ToList() : new List<BaseItem>();
+ }) : new List<BaseItem>();
RemoveFields(options);
@@ -2705,7 +2706,7 @@ namespace Emby.Server.Implementations.LiveTv
return new QueryResult<BaseItemDto>
{
- Items = groups.ToArray(),
+ Items = groups.ToArray(groups.Count),
TotalRecordCount = groups.Count
};
}
@@ -2992,7 +2993,7 @@ namespace Emby.Server.Implementations.LiveTv
Name = tunerChannelId,
Value = providerChannelId
});
- listingsProviderInfo.ChannelMappings = list.ToArray();
+ listingsProviderInfo.ChannelMappings = list.ToArray(list.Count);
}
_config.SaveConfiguration("livetv", config);
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
index a7e69776c..5436a12b8 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
@@ -14,6 +14,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.LiveTv
{
@@ -102,7 +103,7 @@ namespace Emby.Server.Implementations.LiveTv
openKeys.Add(item.GetType().Name);
openKeys.Add(item.Id.ToString("N"));
openKeys.Add(source.Id ?? string.Empty);
- source.OpenToken = string.Join(StreamIdDelimeterString, openKeys.ToArray());
+ source.OpenToken = string.Join(StreamIdDelimeterString, openKeys.ToArray(openKeys.Count));
}
// Dummy this up so that direct play checks can still run
diff --git a/Emby.Server.Implementations/LiveTv/RecordingImageProvider.cs b/Emby.Server.Implementations/LiveTv/RecordingImageProvider.cs
index 47663bdbc..992badbb5 100644
--- a/Emby.Server.Implementations/LiveTv/RecordingImageProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/RecordingImageProvider.cs
@@ -19,12 +19,12 @@ namespace Emby.Server.Implementations.LiveTv
_liveTvManager = liveTvManager;
}
- public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
+ public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item)
{
return new[] { ImageType.Primary };
}
- public async Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken)
+ public async Task<DynamicImageResponse> GetImage(IHasMetadata item, ImageType type, CancellationToken cancellationToken)
{
var liveTvItem = (ILiveTvRecording)item;
@@ -58,7 +58,7 @@ namespace Emby.Server.Implementations.LiveTv
get { return "Live TV Service Provider"; }
}
- public bool Supports(IHasImages item)
+ public bool Supports(IHasMetadata item)
{
return item is ILiveTvRecording;
}
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index 64a41acef..8d3051a89 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.Localization
/// Gets the cultures.
/// </summary>
/// <returns>IEnumerable{CultureDto}.</returns>
- public IEnumerable<CultureDto> GetCultures()
+ public List<CultureDto> GetCultures()
{
var type = GetType();
var path = type.Namespace + ".iso6392.txt";
@@ -169,14 +169,14 @@ namespace Emby.Server.Implementations.Localization
return list.Where(i => !string.IsNullOrWhiteSpace(i.Name) &&
!string.IsNullOrWhiteSpace(i.DisplayName) &&
!string.IsNullOrWhiteSpace(i.ThreeLetterISOLanguageName) &&
- !string.IsNullOrWhiteSpace(i.TwoLetterISOLanguageName));
+ !string.IsNullOrWhiteSpace(i.TwoLetterISOLanguageName)).ToList();
}
/// <summary>
/// Gets the countries.
/// </summary>
/// <returns>IEnumerable{CountryInfo}.</returns>
- public IEnumerable<CountryInfo> GetCountries()
+ public List<CountryInfo> GetCountries()
{
var type = GetType();
var path = type.Namespace + ".countries.json";
diff --git a/Emby.Server.Implementations/Localization/TextLocalizer.cs b/Emby.Server.Implementations/Localization/TextLocalizer.cs
new file mode 100644
index 000000000..5188a959e
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/TextLocalizer.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Emby.Server.Implementations.Localization
+{
+ public class TextLocalizer : ITextLocalizer
+ {
+ public string RemoveDiacritics(string text)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException("text");
+ }
+
+ var chars = Normalize(text, NormalizationForm.FormD)
+ .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) != UnicodeCategory.NonSpacingMark);
+
+ return Normalize(String.Concat(chars), NormalizationForm.FormC);
+ }
+
+ private static string Normalize(string text, NormalizationForm form, bool stripStringOnFailure = true)
+ {
+ if (stripStringOnFailure)
+ {
+ try
+ {
+ return text.Normalize(form);
+ }
+ catch (ArgumentException)
+ {
+ // will throw if input contains invalid unicode chars
+ // https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/
+ text = StripInvalidUnicodeCharacters(text);
+ return Normalize(text, form, false);
+ }
+ }
+
+ try
+ {
+ return text.Normalize(form);
+ }
+ catch (ArgumentException)
+ {
+ // if it still fails, return the original text
+ return text;
+ }
+ }
+
+ private static string StripInvalidUnicodeCharacters(string str)
+ {
+ var invalidCharactersRegex = new Regex("([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])");
+ return invalidCharactersRegex.Replace(str, "");
+ }
+
+ public string NormalizeFormKD(string text)
+ {
+ return text.Normalize(NormalizationForm.FormKD);
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Logging/ConsoleLogger.cs b/Emby.Server.Implementations/Logging/ConsoleLogger.cs
new file mode 100644
index 000000000..51199f08a
--- /dev/null
+++ b/Emby.Server.Implementations/Logging/ConsoleLogger.cs
@@ -0,0 +1,13 @@
+using System;
+using MediaBrowser.Model.Logging;
+
+namespace Emby.Server.Implementations.Logging
+{
+ public class ConsoleLogger : IConsoleLogger
+ {
+ public void WriteLine(string message)
+ {
+ Console.WriteLine(message);
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
index 2cfa4601a..884f46f1f 100644
--- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
+++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
@@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.MediaEncoder
/// Gets the chapter images data path.
/// </summary>
/// <value>The chapter images data path.</value>
- private string GetChapterImagesPath(IHasImages item)
+ private string GetChapterImagesPath(IHasMetadata item)
{
return Path.Combine(item.GetInternalMetadataPath(), "chapters");
}
diff --git a/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs b/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs
index 40752db80..ff152c9e9 100644
--- a/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs
+++ b/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs
@@ -12,6 +12,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Notifications;
using SQLitePCL.pretty;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.Notifications
{
@@ -83,13 +84,13 @@ namespace Emby.Server.Implementations.Notifications
clauses.Add("UserId=?");
paramList.Add(query.UserId.ToGuidBlob());
- var whereClause = " where " + string.Join(" And ", clauses.ToArray());
+ var whereClause = " where " + string.Join(" And ", clauses.ToArray(clauses.Count));
using (WriteLock.Read())
{
using (var connection = CreateConnection(true))
{
- result.TotalRecordCount = connection.Query("select count(Id) from Notifications" + whereClause, paramList.ToArray()).SelectScalarInt().First();
+ result.TotalRecordCount = connection.Query("select count(Id) from Notifications" + whereClause, paramList.ToArray(paramList.Count)).SelectScalarInt().First();
var commandText = string.Format("select Id,UserId,Date,Name,Description,Url,Level,IsRead,Category,RelatedId from Notifications{0} order by IsRead asc, Date desc", whereClause);
@@ -110,12 +111,12 @@ namespace Emby.Server.Implementations.Notifications
var resultList = new List<Notification>();
- foreach (var row in connection.Query(commandText, paramList.ToArray()))
+ foreach (var row in connection.Query(commandText, paramList.ToArray(paramList.Count)))
{
resultList.Add(GetNotification(row));
}
- result.Notifications = resultList.ToArray();
+ result.Notifications = resultList.ToArray(resultList.Count);
}
}
@@ -280,7 +281,7 @@ namespace Emby.Server.Implementations.Notifications
public async Task MarkRead(IEnumerable<string> notificationIdList, string userId, bool isRead, CancellationToken cancellationToken)
{
var list = notificationIdList.ToList();
- var idArray = list.Select(i => new Guid(i)).ToArray();
+ var idArray = list.Select(i => new Guid(i)).ToArray(list.Count);
await MarkReadInternal(idArray, userId, isRead, cancellationToken).ConfigureAwait(false);
@@ -290,7 +291,7 @@ namespace Emby.Server.Implementations.Notifications
{
NotificationsMarkedRead(this, new NotificationReadEventArgs
{
- IdList = list.ToArray(),
+ IdList = list.ToArray(list.Count),
IsRead = isRead,
UserId = userId
});
diff --git a/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs b/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs
index 0239ee336..0d89ba84f 100644
--- a/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs
+++ b/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs
@@ -2,6 +2,7 @@
using MediaBrowser.Controller.Notifications;
using MediaBrowser.Controller.Plugins;
using System.Linq;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.Notifications
{
@@ -33,7 +34,7 @@ namespace Emby.Server.Implementations.Notifications
list.Add(e.UserId);
list.Add(e.IsRead.ToString().ToLower());
- var msg = string.Join("|", list.ToArray());
+ var msg = string.Join("|", list.ToArray(list.Count));
_serverManager.SendWebSocketMessage("NotificationsMarkedRead", msg);
}
diff --git a/Emby.Server.Implementations/Photos/PhotoAlbumImageProvider.cs b/Emby.Server.Implementations/Photos/PhotoAlbumImageProvider.cs
index 17f9b491d..f7c65f63d 100644
--- a/Emby.Server.Implementations/Photos/PhotoAlbumImageProvider.cs
+++ b/Emby.Server.Implementations/Photos/PhotoAlbumImageProvider.cs
@@ -18,15 +18,15 @@ namespace Emby.Server.Implementations.Photos
{
}
- protected override List<BaseItem> GetItemsWithImages(IHasImages item)
+ protected override List<BaseItem> GetItemsWithImages(IHasMetadata item)
{
var photoAlbum = (PhotoAlbum)item;
- var items = GetFinalItems(photoAlbum.Children.ToList());
+ var items = GetFinalItems(photoAlbum.Children);
return items;
}
- protected override string CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+ protected override string CreateImage(IHasMetadata item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
{
return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
}
diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
index 127ce24ae..c2e860339 100644
--- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
+++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
@@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Playlists
{
}
- protected override List<BaseItem> GetItemsWithImages(IHasImages item)
+ protected override List<BaseItem> GetItemsWithImages(IHasMetadata item)
{
var playlist = (Playlist)item;
@@ -65,8 +65,7 @@ namespace Emby.Server.Implementations.Playlists
return null;
})
.Where(i => i != null)
- .DistinctBy(i => i.Id)
- .ToList();
+ .DistinctBy(i => i.Id);
return GetFinalItems(items);
}
@@ -81,7 +80,7 @@ namespace Emby.Server.Implementations.Playlists
_libraryManager = libraryManager;
}
- protected override List<BaseItem> GetItemsWithImages(IHasImages item)
+ protected override List<BaseItem> GetItemsWithImages(IHasMetadata item)
{
var items = _libraryManager.GetItemList(new InternalItemsQuery
{
@@ -93,12 +92,12 @@ namespace Emby.Server.Implementations.Playlists
ImageTypes = new[] { ImageType.Primary },
DtoOptions = new DtoOptions(false)
- }).ToList();
+ });
return GetFinalItems(items);
}
- //protected override Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+ //protected override Task<string> CreateImage(IHasMetadata item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
//{
// return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
//}
@@ -113,7 +112,7 @@ namespace Emby.Server.Implementations.Playlists
_libraryManager = libraryManager;
}
- protected override List<BaseItem> GetItemsWithImages(IHasImages item)
+ protected override List<BaseItem> GetItemsWithImages(IHasMetadata item)
{
var items = _libraryManager.GetItemList(new InternalItemsQuery
{
@@ -125,12 +124,12 @@ namespace Emby.Server.Implementations.Playlists
ImageTypes = new[] { ImageType.Primary },
DtoOptions = new DtoOptions(false)
- }).ToList();
+ });
return GetFinalItems(items);
}
- //protected override Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+ //protected override Task<string> CreateImage(IHasMetadata item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
//{
// return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
//}
diff --git a/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
index 967e7ddd8..91ae2afd1 100644
--- a/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
@@ -15,6 +15,7 @@ using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Tasks;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.ScheduledTasks
{
@@ -142,7 +143,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
_fileSystem.CreateDirectory(parentPath);
- _fileSystem.WriteAllText(failHistoryPath, string.Join("|", previouslyFailedImages.ToArray()));
+ _fileSystem.WriteAllText(failHistoryPath, string.Join("|", previouslyFailedImages.ToArray(previouslyFailedImages.Count)));
}
numComplete++;
diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs
index 9ec0af6bb..d512ff4fb 100644
--- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs
+++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs
@@ -11,6 +11,7 @@ using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using SQLitePCL.pretty;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.Security
{
@@ -174,13 +175,13 @@ namespace Emby.Server.Implementations.Security
var whereTextWithoutPaging = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count));
if (startIndex > 0)
{
var pagingWhereText = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count));
whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM AccessTokens {0} ORDER BY DateCreated LIMIT {1})",
pagingWhereText,
@@ -189,7 +190,7 @@ namespace Emby.Server.Implementations.Security
var whereText = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count));
commandText += whereText;
@@ -236,7 +237,7 @@ namespace Emby.Server.Implementations.Security
}
}
- result.Items = list.ToArray();
+ result.Items = list.ToArray(list.Count);
return result;
}, ReadTransactionMode);
diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs
index 1c530144c..4ad56411a 100644
--- a/Emby.Server.Implementations/Services/ServiceController.cs
+++ b/Emby.Server.Implementations/Services/ServiceController.cs
@@ -15,17 +15,15 @@ namespace Emby.Server.Implementations.Services
public class ServiceController
{
public static ServiceController Instance;
- private readonly Func<IEnumerable<Type>> _resolveServicesFn;
- public ServiceController(Func<IEnumerable<Type>> resolveServicesFn)
+ public ServiceController()
{
Instance = this;
- _resolveServicesFn = resolveServicesFn;
}
- public void Init(HttpListenerHost appHost)
+ public void Init(HttpListenerHost appHost, Type[] serviceTypes)
{
- foreach (var serviceType in _resolveServicesFn())
+ foreach (var serviceType in serviceTypes)
{
RegisterService(appHost, serviceType);
}
diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs
index e0b5e69c0..4a2199c89 100644
--- a/Emby.Server.Implementations/Services/ServiceExec.cs
+++ b/Emby.Server.Implementations/Services/ServiceExec.cs
@@ -5,6 +5,7 @@ using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.Services
{
@@ -123,7 +124,7 @@ namespace Emby.Server.Implementations.Services
}
if (reqFilters.Count > 0)
- actionCtx.RequestFilters = reqFilters.OrderBy(i => i.Priority).ToArray();
+ actionCtx.RequestFilters = reqFilters.OrderBy(i => i.Priority).ToArray(reqFilters.Count);
actions.Add(actionCtx);
}
diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs
index 255b20919..da5e74f1f 100644
--- a/Emby.Server.Implementations/Services/ServicePath.cs
+++ b/Emby.Server.Implementations/Services/ServicePath.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Reflection;
using System.Text;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.Services
{
@@ -142,13 +143,13 @@ namespace Emby.Server.Implementations.Services
}
}
- var components = componentsList.ToArray();
+ var components = componentsList.ToArray(componentsList.Count);
this.TotalComponentsCount = components.Length;
this.literalsToMatch = new string[this.TotalComponentsCount];
this.variablesNames = new string[this.TotalComponentsCount];
this.isWildcard = new bool[this.TotalComponentsCount];
- this.componentsWithSeparators = hasSeparators.ToArray();
+ this.componentsWithSeparators = hasSeparators.ToArray(hasSeparators.Count);
this.PathComponentsCount = this.componentsWithSeparators.Length;
string firstLiteralMatch = null;
@@ -268,7 +269,7 @@ namespace Emby.Server.Implementations.Services
propertyInfos.InsertRange(0, newPropertyInfos);
}
- return propertyInfos.ToArray();
+ return propertyInfos.ToArray(propertyInfos.Count);
}
return GetTypesPublicProperties(type)
@@ -285,7 +286,7 @@ namespace Emby.Server.Implementations.Services
if (mi != null && mi.IsStatic) continue;
pis.Add(pi);
}
- return pis.ToArray();
+ return pis.ToArray(pis.Count);
}
@@ -450,7 +451,7 @@ namespace Emby.Server.Implementations.Services
}
}
- withPathInfoParts = totalComponents.ToArray();
+ withPathInfoParts = totalComponents.ToArray(totalComponents.Count);
return true;
}
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 763ca9f24..0b39cfc47 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -32,6 +32,7 @@ using System.Threading.Tasks;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Threading;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.Session
{
@@ -1000,7 +1001,7 @@ namespace Emby.Server.Implementations.Session
command.PlayCommand = PlayCommand.PlayNow;
}
- command.ItemIds = items.Select(i => i.Id.ToString("N")).ToArray();
+ command.ItemIds = items.Select(i => i.Id.ToString("N")).ToArray(items.Count);
if (user != null)
{
@@ -1033,7 +1034,7 @@ namespace Emby.Server.Implementations.Session
if (episodes.Count > 0)
{
- command.ItemIds = episodes.Select(i => i.Id.ToString("N")).ToArray();
+ command.ItemIds = episodes.Select(i => i.Id.ToString("N")).ToArray(episodes.Count);
}
}
}
diff --git a/Emby.Server.Implementations/Social/SharingRepository.cs b/Emby.Server.Implementations/Social/SharingRepository.cs
index 46e9205bb..a2a1f879a 100644
--- a/Emby.Server.Implementations/Social/SharingRepository.cs
+++ b/Emby.Server.Implementations/Social/SharingRepository.cs
@@ -8,6 +8,7 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Social;
using SQLitePCL.pretty;
+using MediaBrowser.Model.Extensions;
namespace Emby.Server.Implementations.Social
{
@@ -86,7 +87,7 @@ namespace Emby.Server.Implementations.Social
var paramList = new List<object>();
paramList.Add(id.ToGuidBlob());
- foreach (var row in connection.Query(commandText, paramList.ToArray()))
+ foreach (var row in connection.Query(commandText, paramList.ToArray(paramList.Count)))
{
return GetSocialShareInfo(row);
}
diff --git a/Emby.Server.Implementations/SystemEvents.cs b/Emby.Server.Implementations/SystemEvents.cs
new file mode 100644
index 000000000..dfff92f1e
--- /dev/null
+++ b/Emby.Server.Implementations/SystemEvents.cs
@@ -0,0 +1,50 @@
+using System;
+using MediaBrowser.Common.Events;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.System;
+
+namespace Emby.Server.Implementations
+{
+ public class SystemEvents : ISystemEvents
+ {
+ public event EventHandler Resume;
+ public event EventHandler Suspend;
+ public event EventHandler SessionLogoff;
+ public event EventHandler SystemShutdown;
+
+ private readonly ILogger _logger;
+
+ public SystemEvents(ILogger logger)
+ {
+ _logger = logger;
+ Microsoft.Win32.SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
+ Microsoft.Win32.SystemEvents.SessionEnding += SystemEvents_SessionEnding;
+ }
+
+ private void SystemEvents_SessionEnding(object sender, Microsoft.Win32.SessionEndingEventArgs e)
+ {
+ switch (e.Reason)
+ {
+ case Microsoft.Win32.SessionEndReasons.Logoff:
+ EventHelper.FireEventIfNotNull(SessionLogoff, this, EventArgs.Empty, _logger);
+ break;
+ case Microsoft.Win32.SessionEndReasons.SystemShutdown:
+ EventHelper.FireEventIfNotNull(SystemShutdown, this, EventArgs.Empty, _logger);
+ break;
+ }
+ }
+
+ private void SystemEvents_PowerModeChanged(object sender, Microsoft.Win32.PowerModeChangedEventArgs e)
+ {
+ switch (e.Mode)
+ {
+ case Microsoft.Win32.PowerModes.Resume:
+ EventHelper.FireEventIfNotNull(Resume, this, EventArgs.Empty, _logger);
+ break;
+ case Microsoft.Win32.PowerModes.Suspend:
+ EventHelper.FireEventIfNotNull(Suspend, this, EventArgs.Empty, _logger);
+ break;
+ }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
index 863391eea..98e0c4080 100644
--- a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
+++ b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.UserViews
{
}
- public override IEnumerable<ImageType> GetSupportedImages(IHasImages item)
+ public override IEnumerable<ImageType> GetSupportedImages(IHasMetadata item)
{
return new List<ImageType>
{
@@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.UserViews
};
}
- protected override List<BaseItem> GetItemsWithImages(IHasImages item)
+ protected override List<BaseItem> GetItemsWithImages(IHasMetadata item)
{
var view = (CollectionFolder)item;
@@ -91,15 +91,15 @@ namespace Emby.Server.Implementations.UserViews
}).DistinctBy(i => i.Id);
- return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)).ToList(), 8);
+ return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)), 8);
}
- protected override bool Supports(IHasImages item)
+ protected override bool Supports(IHasMetadata item)
{
return item is CollectionFolder;
}
- protected override string CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+ protected override string CreateImage(IHasMetadata item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
{
var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png");
@@ -126,7 +126,7 @@ namespace Emby.Server.Implementations.UserViews
_libraryManager = libraryManager;
}
- public override IEnumerable<ImageType> GetSupportedImages(IHasImages item)
+ public override IEnumerable<ImageType> GetSupportedImages(IHasMetadata item)
{
return new List<ImageType>
{
@@ -134,7 +134,7 @@ namespace Emby.Server.Implementations.UserViews
};
}
- protected override List<BaseItem> GetItemsWithImages(IHasImages item)
+ protected override List<BaseItem> GetItemsWithImages(IHasMetadata item)
{
var view = (ManualCollectionsFolder)item;
@@ -149,15 +149,15 @@ namespace Emby.Server.Implementations.UserViews
DtoOptions = new DtoOptions(false)
});
- return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)).ToList(), 8);
+ return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)), 8);
}
- protected override bool Supports(IHasImages item)
+ protected override bool Supports(IHasMetadata item)
{
return item is ManualCollectionsFolder;
}
- protected override string CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+ protected override string CreateImage(IHasMetadata item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
{
var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png");
diff --git a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs
index 5230da8a6..885dfec58 100644
--- a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs
+++ b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs
@@ -31,7 +31,7 @@ namespace Emby.Server.Implementations.UserViews
_libraryManager = libraryManager;
}
- public override IEnumerable<ImageType> GetSupportedImages(IHasImages item)
+ public override IEnumerable<ImageType> GetSupportedImages(IHasMetadata item)
{
var view = (UserView)item;
if (IsUsingCollectionStrip(view))
@@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.UserViews
};
}
- protected override List<BaseItem> GetItemsWithImages(IHasImages item)
+ protected override List<BaseItem> GetItemsWithImages(IHasMetadata item)
{
var view = (UserView)item;
@@ -62,9 +62,9 @@ namespace Emby.Server.Implementations.UserViews
IsMovie = true,
DtoOptions = new DtoOptions(false)
- }).ToList();
+ });
- return GetFinalItems(programs).ToList();
+ return GetFinalItems(programs);
}
if (string.Equals(view.ViewType, SpecialFolder.MovieGenre, StringComparison.OrdinalIgnoreCase) ||
@@ -133,13 +133,13 @@ namespace Emby.Server.Implementations.UserViews
if (isUsingCollectionStrip)
{
- return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)).ToList(), 8);
+ return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)), 8);
}
- return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary)).ToList());
+ return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary)));
}
- protected override bool Supports(IHasImages item)
+ protected override bool Supports(IHasMetadata item)
{
var view = item as UserView;
if (view != null)
@@ -163,7 +163,7 @@ namespace Emby.Server.Implementations.UserViews
return collectionStripViewTypes.Contains(view.ViewType ?? string.Empty);
}
- protected override string CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+ protected override string CreateImage(IHasMetadata item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
{
if (itemsWithImages.Count == 0)
{
diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config
index 03336c936..c86812ee0 100644
--- a/Emby.Server.Implementations/packages.config
+++ b/Emby.Server.Implementations/packages.config
@@ -2,6 +2,9 @@
<packages>
<package id="Emby.XmlTv" version="1.0.9" targetFramework="net46" />
<package id="MediaBrowser.Naming" version="1.0.5" targetFramework="portable45-net45+win8" />
+ <package id="Microsoft.IO.RecyclableMemoryStream" version="1.2.2" targetFramework="net46" />
+ <package id="ServiceStack.Text" version="4.5.12" targetFramework="net46" />
+ <package id="SimpleInjector" version="4.0.8" targetFramework="net46" />
<package id="SQLitePCL.pretty" version="1.1.0" targetFramework="portable45-net45+win8" />
- <package id="SQLitePCLRaw.core" version="1.1.7" targetFramework="net46" />
+ <package id="SQLitePCLRaw.core" version="1.1.8" targetFramework="net46" />
</packages> \ No newline at end of file