mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-04-23 21:47:14 -04:00
Merge branch 'master' into master
This commit is contained in:
commit
9556561a77
160 changed files with 1678 additions and 5249 deletions
|
@ -190,7 +190,7 @@ jobs:
|
|||
- task: CmdLine@2
|
||||
displayName: Execute ABI compatibility check tool
|
||||
inputs:
|
||||
script: 'dotnet tools/CompatibilityCheckerCoreCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName)'
|
||||
script: 'dotnet tools/CompatibilityCheckerCoreCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines'
|
||||
workingDirectory: $(System.ArtifactsDirectory) # Optional
|
||||
#failOnStderr: false # Optional
|
||||
|
||||
|
|
20
.github/ISSUE_TEMPLATE/enhancement-request.md
vendored
20
.github/ISSUE_TEMPLATE/enhancement-request.md
vendored
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
name: Enhancement request
|
||||
about: Suggest an modification to an existing feature
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -1,14 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest a new feature
|
||||
title: ''
|
||||
labels: feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the feature you'd like**
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
22
.github/stale.yml
vendored
Normal file
22
.github/stale.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 90
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 14
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- regression
|
||||
- security
|
||||
- dotnet-3.0-future
|
||||
- roadmap
|
||||
- future
|
||||
- feature
|
||||
- enhancement
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
Issues go stale after 90d of inactivity. Mark the issue as fresh by adding a comment or commit. Stale issues close after an additional 14d of inactivity.
|
||||
If this issue is safe to close now please do so.
|
||||
If you have any questions you can reach us on [Matrix or Social Media](https://jellyfin.readthedocs.io/en/latest/getting-help/).
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
|
@ -27,6 +27,7 @@
|
|||
- [pjeanjean](https://github.com/pjeanjean)
|
||||
- [DrPandemic](https://github.com/drpandemic)
|
||||
- [joern-h](https://github.com/joern-h)
|
||||
- [Khinenw](https://github.com/HelloWorld017)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
|
|
@ -21,10 +21,10 @@ RUN apt-get update \
|
|||
COPY --from=ffmpeg / /
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
|
||||
ARG JELLYFIN_WEB_VERSION=10.3.7
|
||||
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
ARG JELLYFIN_WEB_VERSION=v10.3.7
|
||||
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
&& rm -rf /jellyfin/jellyfin-web \
|
||||
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
|
||||
&& mv jellyfin-web-* /jellyfin/jellyfin-web
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config /media
|
||||
|
|
|
@ -26,10 +26,10 @@ RUN apt-get update \
|
|||
&& chmod 777 /cache /config /media
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
|
||||
ARG JELLYFIN_WEB_VERSION=10.3.7
|
||||
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
ARG JELLYFIN_WEB_VERSION=v10.3.7
|
||||
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
&& rm -rf /jellyfin/jellyfin-web \
|
||||
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
|
||||
&& mv jellyfin-web-* /jellyfin/jellyfin-web
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config /media
|
||||
|
|
|
@ -26,10 +26,10 @@ RUN apt-get update \
|
|||
&& chmod 777 /cache /config /media
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
|
||||
ARG JELLYFIN_WEB_VERSION=10.3.7
|
||||
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
ARG JELLYFIN_WEB_VERSION=v10.3.7
|
||||
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
&& rm -rf /jellyfin/jellyfin-web \
|
||||
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
|
||||
&& mv jellyfin-web-* /jellyfin/jellyfin-web
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config /media
|
||||
|
|
|
@ -181,19 +181,6 @@ namespace Emby.Dlna.Didl
|
|||
writer.WriteFullEndElement();
|
||||
}
|
||||
|
||||
private string GetMimeType(string input)
|
||||
{
|
||||
var mime = MimeTypes.GetMimeType(input);
|
||||
|
||||
// TODO: Instead of being hard-coded here, this should probably be moved into all of the existing profiles
|
||||
if (string.Equals(mime, "video/mp2t", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mime = "video/mpeg";
|
||||
}
|
||||
|
||||
return mime;
|
||||
}
|
||||
|
||||
private void AddVideoResource(DlnaOptions options, XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
|
||||
{
|
||||
if (streamInfo == null)
|
||||
|
@ -384,7 +371,7 @@ namespace Emby.Dlna.Didl
|
|||
var filename = url.Substring(0, url.IndexOf('?'));
|
||||
|
||||
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
||||
? GetMimeType(filename)
|
||||
? MimeTypes.GetMimeType(filename)
|
||||
: mediaProfile.MimeType;
|
||||
|
||||
writer.WriteAttributeString("protocolInfo", string.Format(
|
||||
|
@ -520,7 +507,7 @@ namespace Emby.Dlna.Didl
|
|||
var filename = url.Substring(0, url.IndexOf('?'));
|
||||
|
||||
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
||||
? GetMimeType(filename)
|
||||
? MimeTypes.GetMimeType(filename)
|
||||
: mediaProfile.MimeType;
|
||||
|
||||
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container,
|
||||
|
@ -545,17 +532,10 @@ namespace Emby.Dlna.Didl
|
|||
}
|
||||
|
||||
public static bool IsIdRoot(string id)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(id)
|
||||
=> string.IsNullOrWhiteSpace(id)
|
||||
|| string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
|
||||
// Samsung sometimes uses 1 as root
|
||||
|| string.Equals(id, "1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|| string.Equals(id, "1", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null)
|
||||
{
|
||||
|
@ -971,7 +951,7 @@ namespace Emby.Dlna.Didl
|
|||
|
||||
writer.WriteAttributeString("protocolInfo", string.Format(
|
||||
"http-get:*:{0}:{1}",
|
||||
GetMimeType("file." + format),
|
||||
MimeTypes.GetMimeType("file." + format),
|
||||
contentFeatures
|
||||
));
|
||||
|
||||
|
@ -1102,7 +1082,7 @@ namespace Emby.Dlna.Didl
|
|||
|
||||
public static string GetClientId(Guid idValue, StubType? stubType)
|
||||
{
|
||||
var id = idValue.ToString("N");
|
||||
var id = idValue.ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
if (stubType.HasValue)
|
||||
{
|
||||
|
@ -1116,7 +1096,7 @@ namespace Emby.Dlna.Didl
|
|||
{
|
||||
var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
|
||||
_serverAddress,
|
||||
info.ItemId.ToString("N"),
|
||||
info.ItemId.ToString("N", CultureInfo.InvariantCulture),
|
||||
info.Type,
|
||||
info.ImageTag,
|
||||
format,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
@ -300,7 +301,7 @@ namespace Emby.Dlna
|
|||
|
||||
profile = ReserializeProfile(tempProfile);
|
||||
|
||||
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N");
|
||||
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
|
||||
|
||||
|
@ -352,7 +353,7 @@ namespace Emby.Dlna
|
|||
|
||||
Info = new DeviceProfileInfo
|
||||
{
|
||||
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N"),
|
||||
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
||||
Name = _fileSystem.GetFileNameWithoutExtension(file),
|
||||
Type = type
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ namespace Emby.Dlna.Eventing
|
|||
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
|
||||
{
|
||||
var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
|
||||
var id = "uuid:" + Guid.NewGuid().ToString("N");
|
||||
var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
// Remove logging for now because some devices are sending this very frequently
|
||||
// TODO re-enable with dlna debug logging setting
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.PlayTo;
|
||||
|
@ -247,7 +249,7 @@ namespace Emby.Dlna.Main
|
|||
|
||||
foreach (var address in addresses)
|
||||
{
|
||||
if (address.AddressFamily == IpAddressFamily.InterNetworkV6)
|
||||
if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
// Not support IPv6 right now
|
||||
continue;
|
||||
|
@ -306,7 +308,7 @@ namespace Emby.Dlna.Main
|
|||
{
|
||||
guid = text.GetMD5();
|
||||
}
|
||||
return guid.ToString("N");
|
||||
return guid.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private void SetProperies(SsdpDevice device, string fullDeviceType)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
|
@ -14,7 +16,6 @@ using MediaBrowser.Controller.Session;
|
|||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
@ -141,7 +142,7 @@ namespace Emby.Dlna.PlayTo
|
|||
return usn;
|
||||
}
|
||||
|
||||
return usn.GetMD5().ToString("N");
|
||||
return usn.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private async Task AddDevice(UpnpDeviceInfo info, string location, CancellationToken cancellationToken)
|
||||
|
@ -156,7 +157,7 @@ namespace Emby.Dlna.PlayTo
|
|||
}
|
||||
else
|
||||
{
|
||||
uuid = location.GetMD5().ToString("N");
|
||||
uuid = location.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion, uuid, null, uri.OriginalString, null);
|
||||
|
@ -172,7 +173,7 @@ namespace Emby.Dlna.PlayTo
|
|||
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
|
||||
|
||||
string serverAddress;
|
||||
if (info.LocalIpAddress == null || info.LocalIpAddress.Equals(IpAddressInfo.Any) || info.LocalIpAddress.Equals(IpAddressInfo.IPv6Any))
|
||||
if (info.LocalIpAddress == null || info.LocalIpAddress.Equals(IPAddress.Any) || info.LocalIpAddress.Equals(IPAddress.IPv6Any))
|
||||
{
|
||||
serverAddress = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace Emby.Dlna.Profiles
|
|||
{
|
||||
Name = "Dish Hopper-Joey";
|
||||
|
||||
ProtocolInfo = "http-get:*:video/mp2t:*,http-get:*:video/MP1S:*,http-get:*:video/mpeg2:*,http-get:*:video/mp4:*,http-get:*:video/x-matroska:*,http-get:*:audio/mpeg:*,http-get:*:audio/mpeg3:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/mp4a-latm:*,http-get:*:image/jpeg:*";
|
||||
ProtocolInfo = "http-get:*:video/mp2t:*,http-get:*:video/mpeg:*,http-get:*:video/MP1S:*,http-get:*:video/mpeg2:*,http-get:*:video/mp4:*,http-get:*:video/x-matroska:*,http-get:*:audio/mpeg:*,http-get:*:audio/mpeg3:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/mp4a-latm:*,http-get:*:image/jpeg:*";
|
||||
|
||||
Identification = new DeviceIdentification
|
||||
{
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||
<ProtocolInfo>http-get:*:video/mp2t:*,http-get:*:video/MP1S:*,http-get:*:video/mpeg2:*,http-get:*:video/mp4:*,http-get:*:video/x-matroska:*,http-get:*:audio/mpeg:*,http-get:*:audio/mpeg3:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/mp4a-latm:*,http-get:*:image/jpeg:*</ProtocolInfo>
|
||||
<ProtocolInfo>http-get:*:video/mp2t:http-get:*:video/mpeg:*,http-get:*:video/MP1S:*,http-get:*:video/mpeg2:*,http-get:*:video/mp4:*,http-get:*:video/x-matroska:*,http-get:*:audio/mpeg:*,http-get:*:audio/mpeg3:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/mp4a-latm:*,http-get:*:image/jpeg:*</ProtocolInfo>
|
||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||
|
|
|
@ -454,14 +454,14 @@ namespace Emby.Drawing
|
|||
// Optimization
|
||||
if (imageEnhancers.Length == 0)
|
||||
{
|
||||
return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N");
|
||||
return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
// Cache name is created with supported enhancers combined with the last config change so we pick up new config changes
|
||||
var cacheKeys = imageEnhancers.Select(i => i.GetConfigurationCacheKey(item, imageType)).ToList();
|
||||
cacheKeys.Add(originalImagePath + dateModified.Ticks);
|
||||
|
||||
return string.Join("|", cacheKeys).GetMD5().ToString("N");
|
||||
return string.Join("|", cacheKeys).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
|
||||
|
@ -480,7 +480,7 @@ namespace Emby.Drawing
|
|||
{
|
||||
try
|
||||
{
|
||||
string filename = (originalImagePath + dateModified.Ticks.ToString(UsCulture)).GetMD5().ToString("N");
|
||||
string filename = (originalImagePath + dateModified.Ticks.ToString(UsCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
string cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png";
|
||||
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -101,7 +102,7 @@ namespace Emby.Notifications
|
|||
var config = GetConfiguration();
|
||||
|
||||
return _userManager.Users
|
||||
.Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Policy))
|
||||
.Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i.Policy))
|
||||
.Select(i => i.Id);
|
||||
}
|
||||
|
||||
|
@ -197,7 +198,7 @@ namespace Emby.Notifications
|
|||
return _services.Select(i => new NameIdPair
|
||||
{
|
||||
Name = i.Name,
|
||||
Id = i.Name.GetMD5().ToString("N")
|
||||
Id = i.Name.GetMD5().ToString("N", CultureInfo.InvariantCulture)
|
||||
|
||||
}).OrderBy(i => i.Name);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -75,7 +76,6 @@ namespace Emby.Server.Implementations.Activity
|
|||
_sessionManager.AuthenticationFailed += OnAuthenticationFailed;
|
||||
_sessionManager.AuthenticationSucceeded += OnAuthenticationSucceeded;
|
||||
_sessionManager.SessionEnded += OnSessionEnded;
|
||||
|
||||
_sessionManager.PlaybackStart += OnPlaybackStart;
|
||||
_sessionManager.PlaybackStopped += OnPlaybackStopped;
|
||||
|
||||
|
@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.Activity
|
|||
{
|
||||
Name = string.Format(_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), e.Provider, Notifications.Notifications.GetItemName(e.Item)),
|
||||
Type = "SubtitleDownloadFailure",
|
||||
ItemId = e.Item.Id.ToString("N"),
|
||||
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||
ShortOverview = e.Exception.Message
|
||||
});
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Activity
|
|||
}
|
||||
else
|
||||
{
|
||||
statement.TryBind("@UserId", entry.UserId.ToString("N"));
|
||||
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
||||
|
@ -141,7 +141,7 @@ namespace Emby.Server.Implementations.Activity
|
|||
}
|
||||
else
|
||||
{
|
||||
statement.TryBind("@UserId", entry.UserId.ToString("N"));
|
||||
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
||||
|
|
|
@ -10,6 +10,8 @@ namespace Emby.Server.Implementations.AppBase
|
|||
/// </summary>
|
||||
public abstract class BaseApplicationPaths : IApplicationPaths
|
||||
{
|
||||
private string _dataPath;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
|
||||
/// </summary>
|
||||
|
@ -30,27 +32,27 @@ namespace Emby.Server.Implementations.AppBase
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the program data folder
|
||||
/// Gets the path to the program data folder.
|
||||
/// </summary>
|
||||
/// <value>The program data path.</value>
|
||||
public string ProgramDataPath { get; private set; }
|
||||
public string ProgramDataPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the web UI resources folder
|
||||
/// Gets the path to the web UI resources folder.
|
||||
/// </summary>
|
||||
/// <value>The web UI resources path.</value>
|
||||
public string WebPath { get; set; }
|
||||
public string WebPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the system folder
|
||||
/// Gets the path to the system folder.
|
||||
/// </summary>
|
||||
/// <value>The path to the system folder.</value>
|
||||
public string ProgramSystemPath { get; } = AppContext.BaseDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder path to the data directory
|
||||
/// Gets the folder path to the data directory.
|
||||
/// </summary>
|
||||
/// <value>The data directory.</value>
|
||||
private string _dataPath;
|
||||
public string DataPath
|
||||
{
|
||||
get => _dataPath;
|
||||
|
@ -58,8 +60,9 @@ namespace Emby.Server.Implementations.AppBase
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the magic strings used for virtual path manipulation.
|
||||
/// Gets the magic string used for virtual path manipulation.
|
||||
/// </summary>
|
||||
/// <value>The magic string used for virtual path manipulation.</value>
|
||||
public string VirtualDataPath { get; } = "%AppDataPath%";
|
||||
|
||||
/// <summary>
|
||||
|
@ -69,43 +72,43 @@ namespace Emby.Server.Implementations.AppBase
|
|||
public string ImageCachePath => Path.Combine(CachePath, "images");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the plugin directory
|
||||
/// Gets the path to the plugin directory.
|
||||
/// </summary>
|
||||
/// <value>The plugins path.</value>
|
||||
public string PluginsPath => Path.Combine(ProgramDataPath, "plugins");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the plugin configurations directory
|
||||
/// Gets the path to the plugin configurations directory.
|
||||
/// </summary>
|
||||
/// <value>The plugin configurations path.</value>
|
||||
public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the log directory
|
||||
/// Gets the path to the log directory.
|
||||
/// </summary>
|
||||
/// <value>The log directory path.</value>
|
||||
public string LogDirectoryPath { get; private set; }
|
||||
public string LogDirectoryPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the application configuration root directory
|
||||
/// Gets the path to the application configuration root directory.
|
||||
/// </summary>
|
||||
/// <value>The configuration directory path.</value>
|
||||
public string ConfigurationDirectoryPath { get; private set; }
|
||||
public string ConfigurationDirectoryPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the system configuration file
|
||||
/// Gets the path to the system configuration file.
|
||||
/// </summary>
|
||||
/// <value>The system configuration file path.</value>
|
||||
public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder path to the cache directory
|
||||
/// Gets or sets the folder path to the cache directory.
|
||||
/// </summary>
|
||||
/// <value>The cache directory.</value>
|
||||
public string CachePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder path to the temp directory within the cache folder
|
||||
/// Gets the folder path to the temp directory within the cache folder.
|
||||
/// </summary>
|
||||
/// <value>The temp directory.</value>
|
||||
public string TempDirectory => Path.Combine(CachePath, "temp");
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
@ -19,11 +20,44 @@ namespace Emby.Server.Implementations.AppBase
|
|||
/// </summary>
|
||||
public abstract class BaseConfigurationManager : IConfigurationManager
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
private readonly ConcurrentDictionary<string, object> _configurations = new ConcurrentDictionary<string, object>();
|
||||
|
||||
private ConfigurationStore[] _configurationStores = Array.Empty<ConfigurationStore>();
|
||||
private IConfigurationFactory[] _configurationFactories = Array.Empty<IConfigurationFactory>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the configuration.
|
||||
/// The _configuration loaded.
|
||||
/// </summary>
|
||||
/// <value>The type of the configuration.</value>
|
||||
protected abstract Type ConfigurationType { get; }
|
||||
private bool _configurationLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// The _configuration sync lock.
|
||||
/// </summary>
|
||||
private object _configurationSyncLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// The _configuration.
|
||||
/// </summary>
|
||||
private BaseApplicationConfiguration _configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseConfigurationManager" /> class.
|
||||
/// </summary>
|
||||
/// <param name="applicationPaths">The application paths.</param>
|
||||
/// <param name="loggerFactory">The logger factory.</param>
|
||||
/// <param name="xmlSerializer">The XML serializer.</param>
|
||||
/// <param name="fileSystem">The file system</param>
|
||||
protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
|
||||
{
|
||||
CommonApplicationPaths = applicationPaths;
|
||||
XmlSerializer = xmlSerializer;
|
||||
_fileSystem = fileSystem;
|
||||
Logger = loggerFactory.CreateLogger(GetType().Name);
|
||||
|
||||
UpdateCachePath();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [configuration updated].
|
||||
|
@ -40,6 +74,12 @@ namespace Emby.Server.Implementations.AppBase
|
|||
/// </summary>
|
||||
public event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the configuration.
|
||||
/// </summary>
|
||||
/// <value>The type of the configuration.</value>
|
||||
protected abstract Type ConfigurationType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger.
|
||||
/// </summary>
|
||||
|
@ -56,20 +96,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||
/// </summary>
|
||||
/// <value>The application paths.</value>
|
||||
public IApplicationPaths CommonApplicationPaths { get; private set; }
|
||||
public readonly IFileSystem FileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// The _configuration loaded
|
||||
/// </summary>
|
||||
private bool _configurationLoaded;
|
||||
/// <summary>
|
||||
/// The _configuration sync lock
|
||||
/// </summary>
|
||||
private object _configurationSyncLock = new object();
|
||||
/// <summary>
|
||||
/// The _configuration
|
||||
/// </summary>
|
||||
private BaseApplicationConfiguration _configuration;
|
||||
/// <summary>
|
||||
/// Gets the system configuration
|
||||
/// </summary>
|
||||
|
@ -90,26 +117,6 @@ namespace Emby.Server.Implementations.AppBase
|
|||
}
|
||||
}
|
||||
|
||||
private ConfigurationStore[] _configurationStores = { };
|
||||
private IConfigurationFactory[] _configurationFactories = { };
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseConfigurationManager" /> class.
|
||||
/// </summary>
|
||||
/// <param name="applicationPaths">The application paths.</param>
|
||||
/// <param name="loggerFactory">The logger factory.</param>
|
||||
/// <param name="xmlSerializer">The XML serializer.</param>
|
||||
/// <param name="fileSystem">The file system</param>
|
||||
protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
|
||||
{
|
||||
CommonApplicationPaths = applicationPaths;
|
||||
XmlSerializer = xmlSerializer;
|
||||
FileSystem = fileSystem;
|
||||
Logger = loggerFactory.CreateLogger(GetType().Name);
|
||||
|
||||
UpdateCachePath();
|
||||
}
|
||||
|
||||
public virtual void AddParts(IEnumerable<IConfigurationFactory> factories)
|
||||
{
|
||||
_configurationFactories = factories.ToArray();
|
||||
|
@ -171,6 +178,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||
private void UpdateCachePath()
|
||||
{
|
||||
string cachePath;
|
||||
|
||||
// If the configuration file has no entry (i.e. not set in UI)
|
||||
if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath))
|
||||
{
|
||||
|
@ -207,12 +215,16 @@ namespace Emby.Server.Implementations.AppBase
|
|||
var newPath = newConfig.CachePath;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(newPath)
|
||||
&& !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath))
|
||||
&& !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath, StringComparison.Ordinal))
|
||||
{
|
||||
// Validate
|
||||
if (!Directory.Exists(newPath))
|
||||
{
|
||||
throw new FileNotFoundException(string.Format("{0} does not exist.", newPath));
|
||||
throw new FileNotFoundException(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} does not exist.",
|
||||
newPath));
|
||||
}
|
||||
|
||||
EnsureWriteAccess(newPath);
|
||||
|
@ -223,11 +235,9 @@ namespace Emby.Server.Implementations.AppBase
|
|||
{
|
||||
var file = Path.Combine(path, Guid.NewGuid().ToString());
|
||||
File.WriteAllText(file, string.Empty);
|
||||
FileSystem.DeleteFile(file);
|
||||
_fileSystem.DeleteFile(file);
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, object> _configurations = new ConcurrentDictionary<string, object>();
|
||||
|
||||
private string GetConfigurationFile(string key)
|
||||
{
|
||||
return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLowerInvariant() + ".xml");
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
@ -107,9 +108,9 @@ using Microsoft.AspNetCore.Hosting;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ServiceStack;
|
||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||
|
||||
|
@ -385,7 +386,7 @@ namespace Emby.Server.Implementations
|
|||
|
||||
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
|
||||
|
||||
NetworkManager.NetworkChanged += NetworkManager_NetworkChanged;
|
||||
NetworkManager.NetworkChanged += OnNetworkChanged;
|
||||
}
|
||||
|
||||
public string ExpandVirtualPath(string path)
|
||||
|
@ -409,7 +410,7 @@ namespace Emby.Server.Implementations
|
|||
return ServerConfigurationManager.Configuration.LocalNetworkSubnets;
|
||||
}
|
||||
|
||||
private void NetworkManager_NetworkChanged(object sender, EventArgs e)
|
||||
private void OnNetworkChanged(object sender, EventArgs e)
|
||||
{
|
||||
_validAddressResults.Clear();
|
||||
}
|
||||
|
@ -417,10 +418,10 @@ namespace Emby.Server.Implementations
|
|||
public string ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current application user agent
|
||||
/// Gets the current application user agent.
|
||||
/// </summary>
|
||||
/// <value>The application user agent.</value>
|
||||
public string ApplicationUserAgent => Name.Replace(' ','-') + '/' + ApplicationVersion;
|
||||
public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the email address for use within a comment section of a user agent field.
|
||||
|
@ -428,14 +429,11 @@ namespace Emby.Server.Implementations
|
|||
/// </summary>
|
||||
public string ApplicationUserAgentAddress { get; } = "team@jellyfin.org";
|
||||
|
||||
private string _productName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current application name
|
||||
/// Gets the current application name.
|
||||
/// </summary>
|
||||
/// <value>The application name.</value>
|
||||
public string ApplicationProductName
|
||||
=> _productName ?? (_productName = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName);
|
||||
public string ApplicationProductName { get; } = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName;
|
||||
|
||||
private DeviceId _deviceId;
|
||||
|
||||
|
@ -469,8 +467,8 @@ namespace Emby.Server.Implementations
|
|||
/// <summary>
|
||||
/// Creates an instance of type and resolves all constructor dependencies
|
||||
/// </summary>
|
||||
/// /// <typeparam name="T">The type</typeparam>
|
||||
/// <returns>T</returns>
|
||||
/// /// <typeparam name="T">The type.</typeparam>
|
||||
/// <returns>T.</returns>
|
||||
public T CreateInstance<T>()
|
||||
=> ActivatorUtilities.CreateInstance<T>(_serviceProvider);
|
||||
|
||||
|
@ -603,10 +601,15 @@ namespace Emby.Server.Implementations
|
|||
|
||||
foreach (var plugin in Plugins)
|
||||
{
|
||||
pluginBuilder.AppendLine(string.Format("{0} {1}", plugin.Name, plugin.Version));
|
||||
pluginBuilder.AppendLine(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} {1}",
|
||||
plugin.Name,
|
||||
plugin.Version));
|
||||
}
|
||||
|
||||
Logger.LogInformation("Plugins: {plugins}", pluginBuilder.ToString());
|
||||
Logger.LogInformation("Plugins: {Plugins}", pluginBuilder.ToString());
|
||||
}
|
||||
|
||||
DiscoverTypes();
|
||||
|
@ -628,7 +631,7 @@ namespace Emby.Server.Implementations
|
|||
|
||||
if (EnableHttps && Certificate != null)
|
||||
{
|
||||
options.ListenAnyIP(HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); });
|
||||
options.ListenAnyIP(HttpsPort, listenOptions => listenOptions.UseHttps(Certificate));
|
||||
}
|
||||
})
|
||||
.UseContentRoot(contentRoot)
|
||||
|
@ -642,6 +645,7 @@ namespace Emby.Server.Implementations
|
|||
app.UseWebSockets();
|
||||
|
||||
app.UseResponseCompression();
|
||||
|
||||
// TODO app.UseMiddleware<WebSocketMiddleware>();
|
||||
app.Use(ExecuteWebsocketHandlerAsync);
|
||||
app.Use(ExecuteHttpHandlerAsync);
|
||||
|
@ -675,7 +679,7 @@ namespace Emby.Server.Implementations
|
|||
var localPath = context.Request.Path.ToString();
|
||||
|
||||
var req = new WebSocketSharpRequest(request, response, request.Path, Logger);
|
||||
await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false);
|
||||
await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static IStreamHelper StreamHelper { get; set; }
|
||||
|
@ -784,7 +788,7 @@ namespace Emby.Server.Implementations
|
|||
|
||||
HttpServer = new HttpListenerHost(
|
||||
this,
|
||||
LoggerFactory,
|
||||
LoggerFactory.CreateLogger<HttpListenerHost>(),
|
||||
ServerConfigurationManager,
|
||||
_configuration,
|
||||
NetworkManager,
|
||||
|
@ -872,7 +876,7 @@ namespace Emby.Server.Implementations
|
|||
serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
|
||||
serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
|
||||
|
||||
AuthService = new AuthService(UserManager, authContext, ServerConfigurationManager, SessionManager, NetworkManager);
|
||||
AuthService = new AuthService(authContext, ServerConfigurationManager, SessionManager, NetworkManager);
|
||||
serviceCollection.AddSingleton(AuthService);
|
||||
|
||||
SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory);
|
||||
|
@ -1043,8 +1047,8 @@ namespace Emby.Server.Implementations
|
|||
.Cast<IServerEntryPoint>()
|
||||
.ToList();
|
||||
|
||||
await Task.WhenAll(StartEntryPoints(entries, true));
|
||||
await Task.WhenAll(StartEntryPoints(entries, false));
|
||||
await Task.WhenAll(StartEntryPoints(entries, true)).ConfigureAwait(false);
|
||||
await Task.WhenAll(StartEntryPoints(entries, false)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1219,7 +1223,7 @@ namespace Emby.Server.Implementations
|
|||
|
||||
// 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 certPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.ProgramDataPath, "ssl", "cert_" + (certHost + "2").GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".pfx");
|
||||
const string Password = "embycert";
|
||||
|
||||
return new CertificateInfo
|
||||
|
@ -1457,15 +1461,10 @@ namespace Emby.Server.Implementations
|
|||
};
|
||||
}
|
||||
|
||||
public WakeOnLanInfo[] GetWakeOnLanInfo()
|
||||
{
|
||||
return NetworkManager.GetMacAddresses()
|
||||
.Select(i => new WakeOnLanInfo
|
||||
{
|
||||
MacAddress = i
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
public IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo()
|
||||
=> NetworkManager.GetMacAddresses()
|
||||
.Select(i => new WakeOnLanInfo(i))
|
||||
.ToList();
|
||||
|
||||
public async Task<PublicSystemInfo> GetPublicSystemInfo(CancellationToken cancellationToken)
|
||||
{
|
||||
|
@ -1481,6 +1480,7 @@ namespace Emby.Server.Implementations
|
|||
{
|
||||
wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns);
|
||||
}
|
||||
|
||||
return new PublicSystemInfo
|
||||
{
|
||||
Version = ApplicationVersion,
|
||||
|
@ -1546,14 +1546,32 @@ namespace Emby.Server.Implementations
|
|||
return null;
|
||||
}
|
||||
|
||||
public string GetLocalApiUrl(IpAddressInfo ipAddress)
|
||||
/// <summary>
|
||||
/// Removes the scope id from IPv6 addresses.
|
||||
/// </summary>
|
||||
/// <param name="address">The IPv6 address.</param>
|
||||
/// <returns>The IPv6 address without the scope id.</returns>
|
||||
private string RemoveScopeId(string address)
|
||||
{
|
||||
if (ipAddress.AddressFamily == IpAddressFamily.InterNetworkV6)
|
||||
var index = address.IndexOf('%');
|
||||
if (index == -1)
|
||||
{
|
||||
return GetLocalApiUrl("[" + ipAddress.Address + "]");
|
||||
return address;
|
||||
}
|
||||
|
||||
return GetLocalApiUrl(ipAddress.Address);
|
||||
return address.Substring(0, index);
|
||||
}
|
||||
|
||||
public string GetLocalApiUrl(IPAddress ipAddress)
|
||||
{
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
var str = RemoveScopeId(ipAddress.ToString());
|
||||
|
||||
return GetLocalApiUrl("[" + str + "]");
|
||||
}
|
||||
|
||||
return GetLocalApiUrl(ipAddress.ToString());
|
||||
}
|
||||
|
||||
public string GetLocalApiUrl(string host)
|
||||
|
@ -1564,19 +1582,22 @@ namespace Emby.Server.Implementations
|
|||
host,
|
||||
HttpsPort.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
return string.Format("http://{0}:{1}",
|
||||
host,
|
||||
HttpPort.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
public string GetWanApiUrl(IpAddressInfo ipAddress)
|
||||
public string GetWanApiUrl(IPAddress ipAddress)
|
||||
{
|
||||
if (ipAddress.AddressFamily == IpAddressFamily.InterNetworkV6)
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
return GetWanApiUrl("[" + ipAddress.Address + "]");
|
||||
var str = RemoveScopeId(ipAddress.ToString());
|
||||
|
||||
return GetWanApiUrl("[" + str + "]");
|
||||
}
|
||||
|
||||
return GetWanApiUrl(ipAddress.Address);
|
||||
return GetWanApiUrl(ipAddress.ToString());
|
||||
}
|
||||
|
||||
public string GetWanApiUrl(string host)
|
||||
|
@ -1587,17 +1608,18 @@ namespace Emby.Server.Implementations
|
|||
host,
|
||||
ServerConfigurationManager.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
return string.Format("http://{0}:{1}",
|
||||
host,
|
||||
ServerConfigurationManager.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
public Task<List<IpAddressInfo>> GetLocalIpAddresses(CancellationToken cancellationToken)
|
||||
public Task<List<IPAddress>> GetLocalIpAddresses(CancellationToken cancellationToken)
|
||||
{
|
||||
return GetLocalIpAddressesInternal(true, 0, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<List<IpAddressInfo>> GetLocalIpAddressesInternal(bool allowLoopback, int limit, CancellationToken cancellationToken)
|
||||
private async Task<List<IPAddress>> GetLocalIpAddressesInternal(bool allowLoopback, int limit, CancellationToken cancellationToken)
|
||||
{
|
||||
var addresses = ServerConfigurationManager
|
||||
.Configuration
|
||||
|
@ -1611,13 +1633,13 @@ namespace Emby.Server.Implementations
|
|||
addresses.AddRange(NetworkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
|
||||
}
|
||||
|
||||
var resultList = new List<IpAddressInfo>();
|
||||
var resultList = new List<IPAddress>();
|
||||
|
||||
foreach (var address in addresses)
|
||||
{
|
||||
if (!allowLoopback)
|
||||
{
|
||||
if (address.Equals(IpAddressInfo.Loopback) || address.Equals(IpAddressInfo.IPv6Loopback))
|
||||
if (address.Equals(IPAddress.Loopback) || address.Equals(IPAddress.IPv6Loopback))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
@ -1638,7 +1660,7 @@ namespace Emby.Server.Implementations
|
|||
return resultList;
|
||||
}
|
||||
|
||||
private IpAddressInfo NormalizeConfiguredLocalAddress(string address)
|
||||
private IPAddress NormalizeConfiguredLocalAddress(string address)
|
||||
{
|
||||
var index = address.Trim('/').IndexOf('/');
|
||||
|
||||
|
@ -1647,7 +1669,7 @@ namespace Emby.Server.Implementations
|
|||
address = address.Substring(index + 1);
|
||||
}
|
||||
|
||||
if (NetworkManager.TryParseIpAddress(address.Trim('/'), out IpAddressInfo result))
|
||||
if (IPAddress.TryParse(address.Trim('/'), out IPAddress result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
@ -1657,10 +1679,10 @@ namespace Emby.Server.Implementations
|
|||
|
||||
private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private async Task<bool> IsIpAddressValidAsync(IpAddressInfo address, CancellationToken cancellationToken)
|
||||
private async Task<bool> IsIpAddressValidAsync(IPAddress address, CancellationToken cancellationToken)
|
||||
{
|
||||
if (address.Equals(IpAddressInfo.Loopback) ||
|
||||
address.Equals(IpAddressInfo.IPv6Loopback))
|
||||
if (address.Equals(IPAddress.Loopback) ||
|
||||
address.Equals(IPAddress.IPv6Loopback))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
@ -206,7 +207,7 @@ namespace Emby.Server.Implementations.Channels
|
|||
|
||||
try
|
||||
{
|
||||
return GetChannelProvider(i).IsEnabledFor(user.Id.ToString("N"));
|
||||
return GetChannelProvider(i).IsEnabledFor(user.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@ -511,7 +512,7 @@ namespace Emby.Server.Implementations.Channels
|
|||
IncludeItemTypes = new[] { typeof(Channel).Name },
|
||||
OrderBy = new ValueTuple<string, SortOrder>[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) }
|
||||
|
||||
}).Select(i => GetChannelFeatures(i.ToString("N"))).ToArray();
|
||||
}).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
|
||||
}
|
||||
|
||||
public ChannelFeatures GetChannelFeatures(string id)
|
||||
|
@ -552,7 +553,7 @@ namespace Emby.Server.Implementations.Channels
|
|||
SupportsSortOrderToggle = features.SupportsSortOrderToggle,
|
||||
SupportsLatestMedia = supportsLatest,
|
||||
Name = channel.Name,
|
||||
Id = channel.Id.ToString("N"),
|
||||
Id = channel.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||
SupportsContentDownloading = features.SupportsContentDownloading,
|
||||
AutoRefreshLevels = features.AutoRefreshLevels
|
||||
};
|
||||
|
@ -740,7 +741,7 @@ namespace Emby.Server.Implementations.Channels
|
|||
bool sortDescending,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var userId = user == null ? null : user.Id.ToString("N");
|
||||
var userId = user == null ? null : user.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
var cacheLength = CacheLength;
|
||||
var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
|
||||
|
@ -836,7 +837,7 @@ namespace Emby.Server.Implementations.Channels
|
|||
ChannelItemSortField? sortField,
|
||||
bool sortDescending)
|
||||
{
|
||||
var channelId = GetInternalChannelId(channel.Name).ToString("N");
|
||||
var channelId = GetInternalChannelId(channel.Name).ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
var userCacheKey = string.Empty;
|
||||
|
||||
|
@ -846,10 +847,10 @@ namespace Emby.Server.Implementations.Channels
|
|||
userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty;
|
||||
}
|
||||
|
||||
var filename = string.IsNullOrEmpty(externalFolderId) ? "root" : externalFolderId.GetMD5().ToString("N");
|
||||
var filename = string.IsNullOrEmpty(externalFolderId) ? "root" : externalFolderId.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
filename += userCacheKey;
|
||||
|
||||
var version = ((channel.DataVersion ?? string.Empty) + "2").GetMD5().ToString("N");
|
||||
var version = ((channel.DataVersion ?? string.Empty) + "2").GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
if (sortField.HasValue)
|
||||
{
|
||||
|
@ -860,7 +861,7 @@ namespace Emby.Server.Implementations.Channels
|
|||
filename += "-sortDescending";
|
||||
}
|
||||
|
||||
filename = filename.GetMD5().ToString("N");
|
||||
filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
return Path.Combine(_config.ApplicationPaths.CachePath,
|
||||
"channels",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
@ -182,7 +183,7 @@ namespace Emby.Server.Implementations.Collections
|
|||
|
||||
public void AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
|
||||
{
|
||||
AddToCollection(collectionId, ids.Select(i => i.ToString("N")), true, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)));
|
||||
AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)));
|
||||
}
|
||||
|
||||
private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
|
||||
|
|
|
@ -6,8 +6,8 @@ namespace Emby.Server.Implementations
|
|||
{
|
||||
public static readonly Dictionary<string, string> Configuration = new Dictionary<string, string>
|
||||
{
|
||||
{"HttpListenerHost:DefaultRedirectPath", "web/index.html"},
|
||||
{"MusicBrainz:BaseUrl", "https://www.musicbrainz.org"}
|
||||
{ "HttpListenerHost:DefaultRedirectPath", "web/index.html" },
|
||||
{ "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ using MediaBrowser.Model.Cryptography;
|
|||
|
||||
namespace Emby.Server.Implementations.Cryptography
|
||||
{
|
||||
public class CryptographyProvider : ICryptoProvider
|
||||
public class CryptographyProvider : ICryptoProvider, IDisposable
|
||||
{
|
||||
private static readonly HashSet<string> _supportedHashMethods = new HashSet<string>()
|
||||
{
|
||||
|
@ -28,26 +28,28 @@ namespace Emby.Server.Implementations.Cryptography
|
|||
"System.Security.Cryptography.SHA512"
|
||||
};
|
||||
|
||||
public string DefaultHashMethod => "PBKDF2";
|
||||
|
||||
private RandomNumberGenerator _randomNumberGenerator;
|
||||
|
||||
private const int _defaultIterations = 1000;
|
||||
|
||||
private bool _disposed = false;
|
||||
|
||||
public CryptographyProvider()
|
||||
{
|
||||
//FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
|
||||
//Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1
|
||||
//there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one
|
||||
//Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1
|
||||
// FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
|
||||
// Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1
|
||||
// there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one
|
||||
// Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1
|
||||
_randomNumberGenerator = RandomNumberGenerator.Create();
|
||||
}
|
||||
|
||||
public Guid GetMD5(string str)
|
||||
{
|
||||
return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str)));
|
||||
}
|
||||
public string DefaultHashMethod => "PBKDF2";
|
||||
|
||||
[Obsolete("Use System.Security.Cryptography.MD5 directly")]
|
||||
public Guid GetMD5(string str)
|
||||
=> new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str)));
|
||||
|
||||
[Obsolete("Use System.Security.Cryptography.SHA1 directly")]
|
||||
public byte[] ComputeSHA1(byte[] bytes)
|
||||
{
|
||||
using (var provider = SHA1.Create())
|
||||
|
@ -56,6 +58,7 @@ namespace Emby.Server.Implementations.Cryptography
|
|||
}
|
||||
}
|
||||
|
||||
[Obsolete("Use System.Security.Cryptography.MD5 directly")]
|
||||
public byte[] ComputeMD5(Stream str)
|
||||
{
|
||||
using (var provider = MD5.Create())
|
||||
|
@ -64,6 +67,7 @@ namespace Emby.Server.Implementations.Cryptography
|
|||
}
|
||||
}
|
||||
|
||||
[Obsolete("Use System.Security.Cryptography.MD5 directly")]
|
||||
public byte[] ComputeMD5(byte[] bytes)
|
||||
{
|
||||
using (var provider = MD5.Create())
|
||||
|
@ -73,9 +77,7 @@ namespace Emby.Server.Implementations.Cryptography
|
|||
}
|
||||
|
||||
public IEnumerable<string> GetSupportedHashMethods()
|
||||
{
|
||||
return _supportedHashMethods;
|
||||
}
|
||||
=> _supportedHashMethods;
|
||||
|
||||
private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations)
|
||||
{
|
||||
|
@ -93,14 +95,10 @@ namespace Emby.Server.Implementations.Cryptography
|
|||
}
|
||||
|
||||
public byte[] ComputeHash(string hashMethod, byte[] bytes)
|
||||
{
|
||||
return ComputeHash(hashMethod, bytes, Array.Empty<byte>());
|
||||
}
|
||||
=> ComputeHash(hashMethod, bytes, Array.Empty<byte>());
|
||||
|
||||
public byte[] ComputeHashWithDefaultMethod(byte[] bytes)
|
||||
{
|
||||
return ComputeHash(DefaultHashMethod, bytes);
|
||||
}
|
||||
=> ComputeHash(DefaultHashMethod, bytes);
|
||||
|
||||
public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt)
|
||||
{
|
||||
|
@ -125,37 +123,27 @@ namespace Emby.Server.Implementations.Cryptography
|
|||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
|
||||
}
|
||||
|
||||
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
|
||||
|
||||
}
|
||||
|
||||
public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
|
||||
{
|
||||
return PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations);
|
||||
}
|
||||
=> PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations);
|
||||
|
||||
public byte[] ComputeHash(PasswordHash hash)
|
||||
{
|
||||
int iterations = _defaultIterations;
|
||||
if (!hash.Parameters.ContainsKey("iterations"))
|
||||
{
|
||||
hash.Parameters.Add("iterations", _defaultIterations.ToString(CultureInfo.InvariantCulture));
|
||||
hash.Parameters.Add("iterations", iterations.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
else
|
||||
else if (!int.TryParse(hash.Parameters["iterations"], out iterations))
|
||||
{
|
||||
try
|
||||
{
|
||||
iterations = int.Parse(hash.Parameters["iterations"]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e);
|
||||
}
|
||||
throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}");
|
||||
}
|
||||
|
||||
return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations);
|
||||
return PBKDF2(hash.Id, hash.Hash, hash.Salt, iterations);
|
||||
}
|
||||
|
||||
public byte[] GenerateSalt()
|
||||
|
@ -164,5 +152,29 @@ namespace Emby.Server.Implementations.Cryptography
|
|||
_randomNumberGenerator.GetBytes(salt);
|
||||
return salt;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_randomNumberGenerator.Dispose();
|
||||
}
|
||||
|
||||
_randomNumberGenerator = null;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
@ -182,7 +183,7 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
return new DisplayPreferences
|
||||
{
|
||||
Id = guidId.ToString("N")
|
||||
Id = guidId.ToString("N", CultureInfo.InvariantCulture)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -696,7 +696,7 @@ namespace Emby.Server.Implementations.Data
|
|||
saveItemStatement.TryBindNull("@EndDate");
|
||||
}
|
||||
|
||||
saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(Guid.Empty) ? null : item.ChannelId.ToString("N"));
|
||||
saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(Guid.Empty) ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture));
|
||||
|
||||
if (item is IHasProgramAttributes hasProgramAttributes)
|
||||
{
|
||||
|
@ -851,7 +851,7 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
else
|
||||
{
|
||||
saveItemStatement.TryBind("@TopParentId", topParent.Id.ToString("N"));
|
||||
saveItemStatement.TryBind("@TopParentId", topParent.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
if (item is Trailer trailer && trailer.TrailerTypes.Length > 0)
|
||||
|
@ -3548,12 +3548,12 @@ namespace Emby.Server.Implementations.Data
|
|||
whereClauses.Add("ChannelId=@ChannelId");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ChannelId", query.ChannelIds[0].ToString("N"));
|
||||
statement.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
else if (query.ChannelIds.Length > 1)
|
||||
{
|
||||
var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i.ToString("N") + "'"));
|
||||
var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
|
||||
whereClauses.Add($"ChannelId in ({inClause})");
|
||||
}
|
||||
|
||||
|
@ -4537,12 +4537,12 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@TopParentId", queryTopParentIds[0].ToString("N"));
|
||||
statement.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
else if (queryTopParentIds.Length > 1)
|
||||
{
|
||||
var val = string.Join(",", queryTopParentIds.Select(i => "'" + i.ToString("N") + "'"));
|
||||
var val = string.Join(",", queryTopParentIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
|
||||
|
||||
if (enableItemsByName && includedItemByNameTypes.Count == 1)
|
||||
{
|
||||
|
@ -4574,7 +4574,7 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
if (query.AncestorIds.Length > 1)
|
||||
{
|
||||
var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + i.ToString("N") + "'"));
|
||||
var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
|
||||
whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause));
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey))
|
||||
|
@ -4637,7 +4637,7 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
foreach (var folderId in query.BoxSetLibraryFolders)
|
||||
{
|
||||
folderIdQueries.Add("data like '%" + folderId.ToString("N") + "%'");
|
||||
folderIdQueries.Add("data like '%" + folderId.ToString("N", CultureInfo.InvariantCulture) + "%'");
|
||||
}
|
||||
|
||||
whereClauses.Add("(" + string.Join(" OR ", folderIdQueries) + ")");
|
||||
|
@ -5161,7 +5161,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
var ancestorId = ancestorIds[i];
|
||||
|
||||
statement.TryBind("@AncestorId" + index, ancestorId.ToGuidBlob());
|
||||
statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N"));
|
||||
statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
statement.Reset();
|
||||
|
@ -5579,6 +5579,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||
{
|
||||
counts.TrailerCount = value;
|
||||
}
|
||||
|
||||
counts.ItemCount += value;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Devices
|
||||
|
@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.Devices
|
|||
|
||||
private static string GetNewId()
|
||||
{
|
||||
return Guid.NewGuid().ToString("N");
|
||||
return Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private string GetDeviceId()
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
@ -195,7 +195,7 @@ namespace Emby.Server.Implementations.Devices
|
|||
|
||||
private string GetDevicePath(string id)
|
||||
{
|
||||
return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N"));
|
||||
return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
public ContentUploadHistory GetCameraUploadHistory(string deviceId)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -213,7 +214,7 @@ namespace Emby.Server.Implementations.Dto
|
|||
|
||||
if (options.ContainsField(ItemFields.DisplayPreferencesId))
|
||||
{
|
||||
dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N");
|
||||
dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (user != null)
|
||||
|
@ -444,7 +445,7 @@ namespace Emby.Server.Implementations.Dto
|
|||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
public string GetDtoId(BaseItem item)
|
||||
{
|
||||
return item.Id.ToString("N");
|
||||
return item.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static void SetBookProperties(BaseItemDto dto, Book item)
|
||||
|
@ -608,7 +609,7 @@ namespace Emby.Server.Implementations.Dto
|
|||
if (dictionary.TryGetValue(person.Name, out Person entity))
|
||||
{
|
||||
baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary);
|
||||
baseItemPerson.Id = entity.Id.ToString("N");
|
||||
baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
list.Add(baseItemPerson);
|
||||
}
|
||||
}
|
||||
|
@ -893,7 +894,7 @@ namespace Emby.Server.Implementations.Dto
|
|||
//var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
|
||||
//{
|
||||
// EnableTotalRecordCount = false,
|
||||
// ItemIds = new[] { item.Id.ToString("N") }
|
||||
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
|
||||
//});
|
||||
|
||||
//dto.ArtistItems = artistItems.Items
|
||||
|
@ -903,7 +904,7 @@ namespace Emby.Server.Implementations.Dto
|
|||
// return new NameIdPair
|
||||
// {
|
||||
// Name = artist.Name,
|
||||
// Id = artist.Id.ToString("N")
|
||||
// Id = artist.Id.ToString("N", CultureInfo.InvariantCulture)
|
||||
// };
|
||||
// })
|
||||
// .ToList();
|
||||
|
@ -946,7 +947,7 @@ namespace Emby.Server.Implementations.Dto
|
|||
//var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
|
||||
//{
|
||||
// EnableTotalRecordCount = false,
|
||||
// ItemIds = new[] { item.Id.ToString("N") }
|
||||
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
|
||||
//});
|
||||
|
||||
//dto.AlbumArtists = artistItems.Items
|
||||
|
@ -956,7 +957,7 @@ namespace Emby.Server.Implementations.Dto
|
|||
// return new NameIdPair
|
||||
// {
|
||||
// Name = artist.Name,
|
||||
// Id = artist.Id.ToString("N")
|
||||
// Id = artist.Id.ToString("N", CultureInfo.InvariantCulture)
|
||||
// };
|
||||
// })
|
||||
// .ToList();
|
||||
|
@ -1044,7 +1045,7 @@ namespace Emby.Server.Implementations.Dto
|
|||
}
|
||||
else
|
||||
{
|
||||
string id = item.Id.ToString("N");
|
||||
string id = item.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(i => i.MediaStreams)
|
||||
.ToArray();
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="IPNetwork2" Version="2.4.0.126" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.2.0" />
|
||||
|
@ -43,6 +44,7 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
|
|
|
@ -100,7 +100,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
_lastProgressMessageTimes[item.Id] = DateTime.UtcNow;
|
||||
|
||||
var dict = new Dictionary<string, string>();
|
||||
dict["ItemId"] = item.Id.ToString("N");
|
||||
dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
dict["Progress"] = progress.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
try
|
||||
|
@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
foreach (var collectionFolder in collectionFolders)
|
||||
{
|
||||
var collectionFolderDict = new Dictionary<string, string>();
|
||||
collectionFolderDict["ItemId"] = collectionFolder.Id.ToString("N");
|
||||
collectionFolderDict["ItemId"] = collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
collectionFolderDict["Progress"] = (collectionFolder.GetRefreshProgress() ?? 0).ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
try
|
||||
|
@ -378,15 +378,15 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
|
||||
return new LibraryUpdateInfo
|
||||
{
|
||||
ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(),
|
||||
ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(),
|
||||
|
||||
ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(),
|
||||
ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(),
|
||||
|
||||
ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true)).Select(i => i.Id.ToString("N")).Distinct().ToArray(),
|
||||
ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(),
|
||||
|
||||
FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(),
|
||||
FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(),
|
||||
|
||||
FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(),
|
||||
FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(),
|
||||
|
||||
CollectionFolders = GetTopParentIds(newAndRemoved, allUserRootChildren).ToArray()
|
||||
};
|
||||
|
@ -422,7 +422,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
var collectionFolders = _libraryManager.GetCollectionFolders(item, allUserRootChildren);
|
||||
foreach (var folder in allUserRootChildren)
|
||||
{
|
||||
list.Add(folder.Id.ToString("N"));
|
||||
list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
|
@ -134,7 +135,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
/// <param name="e">The e.</param>
|
||||
void userManager_UserDeleted(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N"));
|
||||
SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -8,7 +9,6 @@ using MediaBrowser.Controller.Library;
|
|||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
@ -125,12 +125,12 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
.Select(i =>
|
||||
{
|
||||
var dto = _userDataManager.GetUserDataDto(i, user);
|
||||
dto.ItemId = i.Id.ToString("N");
|
||||
dto.ItemId = i.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
return dto;
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
var userIdString = userId.ToString("N");
|
||||
var userIdString = userId.ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
return new UserDataChangeInfo
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
@ -195,7 +196,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
|||
}
|
||||
|
||||
var url = options.Url;
|
||||
var urlHash = url.ToLowerInvariant().GetMD5().ToString("N");
|
||||
var urlHash = url.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash);
|
||||
|
||||
|
|
|
@ -1,50 +1,43 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Emby.Server.Implementations.HttpServer
|
||||
{
|
||||
public class FileWriter : IHttpResult
|
||||
{
|
||||
private static readonly CultureInfo UsCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
|
||||
|
||||
private static readonly string[] _skipLogExtensions = {
|
||||
".js",
|
||||
".html",
|
||||
".css"
|
||||
};
|
||||
|
||||
private readonly IStreamHelper _streamHelper;
|
||||
private ILogger Logger { get; set; }
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
private string RangeHeader { get; set; }
|
||||
private bool IsHeadRequest { get; set; }
|
||||
|
||||
private long RangeStart { get; set; }
|
||||
private long RangeEnd { get; set; }
|
||||
private long RangeLength { get; set; }
|
||||
public long TotalContentLength { get; set; }
|
||||
|
||||
public Action OnComplete { get; set; }
|
||||
public Action OnError { get; set; }
|
||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
public List<Cookie> Cookies { get; private set; }
|
||||
|
||||
public FileShareMode FileShare { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The _options
|
||||
/// </summary>
|
||||
private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
|
||||
/// <summary>
|
||||
/// Gets the options.
|
||||
/// </summary>
|
||||
/// <value>The options.</value>
|
||||
public IDictionary<string, string> Headers => _options;
|
||||
|
||||
public string Path { get; set; }
|
||||
/// <summary>
|
||||
/// The _requested ranges
|
||||
/// </summary>
|
||||
private List<KeyValuePair<long, long?>> _requestedRanges;
|
||||
|
||||
public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem, IStreamHelper streamHelper)
|
||||
{
|
||||
|
@ -57,7 +50,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
_fileSystem = fileSystem;
|
||||
|
||||
Path = path;
|
||||
Logger = logger;
|
||||
_logger = logger;
|
||||
RangeHeader = rangeHeader;
|
||||
|
||||
Headers[HeaderNames.ContentType] = contentType;
|
||||
|
@ -80,6 +73,88 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
Cookies = new List<Cookie>();
|
||||
}
|
||||
|
||||
private string RangeHeader { get; set; }
|
||||
|
||||
private bool IsHeadRequest { get; set; }
|
||||
|
||||
private long RangeStart { get; set; }
|
||||
|
||||
private long RangeEnd { get; set; }
|
||||
|
||||
private long RangeLength { get; set; }
|
||||
|
||||
public long TotalContentLength { get; set; }
|
||||
|
||||
public Action OnComplete { get; set; }
|
||||
|
||||
public Action OnError { get; set; }
|
||||
|
||||
public List<Cookie> Cookies { get; private set; }
|
||||
|
||||
public FileShareMode FileShare { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the options.
|
||||
/// </summary>
|
||||
/// <value>The options.</value>
|
||||
public IDictionary<string, string> Headers => _options;
|
||||
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the requested ranges.
|
||||
/// </summary>
|
||||
/// <value>The requested ranges.</value>
|
||||
protected List<KeyValuePair<long, long?>> RequestedRanges
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_requestedRanges == null)
|
||||
{
|
||||
_requestedRanges = new List<KeyValuePair<long, long?>>();
|
||||
|
||||
// Example: bytes=0-,32-63
|
||||
var ranges = RangeHeader.Split('=')[1].Split(',');
|
||||
|
||||
foreach (var range in ranges)
|
||||
{
|
||||
var vals = range.Split('-');
|
||||
|
||||
long start = 0;
|
||||
long? end = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(vals[0]))
|
||||
{
|
||||
start = long.Parse(vals[0], UsCulture);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(vals[1]))
|
||||
{
|
||||
end = long.Parse(vals[1], UsCulture);
|
||||
}
|
||||
|
||||
_requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
|
||||
}
|
||||
}
|
||||
|
||||
return _requestedRanges;
|
||||
}
|
||||
}
|
||||
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public IRequest RequestContext { get; set; }
|
||||
|
||||
public object Response { get; set; }
|
||||
|
||||
public int Status { get; set; }
|
||||
|
||||
public HttpStatusCode StatusCode
|
||||
{
|
||||
get => (HttpStatusCode)Status;
|
||||
set => Status = (int)value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the range values.
|
||||
/// </summary>
|
||||
|
@ -106,59 +181,10 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}";
|
||||
Headers[HeaderNames.ContentRange] = rangeString;
|
||||
|
||||
Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString);
|
||||
_logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _requested ranges
|
||||
/// </summary>
|
||||
private List<KeyValuePair<long, long?>> _requestedRanges;
|
||||
/// <summary>
|
||||
/// Gets the requested ranges.
|
||||
/// </summary>
|
||||
/// <value>The requested ranges.</value>
|
||||
protected List<KeyValuePair<long, long?>> RequestedRanges
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_requestedRanges == null)
|
||||
{
|
||||
_requestedRanges = new List<KeyValuePair<long, long?>>();
|
||||
|
||||
// Example: bytes=0-,32-63
|
||||
var ranges = RangeHeader.Split('=')[1].Split(',');
|
||||
|
||||
foreach (var range in ranges)
|
||||
{
|
||||
var vals = range.Split('-');
|
||||
|
||||
long start = 0;
|
||||
long? end = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(vals[0]))
|
||||
{
|
||||
start = long.Parse(vals[0], UsCulture);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(vals[1]))
|
||||
{
|
||||
end = long.Parse(vals[1], UsCulture);
|
||||
}
|
||||
|
||||
_requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
|
||||
}
|
||||
}
|
||||
|
||||
return _requestedRanges;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly string[] SkipLogExtensions = {
|
||||
".js",
|
||||
".html",
|
||||
".css"
|
||||
};
|
||||
|
||||
public async Task WriteToAsync(IResponse response, CancellationToken cancellationToken)
|
||||
public async Task WriteToAsync(HttpResponse response, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -176,16 +202,16 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
{
|
||||
var extension = System.IO.Path.GetExtension(path);
|
||||
|
||||
if (extension == null || !SkipLogExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||
if (extension == null || !_skipLogExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.LogDebug("Transmit file {0}", path);
|
||||
_logger.LogDebug("Transmit file {0}", path);
|
||||
}
|
||||
|
||||
offset = 0;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
await response.TransmitFile(path, offset, count, FileShare, _fileSystem, _streamHelper, cancellationToken).ConfigureAwait(false);
|
||||
await TransmitFile(response.Body, path, offset, count, FileShare, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -193,18 +219,32 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
}
|
||||
}
|
||||
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public IRequest RequestContext { get; set; }
|
||||
|
||||
public object Response { get; set; }
|
||||
|
||||
public int Status { get; set; }
|
||||
|
||||
public HttpStatusCode StatusCode
|
||||
public async Task TransmitFile(Stream stream, string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
||||
{
|
||||
get => (HttpStatusCode)Status;
|
||||
set => Status = (int)value;
|
||||
var fileOpenOptions = FileOpenOptions.SequentialScan;
|
||||
|
||||
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
fileOpenOptions |= FileOpenOptions.Asynchronous;
|
||||
}
|
||||
|
||||
using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions))
|
||||
{
|
||||
if (offset > 0)
|
||||
{
|
||||
fs.Position = offset;
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
await _streamHelper.CopyToAsync(fs, stream, count, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await fs.CopyToAsync(stream, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Net;
|
||||
|
@ -30,11 +29,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
{
|
||||
public class HttpListenerHost : IHttpServer, IDisposable
|
||||
{
|
||||
private string DefaultRedirectPath { get; set; }
|
||||
public string[] UrlPrefixes { get; private set; }
|
||||
|
||||
public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly INetworkManager _networkManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
|
@ -42,18 +37,15 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
private readonly IXmlSerializer _xmlSerializer;
|
||||
private readonly IHttpListener _socketListener;
|
||||
private readonly Func<Type, Func<string, object>> _funcParseFn;
|
||||
|
||||
public Action<IRequest, IResponse, object>[] ResponseFilters { get; set; }
|
||||
|
||||
private readonly string _defaultRedirectPath;
|
||||
private readonly Dictionary<Type, Type> ServiceOperationsMap = new Dictionary<Type, Type>();
|
||||
public static HttpListenerHost Instance { get; protected set; }
|
||||
|
||||
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
|
||||
private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>();
|
||||
private bool _disposed = false;
|
||||
|
||||
public HttpListenerHost(
|
||||
IServerApplicationHost applicationHost,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILogger<HttpListenerHost> logger,
|
||||
IServerConfigurationManager config,
|
||||
IConfiguration configuration,
|
||||
INetworkManager networkManager,
|
||||
|
@ -62,9 +54,9 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
IHttpListener socketListener)
|
||||
{
|
||||
_appHost = applicationHost;
|
||||
Logger = loggerFactory.CreateLogger("HttpServer");
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
DefaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"];
|
||||
_defaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"];
|
||||
_networkManager = networkManager;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_xmlSerializer = xmlSerializer;
|
||||
|
@ -74,12 +66,20 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
|
||||
|
||||
Instance = this;
|
||||
ResponseFilters = Array.Empty<Action<IRequest, IResponse, object>>();
|
||||
ResponseFilters = Array.Empty<Action<IRequest, HttpResponse, object>>();
|
||||
}
|
||||
|
||||
public Action<IRequest, HttpResponse, object>[] ResponseFilters { get; set; }
|
||||
|
||||
public static HttpListenerHost Instance { get; protected set; }
|
||||
|
||||
public string[] UrlPrefixes { get; private set; }
|
||||
|
||||
public string GlobalResponse { get; set; }
|
||||
|
||||
protected ILogger Logger { get; }
|
||||
public ServiceController ServiceController { get; private set; }
|
||||
|
||||
public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
|
||||
|
||||
public object CreateInstance(Type type)
|
||||
{
|
||||
|
@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
/// and no more processing should be done.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void ApplyRequestFilters(IRequest req, IResponse res, object requestDto)
|
||||
public void ApplyRequestFilters(IRequest req, HttpResponse res, object requestDto)
|
||||
{
|
||||
//Exec all RequestFilter attributes with Priority < 0
|
||||
var attributes = GetRequestFilterAttributes(requestDto.GetType());
|
||||
|
@ -145,7 +145,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
return;
|
||||
}
|
||||
|
||||
var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, Logger)
|
||||
var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger)
|
||||
{
|
||||
OnReceive = ProcessWebSocketMessageReceived,
|
||||
Url = e.Url,
|
||||
|
@ -215,16 +215,16 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
|
||||
if (logExceptionStackTrace)
|
||||
{
|
||||
Logger.LogError(ex, "Error processing request");
|
||||
_logger.LogError(ex, "Error processing request");
|
||||
}
|
||||
else if (logExceptionMessage)
|
||||
{
|
||||
Logger.LogError(ex.Message);
|
||||
_logger.LogError(ex.Message);
|
||||
}
|
||||
|
||||
var httpRes = httpReq.Response;
|
||||
|
||||
if (httpRes.OriginalResponse.HasStarted)
|
||||
if (httpRes.HasStarted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -233,11 +233,11 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
httpRes.StatusCode = statusCode;
|
||||
|
||||
httpRes.ContentType = "text/html";
|
||||
await Write(httpRes, NormalizeExceptionMessage(ex.Message)).ConfigureAwait(false);
|
||||
await httpRes.WriteAsync(NormalizeExceptionMessage(ex.Message)).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception errorEx)
|
||||
{
|
||||
Logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
|
||||
_logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -431,7 +431,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
{
|
||||
httpRes.StatusCode = 503;
|
||||
httpRes.ContentType = "text/plain";
|
||||
await Write(httpRes, "Server shutting down").ConfigureAwait(false);
|
||||
await httpRes.WriteAsync("Server shutting down", cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -439,7 +439,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
{
|
||||
httpRes.StatusCode = 400;
|
||||
httpRes.ContentType = "text/plain";
|
||||
await Write(httpRes, "Invalid host").ConfigureAwait(false);
|
||||
await httpRes.WriteAsync("Invalid host", cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -447,7 +447,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
{
|
||||
httpRes.StatusCode = 403;
|
||||
httpRes.ContentType = "text/plain";
|
||||
await Write(httpRes, "Forbidden").ConfigureAwait(false);
|
||||
await httpRes.WriteAsync("Forbidden", cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -460,28 +460,27 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
httpRes.StatusCode = 200;
|
||||
httpRes.AddHeader("Access-Control-Allow-Origin", "*");
|
||||
httpRes.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
|
||||
httpRes.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization");
|
||||
httpRes.Headers.Add("Access-Control-Allow-Origin", "*");
|
||||
httpRes.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
|
||||
httpRes.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization");
|
||||
httpRes.ContentType = "text/plain";
|
||||
await Write(httpRes, string.Empty).ConfigureAwait(false);
|
||||
await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
urlToLog = GetUrlToLog(urlString);
|
||||
Logger.LogDebug("HTTP {HttpMethod} {Url} UserAgent: {UserAgent} \nHeaders: {@Headers}", urlToLog, httpReq.UserAgent ?? string.Empty, httpReq.HttpMethod, httpReq.Headers);
|
||||
|
||||
if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
RedirectToUrl(httpRes, DefaultRedirectPath);
|
||||
httpRes.Redirect(_defaultRedirectPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
RedirectToUrl(httpRes, "emby/" + DefaultRedirectPath);
|
||||
httpRes.Redirect("emby/" + _defaultRedirectPath);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -494,9 +493,10 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
|
||||
if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await Write(httpRes,
|
||||
await httpRes.WriteAsync(
|
||||
"<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" +
|
||||
newUrl + "\">" + newUrl + "</a></body></html>").ConfigureAwait(false);
|
||||
newUrl + "\">" + newUrl + "</a></body></html>",
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -511,34 +511,35 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
|
||||
if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await Write(httpRes,
|
||||
await httpRes.WriteAsync(
|
||||
"<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" +
|
||||
newUrl + "\">" + newUrl + "</a></body></html>").ConfigureAwait(false);
|
||||
newUrl + "\">" + newUrl + "</a></body></html>",
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
RedirectToUrl(httpRes, DefaultRedirectPath);
|
||||
httpRes.Redirect(_defaultRedirectPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
RedirectToUrl(httpRes, "../" + DefaultRedirectPath);
|
||||
httpRes.Redirect("../" + _defaultRedirectPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
RedirectToUrl(httpRes, DefaultRedirectPath);
|
||||
httpRes.Redirect(_defaultRedirectPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(localPath))
|
||||
{
|
||||
RedirectToUrl(httpRes, "/" + DefaultRedirectPath);
|
||||
httpRes.Redirect("/" + _defaultRedirectPath);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -546,12 +547,12 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
{
|
||||
if (localPath.EndsWith("web/dashboard.html", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
RedirectToUrl(httpRes, "index.html#!/dashboard.html");
|
||||
httpRes.Redirect("index.html#!/dashboard.html");
|
||||
}
|
||||
|
||||
if (localPath.EndsWith("web/home.html", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
RedirectToUrl(httpRes, "index.html");
|
||||
httpRes.Redirect("index.html");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -562,7 +563,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
{
|
||||
httpRes.StatusCode = 503;
|
||||
httpRes.ContentType = "text/html";
|
||||
await Write(httpRes, GlobalResponse).ConfigureAwait(false);
|
||||
await httpRes.WriteAsync(GlobalResponse, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -571,7 +572,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
|
||||
if (handler != null)
|
||||
{
|
||||
await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, cancellationToken).ConfigureAwait(false);
|
||||
await handler.ProcessRequestAsync(this, httpReq, httpRes, _logger, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -598,11 +599,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
var elapsed = stopWatch.Elapsed;
|
||||
if (elapsed.TotalMilliseconds > 500)
|
||||
{
|
||||
Logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog);
|
||||
_logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -619,18 +616,11 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
return new ServiceHandler(restPath, contentType);
|
||||
}
|
||||
|
||||
Logger.LogError("Could not find handler for {PathInfo}", pathInfo);
|
||||
_logger.LogError("Could not find handler for {PathInfo}", pathInfo);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Task Write(IResponse response, string text)
|
||||
{
|
||||
var bOutput = Encoding.UTF8.GetBytes(text);
|
||||
response.OriginalResponse.ContentLength = bOutput.Length;
|
||||
return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length);
|
||||
}
|
||||
|
||||
private void RedirectToSecureUrl(IHttpRequest httpReq, IResponse httpRes, string url)
|
||||
private void RedirectToSecureUrl(IHttpRequest httpReq, HttpResponse httpRes, string url)
|
||||
{
|
||||
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri))
|
||||
{
|
||||
|
@ -640,23 +630,11 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
Scheme = "https"
|
||||
};
|
||||
url = builder.Uri.ToString();
|
||||
|
||||
RedirectToUrl(httpRes, url);
|
||||
}
|
||||
else
|
||||
{
|
||||
RedirectToUrl(httpRes, url);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RedirectToUrl(IResponse httpRes, string url)
|
||||
{
|
||||
httpRes.StatusCode = 302;
|
||||
httpRes.AddHeader("Location", url);
|
||||
httpRes.Redirect(url);
|
||||
}
|
||||
|
||||
public ServiceController ServiceController { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds the rest handlers.
|
||||
/// </summary>
|
||||
|
@ -672,9 +650,9 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
var types = services.Select(r => r.GetType());
|
||||
ServiceController.Init(this, types);
|
||||
|
||||
ResponseFilters = new Action<IRequest, IResponse, object>[]
|
||||
ResponseFilters = new Action<IRequest, HttpResponse, object>[]
|
||||
{
|
||||
new ResponseFilter(Logger).FilterResponse
|
||||
new ResponseFilter(_logger).FilterResponse
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -772,24 +750,23 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
return "emby/emby/" + path;
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
private readonly object _disposeLock = new object();
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
lock (_disposeLock)
|
||||
if (disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
_disposed = true;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
Stop();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -803,7 +780,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Logger.LogDebug("Websocket message received: {0}", result.MessageType);
|
||||
_logger.LogDebug("Websocket message received: {0}", result.MessageType);
|
||||
|
||||
IEnumerable<Task> GetTasks()
|
||||
{
|
||||
|
@ -815,10 +792,5 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
|
||||
return Task.WhenAll(GetTasks());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Globalization;
|
||||
using System.Text;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
|
@ -9,7 +10,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
{
|
||||
public class ResponseFilter
|
||||
{
|
||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public ResponseFilter(ILogger logger)
|
||||
|
@ -23,12 +24,12 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
/// <param name="req">The req.</param>
|
||||
/// <param name="res">The res.</param>
|
||||
/// <param name="dto">The dto.</param>
|
||||
public void FilterResponse(IRequest req, IResponse res, object dto)
|
||||
public void FilterResponse(IRequest req, HttpResponse res, object dto)
|
||||
{
|
||||
// Try to prevent compatibility view
|
||||
res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization");
|
||||
res.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
|
||||
res.AddHeader("Access-Control-Allow-Origin", "*");
|
||||
res.Headers.Add("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization");
|
||||
res.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
|
||||
res.Headers.Add("Access-Control-Allow-Origin", "*");
|
||||
|
||||
if (dto is Exception exception)
|
||||
{
|
||||
|
@ -39,7 +40,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
var error = exception.Message.Replace(Environment.NewLine, " ");
|
||||
error = RemoveControlCharacters(error);
|
||||
|
||||
res.AddHeader("X-Application-Error-Code", error);
|
||||
res.Headers.Add("X-Application-Error-Code", error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,12 +55,11 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
if (hasHeaders.Headers.TryGetValue(HeaderNames.ContentLength, out string contentLength)
|
||||
&& !string.IsNullOrEmpty(contentLength))
|
||||
{
|
||||
var length = long.Parse(contentLength, UsCulture);
|
||||
var length = long.Parse(contentLength, _usCulture);
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
res.OriginalResponse.ContentLength = length;
|
||||
res.SendChunked = false;
|
||||
res.ContentLength = length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,9 +72,12 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
/// <returns>System.String.</returns>
|
||||
public static string RemoveControlCharacters(string inString)
|
||||
{
|
||||
if (inString == null) return null;
|
||||
if (inString == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var newString = new StringBuilder();
|
||||
var newString = new StringBuilder(inString.Length);
|
||||
|
||||
foreach (var ch in inString)
|
||||
{
|
||||
|
@ -83,6 +86,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
newString.Append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
return newString.ToString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ using System.Linq;
|
|||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Controller.Session;
|
||||
|
@ -13,28 +12,23 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||
{
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private readonly IAuthorizationContext _authorizationContext;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly INetworkManager _networkManager;
|
||||
|
||||
public AuthService(IUserManager userManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config, ISessionManager sessionManager, INetworkManager networkManager)
|
||||
public AuthService(
|
||||
IAuthorizationContext authorizationContext,
|
||||
IServerConfigurationManager config,
|
||||
ISessionManager sessionManager,
|
||||
INetworkManager networkManager)
|
||||
{
|
||||
AuthorizationContext = authorizationContext;
|
||||
_authorizationContext = authorizationContext;
|
||||
_config = config;
|
||||
SessionManager = sessionManager;
|
||||
UserManager = userManager;
|
||||
NetworkManager = networkManager;
|
||||
_sessionManager = sessionManager;
|
||||
_networkManager = networkManager;
|
||||
}
|
||||
|
||||
public IUserManager UserManager { get; private set; }
|
||||
public IAuthorizationContext AuthorizationContext { get; private set; }
|
||||
public ISessionManager SessionManager { get; private set; }
|
||||
public INetworkManager NetworkManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Redirect the client to a specific URL if authentication failed.
|
||||
/// If this property is null, simply `401 Unauthorized` is returned.
|
||||
/// </summary>
|
||||
public string HtmlRedirect { get; set; }
|
||||
|
||||
public void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues)
|
||||
{
|
||||
ValidateUser(request, authAttribtues);
|
||||
|
@ -43,7 +37,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||
private void ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
|
||||
{
|
||||
// This code is executed before the service
|
||||
var auth = AuthorizationContext.GetAuthorizationInfo(request);
|
||||
var auth = _authorizationContext.GetAuthorizationInfo(request);
|
||||
|
||||
if (!IsExemptFromAuthenticationToken(authAttribtues, request))
|
||||
{
|
||||
|
@ -80,7 +74,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||
!string.IsNullOrEmpty(auth.Client) &&
|
||||
!string.IsNullOrEmpty(auth.Device))
|
||||
{
|
||||
SessionManager.LogSessionActivity(auth.Client,
|
||||
_sessionManager.LogSessionActivity(auth.Client,
|
||||
auth.Version,
|
||||
auth.DeviceId,
|
||||
auth.Device,
|
||||
|
@ -89,7 +83,9 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||
}
|
||||
}
|
||||
|
||||
private void ValidateUserAccess(User user, IRequest request,
|
||||
private void ValidateUserAccess(
|
||||
User user,
|
||||
IRequest request,
|
||||
IAuthenticationAttributes authAttribtues,
|
||||
AuthorizationInfo auth)
|
||||
{
|
||||
|
@ -101,7 +97,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||
};
|
||||
}
|
||||
|
||||
if (!user.Policy.EnableRemoteAccess && !NetworkManager.IsInLocalNetwork(request.RemoteIp))
|
||||
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp))
|
||||
{
|
||||
throw new SecurityException("User account has been disabled.")
|
||||
{
|
||||
|
@ -109,11 +105,11 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||
};
|
||||
}
|
||||
|
||||
if (!user.Policy.IsAdministrator &&
|
||||
!authAttribtues.EscapeParentalControl &&
|
||||
!user.IsParentalScheduleAllowed())
|
||||
if (!user.Policy.IsAdministrator
|
||||
&& !authAttribtues.EscapeParentalControl
|
||||
&& !user.IsParentalScheduleAllowed())
|
||||
{
|
||||
request.Response.AddHeader("X-Application-Error-Code", "ParentalControl");
|
||||
request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl");
|
||||
|
||||
throw new SecurityException("This user account is not allowed access at this time.")
|
||||
{
|
||||
|
@ -183,6 +179,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (user == null || !user.Policy.EnableContentDeletion)
|
||||
|
@ -193,6 +190,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (roles.Contains("download", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (user == null || !user.Policy.EnableContentDownloading)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
@ -555,7 +556,7 @@ namespace Emby.Server.Implementations.IO
|
|||
throw new ArgumentNullException(nameof(file2));
|
||||
}
|
||||
|
||||
var temp1 = Path.Combine(_tempPath, Guid.NewGuid().ToString("N"));
|
||||
var temp1 = Path.Combine(_tempPath, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture));
|
||||
|
||||
// Copying over will fail against hidden files
|
||||
SetHidden(file1, false);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
@ -89,7 +90,7 @@ namespace Emby.Server.Implementations.Images
|
|||
ImageType imageType,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var outputPathWithoutExtension = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N"));
|
||||
var outputPathWithoutExtension = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPathWithoutExtension));
|
||||
string outputPath = CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0);
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ using System.Text.RegularExpressions;
|
|||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
|
@ -17,12 +16,10 @@ namespace Emby.Server.Implementations.Library
|
|||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
private bool _ignoreDotPrefix;
|
||||
|
||||
/// <summary>
|
||||
/// Any folder named in this list will be ignored - can be added to at runtime for extensibility
|
||||
/// Any folder named in this list will be ignored
|
||||
/// </summary>
|
||||
public static readonly string[] IgnoreFolders =
|
||||
private static readonly string[] _ignoreFolders =
|
||||
{
|
||||
"metadata",
|
||||
"ps3_update",
|
||||
|
@ -43,25 +40,14 @@ namespace Emby.Server.Implementations.Library
|
|||
"$RECYCLE.BIN",
|
||||
"System Volume Information",
|
||||
".grab",
|
||||
|
||||
// macos
|
||||
".AppleDouble"
|
||||
|
||||
};
|
||||
|
||||
public CoreResolutionIgnoreRule(ILibraryManager libraryManager)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
|
||||
_ignoreDotPrefix = Environment.OSVersion.Platform != PlatformID.Win32NT;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shoulds the ignore.
|
||||
/// </summary>
|
||||
/// <param name="fileInfo">The file information.</param>
|
||||
/// <param name="parent">The parent.</param>
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||
/// <inheritdoc />
|
||||
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent)
|
||||
{
|
||||
// Don't ignore top level folders
|
||||
|
@ -73,46 +59,17 @@ namespace Emby.Server.Implementations.Library
|
|||
var filename = fileInfo.Name;
|
||||
var path = fileInfo.FullName;
|
||||
|
||||
// Handle mac .DS_Store
|
||||
// https://github.com/MediaBrowser/MediaBrowser/issues/427
|
||||
if (_ignoreDotPrefix)
|
||||
// Ignore hidden files on UNIX
|
||||
if (Environment.OSVersion.Platform != PlatformID.Win32NT
|
||||
&& filename[0] == '.')
|
||||
{
|
||||
if (filename.IndexOf('.') == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ignore hidden files and folders
|
||||
//if (fileInfo.IsHidden)
|
||||
//{
|
||||
// if (parent == null)
|
||||
// {
|
||||
// var parentFolderName = Path.GetFileName(_fileSystem.GetDirectoryName(path));
|
||||
|
||||
// if (string.Equals(parentFolderName, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// if (string.Equals(parentFolderName, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Sometimes these are marked hidden
|
||||
// if (_fileSystem.IsRootPath(path))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// return true;
|
||||
//}
|
||||
|
||||
if (fileInfo.IsDirectory)
|
||||
{
|
||||
// Ignore any folders in our list
|
||||
if (IgnoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
|
||||
if (_ignoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -120,8 +77,9 @@ namespace Emby.Server.Implementations.Library
|
|||
if (parent != null)
|
||||
{
|
||||
// Ignore trailer folders but allow it at the collection level
|
||||
if (string.Equals(filename, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase) &&
|
||||
!(parent is AggregateFolder) && !(parent is UserRootFolder))
|
||||
if (string.Equals(filename, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase)
|
||||
&& !(parent is AggregateFolder)
|
||||
&& !(parent is UserRootFolder))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -142,14 +100,15 @@ namespace Emby.Server.Implementations.Library
|
|||
if (parent != null)
|
||||
{
|
||||
// Don't resolve these into audio files
|
||||
if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) && _libraryManager.IsAudioFile(filename))
|
||||
if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename)
|
||||
&& _libraryManager.IsAudioFile(filename))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore samples
|
||||
Match m = Regex.Match(filename,@"\bsample\b",RegexOptions.IgnoreCase);
|
||||
Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase);
|
||||
|
||||
return m.Success;
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@ namespace Emby.Server.Implementations.Library
|
|||
public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser
|
||||
{
|
||||
private readonly ICryptoProvider _cryptographyProvider;
|
||||
public DefaultAuthenticationProvider(ICryptoProvider crypto)
|
||||
public DefaultAuthenticationProvider(ICryptoProvider cryptographyProvider)
|
||||
{
|
||||
_cryptographyProvider = crypto;
|
||||
_cryptographyProvider = cryptographyProvider;
|
||||
}
|
||||
|
||||
public string Name => "Default";
|
||||
|
@ -28,17 +28,17 @@ namespace Emby.Server.Implementations.Library
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// This is the verson that we need to use for local users. Because reasons.
|
||||
// This is the version that we need to use for local users. Because reasons.
|
||||
public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)
|
||||
{
|
||||
bool success = false;
|
||||
if (resolvedUser == null)
|
||||
{
|
||||
throw new Exception("Invalid username or password");
|
||||
throw new ArgumentNullException(nameof(resolvedUser));
|
||||
}
|
||||
|
||||
// As long as jellyfin supports passwordless users, we need this little block here to accomodate
|
||||
if (IsPasswordEmpty(resolvedUser, password))
|
||||
if (!HasPassword(resolvedUser) && string.IsNullOrEmpty(password))
|
||||
{
|
||||
return Task.FromResult(new ProviderAuthenticationResult
|
||||
{
|
||||
|
@ -50,37 +50,24 @@ namespace Emby.Server.Implementations.Library
|
|||
byte[] passwordbytes = Encoding.UTF8.GetBytes(password);
|
||||
|
||||
PasswordHash readyHash = new PasswordHash(resolvedUser.Password);
|
||||
byte[] calculatedHash;
|
||||
string calculatedHashString;
|
||||
if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) || _cryptographyProvider.DefaultHashMethod == readyHash.Id)
|
||||
if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)
|
||||
|| _cryptographyProvider.DefaultHashMethod == readyHash.Id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(readyHash.Salt))
|
||||
{
|
||||
calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes);
|
||||
calculatedHashString = BitConverter.ToString(calculatedHash).Replace("-", string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes);
|
||||
calculatedHashString = BitConverter.ToString(calculatedHash).Replace("-", string.Empty);
|
||||
}
|
||||
byte[] calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.Salt);
|
||||
|
||||
if (calculatedHashString == readyHash.Hash)
|
||||
if (calculatedHash.SequenceEqual(readyHash.Hash))
|
||||
{
|
||||
success = true;
|
||||
// throw new Exception("Invalid username or password");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception(string.Format($"Requested crypto method not available in provider: {readyHash.Id}"));
|
||||
throw new AuthenticationException($"Requested crypto method not available in provider: {readyHash.Id}");
|
||||
}
|
||||
|
||||
// var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
throw new Exception("Invalid username or password");
|
||||
throw new AuthenticationException("Invalid username or password");
|
||||
}
|
||||
|
||||
return Task.FromResult(new ProviderAuthenticationResult
|
||||
|
@ -98,29 +85,22 @@ namespace Emby.Server.Implementations.Library
|
|||
return;
|
||||
}
|
||||
|
||||
if (!user.Password.Contains("$"))
|
||||
if (user.Password.IndexOf('$') == -1)
|
||||
{
|
||||
string hash = user.Password;
|
||||
user.Password = string.Format("$SHA1${0}", hash);
|
||||
}
|
||||
|
||||
if (user.EasyPassword != null && !user.EasyPassword.Contains("$"))
|
||||
if (user.EasyPassword != null
|
||||
&& user.EasyPassword.IndexOf('$') == -1)
|
||||
{
|
||||
string hash = user.EasyPassword;
|
||||
user.EasyPassword = string.Format("$SHA1${0}", hash);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<bool> HasPassword(User user)
|
||||
{
|
||||
var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user));
|
||||
return Task.FromResult(hasConfiguredPassword);
|
||||
}
|
||||
|
||||
private bool IsPasswordEmpty(User user, string password)
|
||||
{
|
||||
return (string.IsNullOrEmpty(user.Password) && string.IsNullOrEmpty(password));
|
||||
}
|
||||
public bool HasPassword(User user)
|
||||
=> !string.IsNullOrEmpty(user.Password);
|
||||
|
||||
public Task ChangePassword(User user, string newPassword)
|
||||
{
|
||||
|
@ -129,30 +109,24 @@ namespace Emby.Server.Implementations.Library
|
|||
if (string.IsNullOrEmpty(user.Password))
|
||||
{
|
||||
PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider);
|
||||
newPasswordHash.SaltBytes = _cryptographyProvider.GenerateSalt();
|
||||
newPasswordHash.Salt = PasswordHash.ConvertToByteString(newPasswordHash.SaltBytes);
|
||||
newPasswordHash.Salt = _cryptographyProvider.GenerateSalt();
|
||||
newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod;
|
||||
newPasswordHash.Hash = GetHashedStringChangeAuth(newPassword, newPasswordHash);
|
||||
newPasswordHash.Hash = GetHashedChangeAuth(newPassword, newPasswordHash);
|
||||
user.Password = newPasswordHash.ToString();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
PasswordHash passwordHash = new PasswordHash(user.Password);
|
||||
if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt))
|
||||
if (passwordHash.Id == "SHA1"
|
||||
&& passwordHash.Salt.Length == 0)
|
||||
{
|
||||
passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt();
|
||||
passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes);
|
||||
passwordHash.Salt = _cryptographyProvider.GenerateSalt();
|
||||
passwordHash.Id = _cryptographyProvider.DefaultHashMethod;
|
||||
passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash);
|
||||
passwordHash.Hash = GetHashedChangeAuth(newPassword, passwordHash);
|
||||
}
|
||||
else if (newPassword != null)
|
||||
{
|
||||
passwordHash.Hash = GetHashedString(user, newPassword);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(passwordHash.Hash))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(passwordHash.Hash));
|
||||
passwordHash.Hash = GetHashed(user, newPassword);
|
||||
}
|
||||
|
||||
user.Password = passwordHash.ToString();
|
||||
|
@ -160,11 +134,6 @@ namespace Emby.Server.Implementations.Library
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public string GetPasswordHash(User user)
|
||||
{
|
||||
return user.Password;
|
||||
}
|
||||
|
||||
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
|
||||
{
|
||||
ConvertPasswordFormat(user);
|
||||
|
@ -190,13 +159,13 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
return string.IsNullOrEmpty(user.EasyPassword)
|
||||
? null
|
||||
: (new PasswordHash(user.EasyPassword)).Hash;
|
||||
: PasswordHash.ConvertToByteString(new PasswordHash(user.EasyPassword).Hash);
|
||||
}
|
||||
|
||||
public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash)
|
||||
internal byte[] GetHashedChangeAuth(string newPassword, PasswordHash passwordHash)
|
||||
{
|
||||
passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword);
|
||||
return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash));
|
||||
passwordHash.Hash = Encoding.UTF8.GetBytes(newPassword);
|
||||
return _cryptographyProvider.ComputeHash(passwordHash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -215,10 +184,10 @@ namespace Emby.Server.Implementations.Library
|
|||
passwordHash = new PasswordHash(user.Password);
|
||||
}
|
||||
|
||||
if (passwordHash.SaltBytes != null)
|
||||
if (passwordHash.Salt != null)
|
||||
{
|
||||
// the password is modern format with PBKDF and we should take advantage of that
|
||||
passwordHash.HashBytes = Encoding.UTF8.GetBytes(str);
|
||||
passwordHash.Hash = Encoding.UTF8.GetBytes(str);
|
||||
return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash));
|
||||
}
|
||||
else
|
||||
|
@ -227,5 +196,31 @@ namespace Emby.Server.Implementations.Library
|
|||
return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str)));
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] GetHashed(User user, string str)
|
||||
{
|
||||
PasswordHash passwordHash;
|
||||
if (string.IsNullOrEmpty(user.Password))
|
||||
{
|
||||
passwordHash = new PasswordHash(_cryptographyProvider);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConvertPasswordFormat(user);
|
||||
passwordHash = new PasswordHash(user.Password);
|
||||
}
|
||||
|
||||
if (passwordHash.Salt != null)
|
||||
{
|
||||
// the password is modern format with PBKDF and we should take advantage of that
|
||||
passwordHash.Hash = Encoding.UTF8.GetBytes(str);
|
||||
return _cryptographyProvider.ComputeHash(passwordHash);
|
||||
}
|
||||
else
|
||||
{
|
||||
// the password has no salt and should be called with the older method for safety
|
||||
return _cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,132 +1,125 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Users;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
public class DefaultPasswordResetProvider : IPasswordResetProvider
|
||||
{
|
||||
public string Name => "Default Password Reset Provider";
|
||||
|
||||
public bool IsEnabled => true;
|
||||
|
||||
private readonly string _passwordResetFileBase;
|
||||
private readonly string _passwordResetFileBaseDir;
|
||||
private readonly string _passwordResetFileBaseName = "passwordreset";
|
||||
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ICryptoProvider _crypto;
|
||||
|
||||
public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager, ICryptoProvider cryptoProvider)
|
||||
{
|
||||
_passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
|
||||
_passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName);
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_userManager = userManager;
|
||||
_crypto = cryptoProvider;
|
||||
}
|
||||
|
||||
public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
|
||||
{
|
||||
SerializablePasswordReset spr;
|
||||
HashSet<string> usersreset = new HashSet<string>();
|
||||
foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*"))
|
||||
{
|
||||
using (var str = File.OpenRead(resetfile))
|
||||
{
|
||||
spr = await _jsonSerializer.DeserializeFromStreamAsync<SerializablePasswordReset>(str).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (spr.ExpirationDate < DateTime.Now)
|
||||
{
|
||||
File.Delete(resetfile);
|
||||
}
|
||||
else if (spr.Pin.Replace("-", "").Equals(pin.Replace("-", ""), StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var resetUser = _userManager.GetUserByName(spr.UserName);
|
||||
if (resetUser == null)
|
||||
{
|
||||
throw new Exception($"User with a username of {spr.UserName} not found");
|
||||
}
|
||||
|
||||
await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
|
||||
usersreset.Add(resetUser.Name);
|
||||
File.Delete(resetfile);
|
||||
}
|
||||
}
|
||||
|
||||
if (usersreset.Count < 1)
|
||||
{
|
||||
throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}");
|
||||
}
|
||||
else
|
||||
{
|
||||
return new PinRedeemResult
|
||||
{
|
||||
Success = true,
|
||||
UsersReset = usersreset.ToArray()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ForgotPasswordResult> StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
|
||||
{
|
||||
string pin = string.Empty;
|
||||
using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create())
|
||||
{
|
||||
byte[] bytes = new byte[4];
|
||||
cryptoRandom.GetBytes(bytes);
|
||||
pin = BitConverter.ToString(bytes);
|
||||
}
|
||||
|
||||
DateTime expireTime = DateTime.Now.AddMinutes(30);
|
||||
string filePath = _passwordResetFileBase + user.InternalId + ".json";
|
||||
SerializablePasswordReset spr = new SerializablePasswordReset
|
||||
{
|
||||
ExpirationDate = expireTime,
|
||||
Pin = pin,
|
||||
PinFile = filePath,
|
||||
UserName = user.Name
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using (FileStream fileStream = File.OpenWrite(filePath))
|
||||
{
|
||||
_jsonSerializer.SerializeToStream(spr, fileStream);
|
||||
await fileStream.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception($"Error serializing or writing password reset for {user.Name} to location: {filePath}", e);
|
||||
}
|
||||
|
||||
return new ForgotPasswordResult
|
||||
{
|
||||
Action = ForgotPasswordAction.PinCode,
|
||||
PinExpirationDate = expireTime,
|
||||
PinFile = filePath
|
||||
};
|
||||
}
|
||||
|
||||
private class SerializablePasswordReset : PasswordPinCreationResult
|
||||
{
|
||||
public string Pin { get; set; }
|
||||
|
||||
public string UserName { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Users;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
public class DefaultPasswordResetProvider : IPasswordResetProvider
|
||||
{
|
||||
public string Name => "Default Password Reset Provider";
|
||||
|
||||
public bool IsEnabled => true;
|
||||
|
||||
private readonly string _passwordResetFileBase;
|
||||
private readonly string _passwordResetFileBaseDir;
|
||||
private readonly string _passwordResetFileBaseName = "passwordreset";
|
||||
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ICryptoProvider _crypto;
|
||||
|
||||
public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager, ICryptoProvider cryptoProvider)
|
||||
{
|
||||
_passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
|
||||
_passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName);
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_userManager = userManager;
|
||||
_crypto = cryptoProvider;
|
||||
}
|
||||
|
||||
public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
|
||||
{
|
||||
SerializablePasswordReset spr;
|
||||
HashSet<string> usersreset = new HashSet<string>();
|
||||
foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*"))
|
||||
{
|
||||
using (var str = File.OpenRead(resetfile))
|
||||
{
|
||||
spr = await _jsonSerializer.DeserializeFromStreamAsync<SerializablePasswordReset>(str).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (spr.ExpirationDate < DateTime.Now)
|
||||
{
|
||||
File.Delete(resetfile);
|
||||
}
|
||||
else if (spr.Pin.Replace("-", "").Equals(pin.Replace("-", ""), StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var resetUser = _userManager.GetUserByName(spr.UserName);
|
||||
if (resetUser == null)
|
||||
{
|
||||
throw new Exception($"User with a username of {spr.UserName} not found");
|
||||
}
|
||||
|
||||
await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
|
||||
usersreset.Add(resetUser.Name);
|
||||
File.Delete(resetfile);
|
||||
}
|
||||
}
|
||||
|
||||
if (usersreset.Count < 1)
|
||||
{
|
||||
throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}");
|
||||
}
|
||||
else
|
||||
{
|
||||
return new PinRedeemResult
|
||||
{
|
||||
Success = true,
|
||||
UsersReset = usersreset.ToArray()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ForgotPasswordResult> StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
|
||||
{
|
||||
string pin = string.Empty;
|
||||
using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create())
|
||||
{
|
||||
byte[] bytes = new byte[4];
|
||||
cryptoRandom.GetBytes(bytes);
|
||||
pin = BitConverter.ToString(bytes);
|
||||
}
|
||||
|
||||
DateTime expireTime = DateTime.Now.AddMinutes(30);
|
||||
string filePath = _passwordResetFileBase + user.InternalId + ".json";
|
||||
SerializablePasswordReset spr = new SerializablePasswordReset
|
||||
{
|
||||
ExpirationDate = expireTime,
|
||||
Pin = pin,
|
||||
PinFile = filePath,
|
||||
UserName = user.Name
|
||||
};
|
||||
|
||||
using (FileStream fileStream = File.OpenWrite(filePath))
|
||||
{
|
||||
_jsonSerializer.SerializeToStream(spr, fileStream);
|
||||
await fileStream.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return new ForgotPasswordResult
|
||||
{
|
||||
Action = ForgotPasswordAction.PinCode,
|
||||
PinExpirationDate = expireTime,
|
||||
PinFile = filePath
|
||||
};
|
||||
}
|
||||
|
||||
private class SerializablePasswordReset : PasswordPinCreationResult
|
||||
{
|
||||
public string Pin { get; set; }
|
||||
|
||||
public string UserName { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
@ -26,7 +27,7 @@ namespace Emby.Server.Implementations.Library
|
|||
EnableStreamSharing = false;
|
||||
_closeFn = closeFn;
|
||||
ConsumerCount = 1;
|
||||
UniqueId = Guid.NewGuid().ToString("N");
|
||||
UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public Task Close()
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
@ -16,12 +13,12 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
|
||||
{
|
||||
throw new SecurityException("User Account cannot login with this provider. The Normal provider for this user cannot be found");
|
||||
throw new AuthenticationException("User Account cannot login with this provider. The Normal provider for this user cannot be found");
|
||||
}
|
||||
|
||||
public Task<bool> HasPassword(User user)
|
||||
public bool HasPassword(User user)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Task ChangePassword(User user, string newPassword)
|
||||
|
@ -31,7 +28,7 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
|
||||
{
|
||||
// Nothing here
|
||||
// Nothing here
|
||||
}
|
||||
|
||||
public string GetPasswordHash(User user)
|
||||
|
|
|
@ -1187,12 +1187,12 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
if (libraryFolder != null && libraryFolder.HasImage(ImageType.Primary))
|
||||
{
|
||||
info.PrimaryImageItemId = libraryFolder.Id.ToString("N");
|
||||
info.PrimaryImageItemId = libraryFolder.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (libraryFolder != null)
|
||||
{
|
||||
info.ItemId = libraryFolder.Id.ToString("N");
|
||||
info.ItemId = libraryFolder.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
info.LibraryOptions = GetLibraryOptions(libraryFolder);
|
||||
|
||||
if (refreshQueue != null)
|
||||
|
@ -2135,12 +2135,12 @@ namespace Emby.Server.Implementations.Library
|
|||
string viewType,
|
||||
string sortName)
|
||||
{
|
||||
var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N");
|
||||
var idValues = "38_namedview_" + name + user.Id.ToString("N") + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
|
||||
var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N", CultureInfo.InvariantCulture);
|
||||
var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
|
||||
|
||||
var id = GetNewItemId(idValues, typeof(UserView));
|
||||
|
||||
var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N"));
|
||||
var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
|
||||
|
||||
var item = GetItemById(id) as UserView;
|
||||
|
||||
|
@ -2271,7 +2271,7 @@ namespace Emby.Server.Implementations.Library
|
|||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N");
|
||||
var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N", CultureInfo.InvariantCulture);
|
||||
var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
|
||||
if (!string.IsNullOrEmpty(uniqueId))
|
||||
{
|
||||
|
@ -2280,7 +2280,7 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
var id = GetNewItemId(idValues, typeof(UserView));
|
||||
|
||||
var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N"));
|
||||
var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
|
||||
|
||||
var item = GetItemById(id) as UserView;
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.Library
|
|||
var now = DateTime.UtcNow;
|
||||
|
||||
MediaInfo mediaInfo = null;
|
||||
var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N") + ".json");
|
||||
var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".json");
|
||||
|
||||
if (!string.IsNullOrEmpty(cacheKey))
|
||||
{
|
||||
|
|
|
@ -269,7 +269,7 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
private static void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource)
|
||||
{
|
||||
var prefix = provider.GetType().FullName.GetMD5().ToString("N") + LiveStreamIdDelimeter;
|
||||
var prefix = provider.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + LiveStreamIdDelimeter;
|
||||
|
||||
if (!string.IsNullOrEmpty(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
@ -626,7 +626,7 @@ namespace Emby.Server.Implementations.Library
|
|||
var now = DateTime.UtcNow;
|
||||
|
||||
MediaInfo mediaInfo = null;
|
||||
var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N") + ".json");
|
||||
var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".json");
|
||||
|
||||
if (!string.IsNullOrEmpty(cacheKey))
|
||||
{
|
||||
|
@ -854,7 +854,7 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
var keys = key.Split(new[] { LiveStreamIdDelimeter }, 2);
|
||||
|
||||
var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), keys[0], StringComparison.OrdinalIgnoreCase));
|
||||
var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), keys[0], StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var splitIndex = key.IndexOf(LiveStreamIdDelimeter);
|
||||
var keyId = key.Substring(splitIndex + 1);
|
||||
|
|
|
@ -12,7 +12,6 @@ using MediaBrowser.Controller.Library;
|
|||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
|
@ -28,7 +27,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
/// <value>The priority.</value>
|
||||
public override ResolverPriority Priority => ResolverPriority.Third;
|
||||
|
||||
public MultiItemResolverResult ResolveMultiple(Folder parent,
|
||||
public MultiItemResolverResult ResolveMultiple(
|
||||
Folder parent,
|
||||
List<FileSystemMetadata> files,
|
||||
string collectionType,
|
||||
IDirectoryService directoryService)
|
||||
|
@ -46,7 +46,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
return result;
|
||||
}
|
||||
|
||||
private MultiItemResolverResult ResolveMultipleInternal(Folder parent,
|
||||
private MultiItemResolverResult ResolveMultipleInternal(
|
||||
Folder parent,
|
||||
List<FileSystemMetadata> files,
|
||||
string collectionType,
|
||||
IDirectoryService directoryService)
|
||||
|
@ -91,7 +92,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
return null;
|
||||
}
|
||||
|
||||
private MultiItemResolverResult ResolveVideos<T>(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, bool suppportMultiEditions, string collectionType, bool parseName)
|
||||
private MultiItemResolverResult ResolveVideos<T>(
|
||||
Folder parent,
|
||||
IEnumerable<FileSystemMetadata> fileSystemEntries,
|
||||
IDirectoryService directoryService,
|
||||
bool suppportMultiEditions,
|
||||
string collectionType,
|
||||
bool parseName)
|
||||
where T : Video, new()
|
||||
{
|
||||
var files = new List<FileSystemMetadata>();
|
||||
|
@ -104,8 +111,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
// This is a hack but currently no better way to resolve a sometimes ambiguous situation
|
||||
if (string.IsNullOrEmpty(collectionType))
|
||||
{
|
||||
if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -115,11 +122,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
{
|
||||
leftOver.Add(child);
|
||||
}
|
||||
else if (IsIgnored(child.Name))
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
else if (!IsIgnored(child.Name))
|
||||
{
|
||||
files.Add(child);
|
||||
}
|
||||
|
@ -168,7 +171,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||
private static bool IsIgnored(string filename)
|
||||
{
|
||||
// Ignore samples
|
||||
Match m = Regex.Match(filename,@"\bsample\b",RegexOptions.IgnoreCase);
|
||||
Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase);
|
||||
|
||||
return m.Success;
|
||||
}
|
||||
|
|
|
@ -152,7 +152,7 @@ namespace Emby.Server.Implementations.Library
|
|||
/// <returns>System.String.</returns>
|
||||
private static string GetCacheKey(long internalUserId, Guid itemId)
|
||||
{
|
||||
return internalUserId.ToString(CultureInfo.InvariantCulture) + "-" + itemId.ToString("N");
|
||||
return internalUserId.ToString(CultureInfo.InvariantCulture) + "-" + itemId.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public UserItemData GetUserData(User user, BaseItem item)
|
||||
|
|
|
@ -266,6 +266,7 @@ namespace Emby.Server.Implementations.Library
|
|||
builder.Append(c);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
|
@ -286,17 +287,17 @@ namespace Emby.Server.Implementations.Library
|
|||
if (user != null)
|
||||
{
|
||||
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false);
|
||||
authenticationProvider = authResult.Item1;
|
||||
updatedUsername = authResult.Item2;
|
||||
success = authResult.Item3;
|
||||
authenticationProvider = authResult.authenticationProvider;
|
||||
updatedUsername = authResult.username;
|
||||
success = authResult.success;
|
||||
}
|
||||
else
|
||||
{
|
||||
// user is null
|
||||
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false);
|
||||
authenticationProvider = authResult.Item1;
|
||||
updatedUsername = authResult.Item2;
|
||||
success = authResult.Item3;
|
||||
authenticationProvider = authResult.authenticationProvider;
|
||||
updatedUsername = authResult.username;
|
||||
success = authResult.success;
|
||||
|
||||
if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider))
|
||||
{
|
||||
|
@ -331,22 +332,25 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
if (user == null)
|
||||
{
|
||||
throw new SecurityException("Invalid username or password entered.");
|
||||
throw new AuthenticationException("Invalid username or password entered.");
|
||||
}
|
||||
|
||||
if (user.Policy.IsDisabled)
|
||||
{
|
||||
throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name));
|
||||
throw new AuthenticationException(string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"The {0} account is currently disabled. Please consult with your administrator.",
|
||||
user.Name));
|
||||
}
|
||||
|
||||
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
|
||||
{
|
||||
throw new SecurityException("Forbidden.");
|
||||
throw new AuthenticationException("Forbidden.");
|
||||
}
|
||||
|
||||
if (!user.IsParentalScheduleAllowed())
|
||||
{
|
||||
throw new SecurityException("User is not allowed access at this time.");
|
||||
throw new AuthenticationException("User is not allowed access at this time.");
|
||||
}
|
||||
|
||||
// Update LastActivityDate and LastLoginDate, then save
|
||||
|
@ -357,6 +361,7 @@ namespace Emby.Server.Implementations.Library
|
|||
user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow;
|
||||
UpdateUser(user);
|
||||
}
|
||||
|
||||
UpdateInvalidLoginAttemptCount(user, 0);
|
||||
}
|
||||
else
|
||||
|
@ -429,7 +434,7 @@ namespace Emby.Server.Implementations.Library
|
|||
return providers;
|
||||
}
|
||||
|
||||
private async Task<Tuple<string, bool>> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser)
|
||||
private async Task<(string username, bool success)> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -444,23 +449,23 @@ namespace Emby.Server.Implementations.Library
|
|||
authenticationResult = await provider.Authenticate(username, password).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if(authenticationResult.Username != username)
|
||||
if (authenticationResult.Username != username)
|
||||
{
|
||||
_logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username);
|
||||
username = authenticationResult.Username;
|
||||
}
|
||||
|
||||
return new Tuple<string, bool>(username, true);
|
||||
return (username, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (AuthenticationException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name);
|
||||
_logger.LogError(ex, "Error authenticating with provider {Provider}", provider.Name);
|
||||
|
||||
return new Tuple<string, bool>(username, false);
|
||||
return (username, false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Tuple<IAuthenticationProvider, string, bool>> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint)
|
||||
private async Task<(IAuthenticationProvider authenticationProvider, string username, bool success)> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint)
|
||||
{
|
||||
string updatedUsername = null;
|
||||
bool success = false;
|
||||
|
@ -475,15 +480,15 @@ namespace Emby.Server.Implementations.Library
|
|||
if (password == null)
|
||||
{
|
||||
// legacy
|
||||
success = string.Equals(GetAuthenticationProvider(user).GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
|
||||
success = string.Equals(user.Password, hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var provider in GetAuthenticationProviders(user))
|
||||
{
|
||||
var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
|
||||
updatedUsername = providerAuthResult.Item1;
|
||||
success = providerAuthResult.Item2;
|
||||
updatedUsername = providerAuthResult.username;
|
||||
success = providerAuthResult.success;
|
||||
|
||||
if (success)
|
||||
{
|
||||
|
@ -510,7 +515,7 @@ namespace Emby.Server.Implementations.Library
|
|||
}
|
||||
}
|
||||
|
||||
return new Tuple<IAuthenticationProvider, string, bool>(authenticationProvider, username, success);
|
||||
return (authenticationProvider, username, success);
|
||||
}
|
||||
|
||||
private void UpdateInvalidLoginAttemptCount(User user, int newValue)
|
||||
|
@ -593,7 +598,7 @@ namespace Emby.Server.Implementations.Library
|
|||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result;
|
||||
bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user);
|
||||
bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user));
|
||||
|
||||
bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ?
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
|
@ -117,7 +118,7 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
if (!query.IncludeHidden)
|
||||
{
|
||||
list = list.Where(i => !user.Configuration.MyMediaExcludes.Contains(i.Id.ToString("N"))).ToList();
|
||||
list = list.Where(i => !user.Configuration.MyMediaExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))).ToList();
|
||||
}
|
||||
|
||||
var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList();
|
||||
|
@ -127,7 +128,7 @@ namespace Emby.Server.Implementations.Library
|
|||
return list
|
||||
.OrderBy(i =>
|
||||
{
|
||||
var index = orders.IndexOf(i.Id.ToString("N"));
|
||||
var index = orders.IndexOf(i.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
|
@ -136,7 +137,7 @@ namespace Emby.Server.Implementations.Library
|
|||
{
|
||||
if (!view.DisplayParentId.Equals(Guid.Empty))
|
||||
{
|
||||
index = orders.IndexOf(view.DisplayParentId.ToString("N"));
|
||||
index = orders.IndexOf(view.DisplayParentId.ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -269,7 +270,7 @@ namespace Emby.Server.Implementations.Library
|
|||
{
|
||||
parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
||||
.Where(i => i is Folder)
|
||||
.Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N")))
|
||||
.Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||
continue;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N"), item.Name, item.GetType().Name);
|
||||
_logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name);
|
||||
|
||||
_libraryManager.DeleteItem(item, new DeleteOptions
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
|
@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||
|
||||
foreach (var item in deadEntities)
|
||||
{
|
||||
_logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N"), item.Name, item.GetType().Name);
|
||||
_logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name);
|
||||
|
||||
_libraryManager.DeleteItem(item, new DeleteOptions
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
@ -76,7 +77,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||
|
||||
foreach (var item in deadEntities)
|
||||
{
|
||||
_logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N"), item.Name, item.GetType().Name);
|
||||
_logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name);
|
||||
|
||||
_libraryManager.DeleteItem(item, new DeleteOptions
|
||||
{
|
||||
|
|
|
@ -681,7 +681,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
}
|
||||
}
|
||||
|
||||
timer.Id = Guid.NewGuid().ToString("N");
|
||||
timer.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
LiveTvProgram programInfo = null;
|
||||
|
||||
|
@ -713,7 +713,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
public async Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
info.Id = Guid.NewGuid().ToString("N");
|
||||
info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
// populate info.seriesID
|
||||
var program = GetProgramInfoFromCache(info.ProgramId);
|
||||
|
@ -1059,7 +1059,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
var json = _jsonSerializer.SerializeToString(mediaSource);
|
||||
mediaSource = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
|
||||
|
||||
mediaSource.Id = Guid.NewGuid().ToString("N") + "_" + mediaSource.Id;
|
||||
mediaSource.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture) + "_" + mediaSource.Id;
|
||||
|
||||
//if (mediaSource.DateLiveStreamOpened.HasValue && enableStreamSharing)
|
||||
//{
|
||||
|
@ -2529,7 +2529,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
var timer = new TimerInfo
|
||||
{
|
||||
ChannelId = channelId,
|
||||
Id = (seriesTimer.Id + parent.ExternalId).GetMD5().ToString("N"),
|
||||
Id = (seriesTimer.Id + parent.ExternalId).GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
||||
StartDate = parent.StartDate,
|
||||
EndDate = parent.EndDate.Value,
|
||||
ProgramId = parent.ExternalId,
|
||||
|
|
|
@ -211,7 +211,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
HasImage = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source),
|
||||
OfficialRating = program.Rating != null && !string.IsNullOrEmpty(program.Rating.Value) ? program.Rating.Value : null,
|
||||
CommunityRating = program.StarRating,
|
||||
SeriesId = program.Episode == null ? null : program.Title.GetMD5().ToString("N")
|
||||
SeriesId = program.Episode == null ? null : program.Title.GetMD5().ToString("N", CultureInfo.InvariantCulture)
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(program.ProgramId))
|
||||
|
@ -227,7 +227,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
uniqueString = "-" + programInfo.EpisodeNumber.Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
programInfo.ShowId = uniqueString.GetMD5().ToString("N");
|
||||
programInfo.ShowId = uniqueString.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
// If we don't have valid episode info, assume it's a unique program, otherwise recordings might be skipped
|
||||
if (programInfo.IsSeries
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -52,7 +53,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
ExternalId = info.Id,
|
||||
ChannelId = GetInternalChannelId(service.Name, info.ChannelId),
|
||||
Status = info.Status,
|
||||
SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null : GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N"),
|
||||
SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null : GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N", CultureInfo.InvariantCulture),
|
||||
PrePaddingSeconds = info.PrePaddingSeconds,
|
||||
PostPaddingSeconds = info.PostPaddingSeconds,
|
||||
IsPostPaddingRequired = info.IsPostPaddingRequired,
|
||||
|
@ -69,7 +70,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
if (!string.IsNullOrEmpty(info.ProgramId))
|
||||
{
|
||||
dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N");
|
||||
dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (program != null)
|
||||
|
@ -107,7 +108,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
{
|
||||
var dto = new SeriesTimerInfoDto
|
||||
{
|
||||
Id = GetInternalSeriesTimerId(info.Id).ToString("N"),
|
||||
Id = GetInternalSeriesTimerId(info.Id).ToString("N", CultureInfo.InvariantCulture),
|
||||
Overview = info.Overview,
|
||||
EndDate = info.EndDate,
|
||||
Name = info.Name,
|
||||
|
@ -139,7 +140,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
if (!string.IsNullOrEmpty(info.ProgramId))
|
||||
{
|
||||
dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N");
|
||||
dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days.ToArray());
|
||||
|
@ -169,7 +170,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
try
|
||||
{
|
||||
dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image);
|
||||
dto.ParentThumbItemId = librarySeries.Id.ToString("N");
|
||||
dto.ParentThumbItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -185,7 +186,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
{
|
||||
_imageProcessor.GetImageCacheTag(librarySeries, image)
|
||||
};
|
||||
dto.ParentBackdropItemId = librarySeries.Id.ToString("N");
|
||||
dto.ParentBackdropItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -213,7 +214,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
try
|
||||
{
|
||||
dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image);
|
||||
dto.ParentPrimaryImageItemId = program.Id.ToString("N");
|
||||
dto.ParentPrimaryImageItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -232,7 +233,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
{
|
||||
_imageProcessor.GetImageCacheTag(program, image)
|
||||
};
|
||||
dto.ParentBackdropItemId = program.Id.ToString("N");
|
||||
dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -263,7 +264,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
try
|
||||
{
|
||||
dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image);
|
||||
dto.ParentThumbItemId = librarySeries.Id.ToString("N");
|
||||
dto.ParentThumbItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -279,7 +280,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
{
|
||||
_imageProcessor.GetImageCacheTag(librarySeries, image)
|
||||
};
|
||||
dto.ParentBackdropItemId = librarySeries.Id.ToString("N");
|
||||
dto.ParentBackdropItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -320,7 +321,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
try
|
||||
{
|
||||
dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image);
|
||||
dto.ParentPrimaryImageItemId = program.Id.ToString("N");
|
||||
dto.ParentPrimaryImageItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -339,7 +340,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
{
|
||||
_imageProcessor.GetImageCacheTag(program, image)
|
||||
};
|
||||
dto.ParentBackdropItemId = program.Id.ToString("N");
|
||||
dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -407,7 +408,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
{
|
||||
var name = ServiceName + externalId + InternalVersionNumber;
|
||||
|
||||
return name.ToLowerInvariant().GetMD5().ToString("N");
|
||||
return name.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public Guid GetInternalSeriesTimerId(string externalId)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -258,7 +259,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
}
|
||||
info.RequiresClosing = true;
|
||||
|
||||
var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
|
||||
var idPrefix = service.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_";
|
||||
|
||||
info.LiveStreamId = idPrefix + info.Id;
|
||||
|
||||
|
@ -820,7 +821,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
if (!string.IsNullOrWhiteSpace(query.SeriesTimerId))
|
||||
{
|
||||
var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery { }, cancellationToken).ConfigureAwait(false);
|
||||
var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.Id).ToString("N"), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase));
|
||||
var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.Id).ToString("N", CultureInfo.InvariantCulture), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase));
|
||||
if (seriesTimer != null)
|
||||
{
|
||||
internalQuery.ExternalSeriesId = seriesTimer.SeriesId;
|
||||
|
@ -997,7 +998,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
if (!string.IsNullOrEmpty(timer.SeriesTimerId))
|
||||
{
|
||||
program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(timer.SeriesTimerId)
|
||||
.ToString("N");
|
||||
.ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
foundSeriesTimer = true;
|
||||
}
|
||||
|
@ -1018,7 +1019,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
if (seriesTimer != null)
|
||||
{
|
||||
program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(seriesTimer.Id)
|
||||
.ToString("N");
|
||||
.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1472,7 +1473,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId)
|
||||
? null
|
||||
: _tvDtoService.GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N");
|
||||
: _tvDtoService.GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
dto.TimerId = string.IsNullOrEmpty(info.Id)
|
||||
? null
|
||||
|
@ -2027,7 +2028,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
info.StartDate = program.StartDate;
|
||||
info.Name = program.Name;
|
||||
info.Overview = program.Overview;
|
||||
info.ProgramId = programDto.Id.ToString("N");
|
||||
info.ProgramId = programDto.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
info.ExternalProgramId = program.ExternalId;
|
||||
|
||||
if (program.EndDate.HasValue)
|
||||
|
@ -2088,7 +2089,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
if (service is ISupportsNewTimerIds supportsNewTimerIds)
|
||||
{
|
||||
newTimerId = await supportsNewTimerIds.CreateSeriesTimer(info, cancellationToken).ConfigureAwait(false);
|
||||
newTimerId = _tvDtoService.GetInternalSeriesTimerId(newTimerId).ToString("N");
|
||||
newTimerId = _tvDtoService.GetInternalSeriesTimerId(newTimerId).ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2192,7 +2193,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
info.EnabledUsers = _userManager.Users
|
||||
.Where(IsLiveTvEnabled)
|
||||
.Select(i => i.Id.ToString("N"))
|
||||
.Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
|
||||
.ToArray();
|
||||
|
||||
return info;
|
||||
|
@ -2219,7 +2220,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
{
|
||||
var parts = id.Split(new[] { '_' }, 2);
|
||||
|
||||
var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase));
|
||||
var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), parts[0], StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (service == null)
|
||||
{
|
||||
|
@ -2269,7 +2270,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
|
||||
{
|
||||
info.Id = Guid.NewGuid().ToString("N");
|
||||
info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
list.Add(info);
|
||||
config.TunerHosts = list.ToArray();
|
||||
}
|
||||
|
@ -2312,7 +2313,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
|
||||
{
|
||||
info.Id = Guid.NewGuid().ToString("N");
|
||||
info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
list.Add(info);
|
||||
config.ListingProviders = list.ToArray();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -101,7 +102,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
{
|
||||
var openKeys = new List<string>();
|
||||
openKeys.Add(item.GetType().Name);
|
||||
openKeys.Add(item.Id.ToString("N"));
|
||||
openKeys.Add(item.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
openKeys.Add(source.Id ?? string.Empty);
|
||||
source.OpenToken = string.Join(StreamIdDelimeterString, openKeys.ToArray());
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
@ -11,7 +13,6 @@ using MediaBrowser.Controller;
|
|||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
@ -20,7 +21,6 @@ using MediaBrowser.Model.LiveTv;
|
|||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
|
@ -259,7 +259,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
using (var manager = new HdHomerunManager(_socketFactory, Logger))
|
||||
{
|
||||
// Legacy HdHomeruns are IPv4 only
|
||||
var ipInfo = _networkManager.ParseIpAddress(uri.Host);
|
||||
var ipInfo = IPAddress.Parse(uri.Host);
|
||||
|
||||
for (int i = 0; i < model.TunerCount; ++i)
|
||||
{
|
||||
|
@ -461,7 +461,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
{
|
||||
id = "native";
|
||||
}
|
||||
id += "_" + channelId.GetMD5().ToString("N") + "_" + url.GetMD5().ToString("N");
|
||||
id += "_" + channelId.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_" + url.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
var mediaSource = new MediaSourceInfo
|
||||
{
|
||||
|
@ -675,13 +675,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
// Need a way to set the Receive timeout on the socket otherwise this might never timeout?
|
||||
try
|
||||
{
|
||||
await udpClient.SendToAsync(discBytes, 0, discBytes.Length, new IpEndPointInfo(new IpAddressInfo("255.255.255.255", IpAddressFamily.InterNetwork), 65001), cancellationToken);
|
||||
await udpClient.SendToAsync(discBytes, 0, discBytes.Length, new IPEndPoint(IPAddress.Parse("255.255.255.255"), 65001), cancellationToken);
|
||||
var receiveBuffer = new byte[8192];
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var response = await udpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
|
||||
var deviceIp = response.RemoteEndPoint.IpAddress.Address;
|
||||
var deviceIp = response.RemoteEndPoint.Address.ToString();
|
||||
|
||||
// check to make sure we have enough bytes received to be a valid message and make sure the 2nd byte is the discover reply byte
|
||||
if (response.ReceivedBytes > 13 && response.Buffer[1] == 3)
|
||||
|
|
|
@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
private uint? _lockkey = null;
|
||||
private int _activeTuner = -1;
|
||||
private readonly ISocketFactory _socketFactory;
|
||||
private IpAddressInfo _remoteIp;
|
||||
private IPAddress _remoteIp;
|
||||
|
||||
private ILogger _logger;
|
||||
private ISocket _currentTcpSocket;
|
||||
|
@ -114,7 +114,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<bool> CheckTunerAvailability(IpAddressInfo remoteIp, int tuner, CancellationToken cancellationToken)
|
||||
public async Task<bool> CheckTunerAvailability(IPAddress remoteIp, int tuner, CancellationToken cancellationToken)
|
||||
{
|
||||
using (var socket = _socketFactory.CreateTcpSocket(remoteIp, HdHomeRunPort))
|
||||
{
|
||||
|
@ -122,9 +122,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
}
|
||||
}
|
||||
|
||||
private static async Task<bool> CheckTunerAvailability(ISocket socket, IpAddressInfo remoteIp, int tuner, CancellationToken cancellationToken)
|
||||
private static async Task<bool> CheckTunerAvailability(ISocket socket, IPAddress remoteIp, int tuner, CancellationToken cancellationToken)
|
||||
{
|
||||
var ipEndPoint = new IpEndPointInfo(remoteIp, HdHomeRunPort);
|
||||
var ipEndPoint = new IPEndPoint(remoteIp, HdHomeRunPort);
|
||||
|
||||
var lockkeyMsg = CreateGetMessage(tuner, "lockkey");
|
||||
await socket.SendToAsync(lockkeyMsg, 0, lockkeyMsg.Length, ipEndPoint, cancellationToken);
|
||||
|
@ -137,7 +137,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
return string.Equals(returnVal, "none", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public async Task StartStreaming(IpAddressInfo remoteIp, IPAddress localIp, int localPort, IHdHomerunChannelCommands commands, int numTuners, CancellationToken cancellationToken)
|
||||
public async Task StartStreaming(IPAddress remoteIp, IPAddress localIp, int localPort, IHdHomerunChannelCommands commands, int numTuners, CancellationToken cancellationToken)
|
||||
{
|
||||
_remoteIp = remoteIp;
|
||||
|
||||
|
@ -154,7 +154,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
|
||||
var lockKeyValue = _lockkey.Value;
|
||||
|
||||
var ipEndPoint = new IpEndPointInfo(_remoteIp, HdHomeRunPort);
|
||||
var ipEndPoint = new IPEndPoint(_remoteIp, HdHomeRunPort);
|
||||
|
||||
for (int i = 0; i < numTuners; ++i)
|
||||
{
|
||||
|
@ -217,7 +217,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
foreach (Tuple<string, string> command in commandList)
|
||||
{
|
||||
var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey);
|
||||
await tcpClient.SendToAsync(channelMsg, 0, channelMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), cancellationToken).ConfigureAwait(false);
|
||||
await tcpClient.SendToAsync(channelMsg, 0, channelMsg.Length, new IPEndPoint(_remoteIp, HdHomeRunPort), cancellationToken).ConfigureAwait(false);
|
||||
var response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
|
||||
// parse response to make sure it worked
|
||||
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out string returnVal))
|
||||
|
@ -242,7 +242,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
{
|
||||
_logger.LogInformation("HdHomerunManager.ReleaseLockkey {0}", lockKeyValue);
|
||||
|
||||
var ipEndPoint = new IpEndPointInfo(_remoteIp, HdHomeRunPort);
|
||||
var ipEndPoint = new IPEndPoint(_remoteIp, HdHomeRunPort);
|
||||
|
||||
var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue);
|
||||
await tcpClient.SendToAsync(releaseTarget, 0, releaseTarget.Length, ipEndPoint, CancellationToken.None).ConfigureAwait(false);
|
||||
|
|
|
@ -25,7 +25,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
private readonly int _numTuners;
|
||||
private readonly INetworkManager _networkManager;
|
||||
|
||||
public HdHomerunUdpStream(MediaSourceInfo mediaSource, TunerHostInfo tunerHostInfo, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, MediaBrowser.Model.Net.ISocketFactory socketFactory, INetworkManager networkManager)
|
||||
public HdHomerunUdpStream(
|
||||
MediaSourceInfo mediaSource,
|
||||
TunerHostInfo tunerHostInfo,
|
||||
string originalStreamId,
|
||||
IHdHomerunChannelCommands channelCommands,
|
||||
int numTuners,
|
||||
IFileSystem fileSystem,
|
||||
IHttpClient httpClient,
|
||||
ILogger logger,
|
||||
IServerApplicationPaths appPaths,
|
||||
IServerApplicationHost appHost,
|
||||
MediaBrowser.Model.Net.ISocketFactory socketFactory,
|
||||
INetworkManager networkManager)
|
||||
: base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths)
|
||||
{
|
||||
_appHost = appHost;
|
||||
|
@ -58,7 +70,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
Logger.LogInformation("Opening HDHR UDP Live stream from {host}", uri.Host);
|
||||
|
||||
var remoteAddress = IPAddress.Parse(uri.Host);
|
||||
var embyRemoteAddress = _networkManager.ParseIpAddress(uri.Host);
|
||||
IPAddress localAddress = null;
|
||||
using (var tcpSocket = CreateSocket(remoteAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
|
||||
{
|
||||
|
@ -81,7 +92,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
try
|
||||
{
|
||||
// send url to start streaming
|
||||
await hdHomerunManager.StartStreaming(embyRemoteAddress, localAddress, localPort, _channelCommands, _numTuners, openCancellationToken).ConfigureAwait(false);
|
||||
await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelCommands, _numTuners, openCancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
@ -42,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
MediaSource = mediaSource;
|
||||
Logger = logger;
|
||||
EnableStreamSharing = true;
|
||||
UniqueId = Guid.NewGuid().ToString("N");
|
||||
UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
if (tuner != null)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
@ -43,7 +44,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
|
||||
private string GetFullChannelIdPrefix(TunerHostInfo info)
|
||||
{
|
||||
return ChannelIdPrefix + info.Url.GetMD5().ToString("N");
|
||||
return ChannelIdPrefix + info.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
|
||||
|
@ -61,7 +62,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
Name = Name,
|
||||
SourceType = Type,
|
||||
Status = LiveTvTunerStatus.Available,
|
||||
Id = i.Url.GetMD5().ToString("N"),
|
||||
Id = i.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
||||
Url = i.Url
|
||||
})
|
||||
.ToList();
|
||||
|
@ -173,7 +174,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
|
||||
ReadAtNativeFramerate = false,
|
||||
|
||||
Id = channel.Path.GetMD5().ToString("N"),
|
||||
Id = channel.Path.GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
||||
IsInfiniteStream = true,
|
||||
IsRemote = isRemote,
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
UserAgent = _appHost.ApplicationUserAgent
|
||||
});
|
||||
}
|
||||
|
||||
return Task.FromResult((Stream)File.OpenRead(url));
|
||||
}
|
||||
|
||||
|
@ -92,11 +93,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
var channel = GetChannelnfo(extInf, tunerHostId, line);
|
||||
if (string.IsNullOrWhiteSpace(channel.Id))
|
||||
{
|
||||
channel.Id = channelIdPrefix + line.GetMD5().ToString("N");
|
||||
channel.Id = channelIdPrefix + line.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
channel.Id = channelIdPrefix + channel.Id.GetMD5().ToString("N");
|
||||
channel.Id = channelIdPrefix + channel.Id.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
channel.Path = line;
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
"HeaderAlbumArtists": "Albumkunstnere",
|
||||
"HeaderCameraUploads": "Kamera Uploads",
|
||||
"HeaderContinueWatching": "Fortsæt Afspilning",
|
||||
"HeaderFavoriteAlbums": "Favoritalbum",
|
||||
"HeaderFavoriteAlbums": "Favoritalbummer",
|
||||
"HeaderFavoriteArtists": "Favoritkunstnere",
|
||||
"HeaderFavoriteEpisodes": "Favorit-afsnit",
|
||||
"HeaderFavoriteShows": "Favorit-serier",
|
||||
"HeaderFavoriteSongs": "Favorit-sange",
|
||||
"HeaderFavoriteEpisodes": "Favoritepisoder",
|
||||
"HeaderFavoriteShows": "Favoritserier",
|
||||
"HeaderFavoriteSongs": "Favoritsange",
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderNextUp": "Næste",
|
||||
"HeaderRecordingGroups": "Optagelsesgrupper",
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
"HeaderCameraUploads": "Kamera feltöltések",
|
||||
"HeaderContinueWatching": "Folyamatban lévő filmek",
|
||||
"HeaderFavoriteAlbums": "Kedvenc Albumok",
|
||||
"HeaderFavoriteArtists": "Kedvenc Művészek",
|
||||
"HeaderFavoriteArtists": "Kedvenc Előadók",
|
||||
"HeaderFavoriteEpisodes": "Kedvenc Epizódok",
|
||||
"HeaderFavoriteShows": "Kedvenc Műsorok",
|
||||
"HeaderFavoriteShows": "Kedvenc Sorozatok",
|
||||
"HeaderFavoriteSongs": "Kedvenc Dalok",
|
||||
"HeaderLiveTV": "Élő TV",
|
||||
"HeaderNextUp": "Következik",
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
"HeaderAlbumArtists": "Wykonawcy albumów",
|
||||
"HeaderCameraUploads": "Przekazane obrazy",
|
||||
"HeaderContinueWatching": "Kontynuuj odtwarzanie",
|
||||
"HeaderFavoriteAlbums": "Albumy ulubione",
|
||||
"HeaderFavoriteArtists": "Wykonawcy ulubieni",
|
||||
"HeaderFavoriteEpisodes": "Odcinki ulubione",
|
||||
"HeaderFavoriteShows": "Seriale ulubione",
|
||||
"HeaderFavoriteSongs": "Utwory ulubione",
|
||||
"HeaderFavoriteAlbums": "Ulubione albumy",
|
||||
"HeaderFavoriteArtists": "Ulubieni wykonawcy",
|
||||
"HeaderFavoriteEpisodes": "Ulubione odcinki",
|
||||
"HeaderFavoriteShows": "Ulubione seriale",
|
||||
"HeaderFavoriteSongs": "Ulubione utwory",
|
||||
"HeaderLiveTV": "Telewizja",
|
||||
"HeaderNextUp": "Do obejrzenia",
|
||||
"HeaderRecordingGroups": "Grupy nagrań",
|
||||
|
@ -41,26 +41,26 @@
|
|||
"Movies": "Filmy",
|
||||
"Music": "Muzyka",
|
||||
"MusicVideos": "Teledyski",
|
||||
"NameInstallFailed": "Instalacja {0} nieudana.",
|
||||
"NameInstallFailed": "Instalacja {0} nieudana",
|
||||
"NameSeasonNumber": "Sezon {0}",
|
||||
"NameSeasonUnknown": "Sezon nieznany",
|
||||
"NameSeasonUnknown": "Nieznany sezon",
|
||||
"NewVersionIsAvailable": "Nowa wersja serwera Jellyfin jest dostępna do pobrania.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Dostępna aktualizacja aplikacji",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Zainstalowano aktualizację aplikacji",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Zaktualizowano aplikację",
|
||||
"NotificationOptionAudioPlayback": "Rozpoczęto odtwarzanie muzyki",
|
||||
"NotificationOptionAudioPlaybackStopped": "Odtwarzane dźwięku zatrzymane",
|
||||
"NotificationOptionCameraImageUploaded": "Przekazano obraz z urządzenia mobilnego",
|
||||
"NotificationOptionInstallationFailed": "Niepowodzenie instalacji",
|
||||
"NotificationOptionCameraImageUploaded": "Przekazano obraz z urządzenia przenośnego",
|
||||
"NotificationOptionInstallationFailed": "Nieudana instalacja",
|
||||
"NotificationOptionNewLibraryContent": "Dodano nową zawartość",
|
||||
"NotificationOptionPluginError": "Awaria wtyczki",
|
||||
"NotificationOptionPluginInstalled": "Zainstalowano wtyczkę",
|
||||
"NotificationOptionPluginUninstalled": "Odinstalowano wtyczkę",
|
||||
"NotificationOptionPluginUpdateInstalled": "Zainstalowano aktualizację wtyczki",
|
||||
"NotificationOptionPluginUpdateInstalled": "Zaktualizowano wtyczkę",
|
||||
"NotificationOptionServerRestartRequired": "Wymagane ponowne uruchomienie serwera",
|
||||
"NotificationOptionTaskFailed": "Awaria zaplanowanego zadania",
|
||||
"NotificationOptionUserLockedOut": "Użytkownik zablokowany",
|
||||
"NotificationOptionVideoPlayback": "Rozpoczęto odtwarzanie wideo",
|
||||
"NotificationOptionVideoPlaybackStopped": "Odtwarzanie wideo zatrzymane",
|
||||
"NotificationOptionVideoPlaybackStopped": "Zatrzymano odtwarzanie wideo",
|
||||
"Photos": "Zdjęcia",
|
||||
"Playlists": "Listy odtwarzania",
|
||||
"Plugin": "Wtyczka",
|
||||
|
@ -73,7 +73,7 @@
|
|||
"ServerNameNeedsToBeRestarted": "{0} wymaga ponownego uruchomienia",
|
||||
"Shows": "Seriale",
|
||||
"Songs": "Utwory",
|
||||
"StartupEmbyServerIsLoading": "Twa wczytywanie serwera Jellyfin. Spróbuj ponownie za chwilę.",
|
||||
"StartupEmbyServerIsLoading": "Trwa wczytywanie serwera Jellyfin. Spróbuj ponownie za chwilę.",
|
||||
"SubtitleDownloadFailureForItem": "Pobieranie napisów dla {0} zakończone niepowodzeniem",
|
||||
"SubtitleDownloadFailureFromForItem": "Nieudane pobieranie napisów z {0} dla {1}",
|
||||
"SubtitlesDownloadedForItem": "Pobrano napisy dla {0}",
|
||||
|
@ -91,7 +91,7 @@
|
|||
"UserPolicyUpdatedWithName": "Zmieniono zasady użytkowania dla {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} odtwarza {1} na {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} zakończył odtwarzanie {1} na {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} został dodany to biblioteki mediów",
|
||||
"ValueHasBeenAddedToLibrary": "{0} został dodany do biblioteki mediów",
|
||||
"ValueSpecialEpisodeName": "Specjalne - {0}",
|
||||
"VersionNumber": "Wersja {0}"
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@
|
|||
"HeaderFavoriteAlbums": "Álbuns Favoritos",
|
||||
"HeaderFavoriteArtists": "Artistas Favoritos",
|
||||
"HeaderFavoriteEpisodes": "Episódios Favoritos",
|
||||
"HeaderFavoriteShows": "Shows Favoritos",
|
||||
"HeaderFavoriteSongs": "Musicas Favoritas",
|
||||
"HeaderFavoriteShows": "Séries Favoritas",
|
||||
"HeaderFavoriteSongs": "Músicas Favoritas",
|
||||
"HeaderLiveTV": "TV ao Vivo",
|
||||
"HeaderNextUp": "Próximos",
|
||||
"HeaderRecordingGroups": "Grupos de Gravação",
|
||||
|
@ -32,19 +32,19 @@
|
|||
"ItemRemovedWithName": "{0} foi removido da biblioteca",
|
||||
"LabelIpAddressValue": "Endereço IP: {0}",
|
||||
"LabelRunningTimeValue": "Tempo de execução: {0}",
|
||||
"Latest": "Recente",
|
||||
"MessageApplicationUpdated": "O servidor Jellyfin foi atualizado",
|
||||
"MessageApplicationUpdatedTo": "O Servidor Jellyfin foi atualizado para {0}",
|
||||
"Latest": "Recentes",
|
||||
"MessageApplicationUpdated": "Servidor Jellyfin atualizado",
|
||||
"MessageApplicationUpdatedTo": "Servidor Jellyfin atualizado para {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "A seção {0} da configuração do servidor foi atualizada",
|
||||
"MessageServerConfigurationUpdated": "A configuração do servidor foi atualizada",
|
||||
"MixedContent": "Conteúdo misto",
|
||||
"Movies": "Filmes",
|
||||
"Music": "Música",
|
||||
"MusicVideos": "Vídeos musicais",
|
||||
"MusicVideos": "Clipes",
|
||||
"NameInstallFailed": "A instalação de {0} falhou",
|
||||
"NameSeasonNumber": "Temporada {0}",
|
||||
"NameSeasonUnknown": "Temporada Desconhecida",
|
||||
"NewVersionIsAvailable": "Uma nova versão do servidor Jellyfin está disponível para download.",
|
||||
"NewVersionIsAvailable": "Uma nova versão do Servidor Jellyfin está disponível para download.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Atualização de aplicativo disponível",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Atualização de aplicativo instalada",
|
||||
"NotificationOptionAudioPlayback": "Reprodução de áudio iniciada",
|
||||
|
|
|
@ -16,14 +16,14 @@ namespace Emby.Server.Implementations.Net
|
|||
// but that wasn't really the point so kept to YAGNI principal for now, even if the
|
||||
// interfaces are a bit ugly, specific and make assumptions.
|
||||
|
||||
public ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort)
|
||||
public ISocket CreateTcpSocket(IPAddress remoteAddress, int remotePort)
|
||||
{
|
||||
if (remotePort < 0)
|
||||
{
|
||||
throw new ArgumentException("remotePort cannot be less than zero.", nameof(remotePort));
|
||||
}
|
||||
|
||||
var addressFamily = remoteAddress.AddressFamily == IpAddressFamily.InterNetwork
|
||||
var addressFamily = remoteAddress.AddressFamily == AddressFamily.InterNetwork
|
||||
? AddressFamily.InterNetwork
|
||||
: AddressFamily.InterNetworkV6;
|
||||
|
||||
|
@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.Net
|
|||
|
||||
try
|
||||
{
|
||||
return new UdpSocket(retVal, new IpEndPointInfo(remoteAddress, remotePort));
|
||||
return new UdpSocket(retVal, new IPEndPoint(remoteAddress, remotePort));
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Net
|
|||
/// Creates a new UDP acceptSocket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
|
||||
/// </summary>
|
||||
/// <returns>An implementation of the <see cref="ISocket"/> interface used by RSSDP components to perform acceptSocket operations.</returns>
|
||||
public ISocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort)
|
||||
public ISocket CreateSsdpUdpSocket(IPAddress localIpAddress, int localPort)
|
||||
{
|
||||
if (localPort < 0)
|
||||
{
|
||||
|
@ -115,10 +115,8 @@ namespace Emby.Server.Implementations.Net
|
|||
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
|
||||
|
||||
var localIp = NetworkManager.ToIPAddress(localIpAddress);
|
||||
|
||||
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIp));
|
||||
return new UdpSocket(retVal, localPort, localIp);
|
||||
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIpAddress));
|
||||
return new UdpSocket(retVal, localPort, localIpAddress);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
|
@ -3,7 +3,6 @@ using System.Net;
|
|||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Networking;
|
||||
using MediaBrowser.Model.Net;
|
||||
|
||||
namespace Emby.Server.Implementations.Net
|
||||
|
@ -19,7 +18,7 @@ namespace Emby.Server.Implementations.Net
|
|||
|
||||
public Socket Socket => _socket;
|
||||
|
||||
public IpAddressInfo LocalIPAddress { get; }
|
||||
public IPAddress LocalIPAddress { get; }
|
||||
|
||||
private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs()
|
||||
{
|
||||
|
@ -40,7 +39,7 @@ namespace Emby.Server.Implementations.Net
|
|||
|
||||
_socket = socket;
|
||||
_localPort = localPort;
|
||||
LocalIPAddress = NetworkManager.ToIpAddressInfo(ip);
|
||||
LocalIPAddress = ip;
|
||||
|
||||
_socket.Bind(new IPEndPoint(ip, _localPort));
|
||||
|
||||
|
@ -71,7 +70,7 @@ namespace Emby.Server.Implementations.Net
|
|||
{
|
||||
Buffer = e.Buffer,
|
||||
ReceivedBytes = e.BytesTransferred,
|
||||
RemoteEndPoint = ToIpEndPointInfo(e.RemoteEndPoint as IPEndPoint),
|
||||
RemoteEndPoint = e.RemoteEndPoint as IPEndPoint,
|
||||
LocalIPAddress = LocalIPAddress
|
||||
});
|
||||
}
|
||||
|
@ -100,12 +99,12 @@ namespace Emby.Server.Implementations.Net
|
|||
}
|
||||
}
|
||||
|
||||
public UdpSocket(Socket socket, IpEndPointInfo endPoint)
|
||||
public UdpSocket(Socket socket, IPEndPoint endPoint)
|
||||
{
|
||||
if (socket == null) throw new ArgumentNullException(nameof(socket));
|
||||
|
||||
_socket = socket;
|
||||
_socket.Connect(NetworkManager.ToIPEndPoint(endPoint));
|
||||
_socket.Connect(endPoint);
|
||||
|
||||
InitReceiveSocketAsyncEventArgs();
|
||||
}
|
||||
|
@ -140,7 +139,7 @@ namespace Emby.Server.Implementations.Net
|
|||
return new SocketReceiveResult
|
||||
{
|
||||
ReceivedBytes = receivedBytes,
|
||||
RemoteEndPoint = ToIpEndPointInfo((IPEndPoint)remoteEndPoint),
|
||||
RemoteEndPoint = (IPEndPoint)remoteEndPoint,
|
||||
Buffer = buffer,
|
||||
LocalIPAddress = LocalIPAddress
|
||||
};
|
||||
|
@ -191,7 +190,7 @@ namespace Emby.Server.Implementations.Net
|
|||
return ReceiveAsync(buffer, 0, buffer.Length, cancellationToken);
|
||||
}
|
||||
|
||||
public Task SendToAsync(byte[] buffer, int offset, int size, IpEndPointInfo endPoint, CancellationToken cancellationToken)
|
||||
public Task SendToAsync(byte[] buffer, int offset, int size, IPEndPoint endPoint, CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
|
@ -227,13 +226,11 @@ namespace Emby.Server.Implementations.Net
|
|||
return taskCompletion.Task;
|
||||
}
|
||||
|
||||
public IAsyncResult BeginSendTo(byte[] buffer, int offset, int size, IpEndPointInfo endPoint, AsyncCallback callback, object state)
|
||||
public IAsyncResult BeginSendTo(byte[] buffer, int offset, int size, IPEndPoint endPoint, AsyncCallback callback, object state)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
var ipEndPoint = NetworkManager.ToIPEndPoint(endPoint);
|
||||
|
||||
return _socket.BeginSendTo(buffer, offset, size, SocketFlags.None, ipEndPoint, callback, state);
|
||||
return _socket.BeginSendTo(buffer, offset, size, SocketFlags.None, endPoint, callback, state);
|
||||
}
|
||||
|
||||
public int EndSendTo(IAsyncResult result)
|
||||
|
@ -268,15 +265,5 @@ namespace Emby.Server.Implementations.Net
|
|||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint)
|
||||
{
|
||||
if (endpoint == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return NetworkManager.ToIpEndPointInfo(endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,167 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
namespace Emby.Server.Implementations.Networking.IPNetwork
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods to convert <see cref="BigInteger"/>
|
||||
/// instances to hexadecimal, octal, and binary strings.
|
||||
/// </summary>
|
||||
public static class BigIntegerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a <see cref="BigInteger"/> to a binary string.
|
||||
/// </summary>
|
||||
/// <param name="bigint">A <see cref="BigInteger"/>.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="string"/> containing a binary
|
||||
/// representation of the supplied <see cref="BigInteger"/>.
|
||||
/// </returns>
|
||||
public static string ToBinaryString(this BigInteger bigint)
|
||||
{
|
||||
var bytes = bigint.ToByteArray();
|
||||
var idx = bytes.Length - 1;
|
||||
|
||||
// Create a StringBuilder having appropriate capacity.
|
||||
var base2 = new StringBuilder(bytes.Length * 8);
|
||||
|
||||
// Convert first byte to binary.
|
||||
var binary = Convert.ToString(bytes[idx], 2);
|
||||
|
||||
// Ensure leading zero exists if value is positive.
|
||||
if (binary[0] != '0' && bigint.Sign == 1)
|
||||
{
|
||||
base2.Append('0');
|
||||
}
|
||||
|
||||
// Append binary string to StringBuilder.
|
||||
base2.Append(binary);
|
||||
|
||||
// Convert remaining bytes adding leading zeros.
|
||||
for (idx--; idx >= 0; idx--)
|
||||
{
|
||||
base2.Append(Convert.ToString(bytes[idx], 2).PadLeft(8, '0'));
|
||||
}
|
||||
|
||||
return base2.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="BigInteger"/> to a hexadecimal string.
|
||||
/// </summary>
|
||||
/// <param name="bigint">A <see cref="BigInteger"/>.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="string"/> containing a hexadecimal
|
||||
/// representation of the supplied <see cref="BigInteger"/>.
|
||||
/// </returns>
|
||||
public static string ToHexadecimalString(this BigInteger bigint)
|
||||
{
|
||||
return bigint.ToString("X");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="BigInteger"/> to a octal string.
|
||||
/// </summary>
|
||||
/// <param name="bigint">A <see cref="BigInteger"/>.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="string"/> containing an octal
|
||||
/// representation of the supplied <see cref="BigInteger"/>.
|
||||
/// </returns>
|
||||
public static string ToOctalString(this BigInteger bigint)
|
||||
{
|
||||
var bytes = bigint.ToByteArray();
|
||||
var idx = bytes.Length - 1;
|
||||
|
||||
// Create a StringBuilder having appropriate capacity.
|
||||
var base8 = new StringBuilder(((bytes.Length / 3) + 1) * 8);
|
||||
|
||||
// Calculate how many bytes are extra when byte array is split
|
||||
// into three-byte (24-bit) chunks.
|
||||
var extra = bytes.Length % 3;
|
||||
|
||||
// If no bytes are extra, use three bytes for first chunk.
|
||||
if (extra == 0)
|
||||
{
|
||||
extra = 3;
|
||||
}
|
||||
|
||||
// Convert first chunk (24-bits) to integer value.
|
||||
int int24 = 0;
|
||||
for (; extra != 0; extra--)
|
||||
{
|
||||
int24 <<= 8;
|
||||
int24 += bytes[idx--];
|
||||
}
|
||||
|
||||
// Convert 24-bit integer to octal without adding leading zeros.
|
||||
var octal = Convert.ToString(int24, 8);
|
||||
|
||||
// Ensure leading zero exists if value is positive.
|
||||
if (octal[0] != '0')
|
||||
{
|
||||
if (bigint.Sign == 1)
|
||||
{
|
||||
base8.Append('0');
|
||||
}
|
||||
}
|
||||
|
||||
// Append first converted chunk to StringBuilder.
|
||||
base8.Append(octal);
|
||||
|
||||
// Convert remaining 24-bit chunks, adding leading zeros.
|
||||
for (; idx >= 0; idx -= 3)
|
||||
{
|
||||
int24 = (bytes[idx] << 16) + (bytes[idx - 1] << 8) + bytes[idx - 2];
|
||||
base8.Append(Convert.ToString(int24, 8).PadLeft(8, '0'));
|
||||
}
|
||||
|
||||
return base8.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Reverse a Positive BigInteger ONLY
|
||||
/// Bitwise ~ operator
|
||||
///
|
||||
/// Input : FF FF FF FF
|
||||
/// Width : 4
|
||||
/// Result : 00 00 00 00
|
||||
///
|
||||
///
|
||||
/// Input : 00 00 00 00
|
||||
/// Width : 4
|
||||
/// Result : FF FF FF FF
|
||||
///
|
||||
/// Input : FF FF FF FF
|
||||
/// Width : 8
|
||||
/// Result : FF FF FF FF 00 00 00 00
|
||||
///
|
||||
///
|
||||
/// Input : 00 00 00 00
|
||||
/// Width : 8
|
||||
/// Result : FF FF FF FF FF FF FF FF
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="width"></param>
|
||||
/// <returns></returns>
|
||||
public static BigInteger PositiveReverse(this BigInteger input, int width)
|
||||
{
|
||||
|
||||
var result = new List<byte>();
|
||||
var bytes = input.ToByteArray();
|
||||
var work = new byte[width];
|
||||
Array.Copy(bytes, 0, work, 0, bytes.Length - 1); // Length -1 : positive BigInteger
|
||||
|
||||
for (int i = 0; i < work.Length; i++)
|
||||
{
|
||||
result.Add((byte)(~work[i]));
|
||||
}
|
||||
result.Add(0); // positive BigInteger
|
||||
return new BigInteger(result.ToArray());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Emby.Server.Implementations.Networking.IPNetwork
|
||||
{
|
||||
public class IPAddressCollection : IEnumerable<IPAddress>, IEnumerator<IPAddress>
|
||||
{
|
||||
|
||||
private IPNetwork _ipnetwork;
|
||||
private BigInteger _enumerator;
|
||||
|
||||
internal IPAddressCollection(IPNetwork ipnetwork)
|
||||
{
|
||||
this._ipnetwork = ipnetwork;
|
||||
this._enumerator = -1;
|
||||
}
|
||||
|
||||
|
||||
#region Count, Array, Enumerator
|
||||
|
||||
public BigInteger Count => this._ipnetwork.Total;
|
||||
|
||||
public IPAddress this[BigInteger i]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (i >= this.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(i));
|
||||
}
|
||||
byte width = this._ipnetwork.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ? (byte)32 : (byte)128;
|
||||
var ipn = this._ipnetwork.Subnet(width);
|
||||
return ipn[i].Network;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
IEnumerator<IPAddress> IEnumerable<IPAddress>.GetEnumerator()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
#region IEnumerator<IPNetwork> Members
|
||||
|
||||
public IPAddress Current => this[this._enumerator];
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// nothing to dispose
|
||||
return;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerator Members
|
||||
|
||||
object IEnumerator.Current => this.Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
this._enumerator++;
|
||||
if (this._enumerator >= this.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this._enumerator = -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,129 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Emby.Server.Implementations.Networking.IPNetwork
|
||||
{
|
||||
public class IPNetworkCollection : IEnumerable<IPNetwork>, IEnumerator<IPNetwork>
|
||||
{
|
||||
|
||||
private BigInteger _enumerator;
|
||||
private byte _cidrSubnet;
|
||||
private IPNetwork _ipnetwork;
|
||||
|
||||
private byte _cidr => this._ipnetwork.Cidr;
|
||||
|
||||
private BigInteger _broadcast => IPNetwork.ToBigInteger(this._ipnetwork.Broadcast);
|
||||
|
||||
private BigInteger _lastUsable => IPNetwork.ToBigInteger(this._ipnetwork.LastUsable);
|
||||
private BigInteger _network => IPNetwork.ToBigInteger(this._ipnetwork.Network);
|
||||
#if TRAVISCI
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
IPNetworkCollection(IPNetwork ipnetwork, byte cidrSubnet)
|
||||
{
|
||||
|
||||
int maxCidr = ipnetwork.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ? 32 : 128;
|
||||
if (cidrSubnet > maxCidr)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(cidrSubnet));
|
||||
}
|
||||
|
||||
if (cidrSubnet < ipnetwork.Cidr)
|
||||
{
|
||||
throw new ArgumentException("cidr");
|
||||
}
|
||||
|
||||
this._cidrSubnet = cidrSubnet;
|
||||
this._ipnetwork = ipnetwork;
|
||||
this._enumerator = -1;
|
||||
}
|
||||
|
||||
#region Count, Array, Enumerator
|
||||
|
||||
public BigInteger Count
|
||||
{
|
||||
get
|
||||
{
|
||||
var count = BigInteger.Pow(2, this._cidrSubnet - this._cidr);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public IPNetwork this[BigInteger i]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (i >= this.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(i));
|
||||
}
|
||||
|
||||
var last = this._ipnetwork.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6
|
||||
? this._lastUsable : this._broadcast;
|
||||
var increment = (last - this._network) / this.Count;
|
||||
var uintNetwork = this._network + ((increment + 1) * i);
|
||||
var ipn = new IPNetwork(uintNetwork, this._ipnetwork.AddressFamily, this._cidrSubnet);
|
||||
return ipn;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
IEnumerator<IPNetwork> IEnumerable<IPNetwork>.GetEnumerator()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
#region IEnumerator<IPNetwork> Members
|
||||
|
||||
public IPNetwork Current => this[this._enumerator];
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// nothing to dispose
|
||||
return;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerator Members
|
||||
|
||||
object IEnumerator.Current => this.Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
this._enumerator++;
|
||||
if (this._enumerator >= this.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this._enumerator = -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
Copyright (c) 2015, lduchosal
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -9,55 +9,38 @@ using System.Threading.Tasks;
|
|||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||
|
||||
namespace Emby.Server.Implementations.Networking
|
||||
{
|
||||
public class NetworkManager : INetworkManager
|
||||
{
|
||||
protected ILogger Logger { get; private set; }
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public event EventHandler NetworkChanged;
|
||||
public Func<string[]> LocalSubnetsFn { get; set; }
|
||||
private IPAddress[] _localIpAddresses;
|
||||
private readonly object _localIpAddressSyncLock = new object();
|
||||
|
||||
public NetworkManager(ILoggerFactory loggerFactory)
|
||||
public NetworkManager(ILogger<NetworkManager> logger)
|
||||
{
|
||||
Logger = loggerFactory.CreateLogger(nameof(NetworkManager));
|
||||
_logger = logger;
|
||||
|
||||
// In FreeBSD these events cause a crash
|
||||
if (OperatingSystem.Id != OperatingSystemId.BSD)
|
||||
{
|
||||
try
|
||||
{
|
||||
NetworkChange.NetworkAddressChanged += NetworkChange_NetworkAddressChanged;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error binding to NetworkAddressChanged event");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
NetworkChange.NetworkAvailabilityChanged += NetworkChange_NetworkAvailabilityChanged;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error binding to NetworkChange_NetworkAvailabilityChanged event");
|
||||
}
|
||||
}
|
||||
NetworkChange.NetworkAddressChanged += OnNetworkAddressChanged;
|
||||
NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
|
||||
}
|
||||
|
||||
private void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
|
||||
public Func<string[]> LocalSubnetsFn { get; set; }
|
||||
|
||||
public event EventHandler NetworkChanged;
|
||||
|
||||
private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
|
||||
{
|
||||
Logger.LogDebug("NetworkAvailabilityChanged");
|
||||
_logger.LogDebug("NetworkAvailabilityChanged");
|
||||
OnNetworkChanged();
|
||||
}
|
||||
|
||||
private void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
|
||||
private void OnNetworkAddressChanged(object sender, EventArgs e)
|
||||
{
|
||||
Logger.LogDebug("NetworkAddressChanged");
|
||||
_logger.LogDebug("NetworkAddressChanged");
|
||||
OnNetworkChanged();
|
||||
}
|
||||
|
||||
|
@ -68,39 +51,35 @@ namespace Emby.Server.Implementations.Networking
|
|||
_localIpAddresses = null;
|
||||
_macAddresses = null;
|
||||
}
|
||||
|
||||
if (NetworkChanged != null)
|
||||
{
|
||||
NetworkChanged(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private IpAddressInfo[] _localIpAddresses;
|
||||
private readonly object _localIpAddressSyncLock = new object();
|
||||
|
||||
public IpAddressInfo[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
|
||||
public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
|
||||
{
|
||||
lock (_localIpAddressSyncLock)
|
||||
{
|
||||
if (_localIpAddresses == null)
|
||||
{
|
||||
var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).Result.Select(ToIpAddressInfo).ToArray();
|
||||
var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).ToArray();
|
||||
|
||||
_localIpAddresses = addresses;
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
return _localIpAddresses;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<IPAddress>> GetLocalIpAddressesInternal(bool ignoreVirtualInterface)
|
||||
private List<IPAddress> GetLocalIpAddressesInternal(bool ignoreVirtualInterface)
|
||||
{
|
||||
var list = GetIPsDefault(ignoreVirtualInterface)
|
||||
.ToList();
|
||||
var list = GetIPsDefault(ignoreVirtualInterface).ToList();
|
||||
|
||||
if (list.Count == 0)
|
||||
{
|
||||
list.AddRange(await GetLocalIpAddressesFallback().ConfigureAwait(false));
|
||||
list = GetLocalIpAddressesFallback().GetAwaiter().GetResult().ToList();
|
||||
}
|
||||
|
||||
var listClone = list.ToList();
|
||||
|
@ -116,9 +95,8 @@ namespace Emby.Server.Implementations.Networking
|
|||
|
||||
private static bool FilterIpAddress(IPAddress address)
|
||||
{
|
||||
var addressString = address.ToString();
|
||||
|
||||
if (addressString.StartsWith("169.", StringComparison.OrdinalIgnoreCase))
|
||||
if (address.IsIPv6LinkLocal
|
||||
|| address.ToString().StartsWith("169.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -279,7 +257,7 @@ namespace Emby.Server.Implementations.Networking
|
|||
|
||||
if (normalizedSubnet.IndexOf('/') != -1)
|
||||
{
|
||||
var ipnetwork = IPNetwork.IPNetwork.Parse(normalizedSubnet);
|
||||
var ipnetwork = IPNetwork.Parse(normalizedSubnet);
|
||||
if (ipnetwork.Contains(address))
|
||||
{
|
||||
return true;
|
||||
|
@ -351,13 +329,13 @@ namespace Emby.Server.Implementations.Networking
|
|||
try
|
||||
{
|
||||
var host = uri.DnsSafeHost;
|
||||
Logger.LogDebug("Resolving host {0}", host);
|
||||
_logger.LogDebug("Resolving host {0}", host);
|
||||
|
||||
address = GetIpAddresses(host).Result.FirstOrDefault();
|
||||
|
||||
if (address != null)
|
||||
{
|
||||
Logger.LogDebug("{0} resolved to {1}", host, address);
|
||||
_logger.LogDebug("{0} resolved to {1}", host, address);
|
||||
|
||||
return IsInLocalNetworkInternal(address.ToString(), false);
|
||||
}
|
||||
|
@ -368,7 +346,7 @@ namespace Emby.Server.Implementations.Networking
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error resolving hostname");
|
||||
_logger.LogError(ex, "Error resolving hostname");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -381,56 +359,41 @@ namespace Emby.Server.Implementations.Networking
|
|||
return Dns.GetHostAddressesAsync(hostName);
|
||||
}
|
||||
|
||||
private List<IPAddress> GetIPsDefault(bool ignoreVirtualInterface)
|
||||
private IEnumerable<IPAddress> GetIPsDefault(bool ignoreVirtualInterface)
|
||||
{
|
||||
NetworkInterface[] interfaces;
|
||||
IEnumerable<NetworkInterface> interfaces;
|
||||
|
||||
try
|
||||
{
|
||||
var validStatuses = new[] { OperationalStatus.Up, OperationalStatus.Unknown };
|
||||
|
||||
interfaces = NetworkInterface.GetAllNetworkInterfaces()
|
||||
.Where(i => validStatuses.Contains(i.OperationalStatus))
|
||||
.ToArray();
|
||||
.Where(x => x.OperationalStatus == OperationalStatus.Up
|
||||
|| x.OperationalStatus == OperationalStatus.Unknown);
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (NetworkInformationException ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error in GetAllNetworkInterfaces");
|
||||
return new List<IPAddress>();
|
||||
_logger.LogError(ex, "Error in GetAllNetworkInterfaces");
|
||||
return Enumerable.Empty<IPAddress>();
|
||||
}
|
||||
|
||||
return interfaces.SelectMany(network =>
|
||||
{
|
||||
var ipProperties = network.GetIPProperties();
|
||||
|
||||
try
|
||||
// Try to exclude virtual adapters
|
||||
// http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms
|
||||
var addr = ipProperties.GatewayAddresses.FirstOrDefault();
|
||||
if (addr == null
|
||||
|| (ignoreVirtualInterface
|
||||
&& (addr.Address.Equals(IPAddress.Any) || addr.Address.Equals(IPAddress.IPv6Any))))
|
||||
{
|
||||
// suppress logging because it might be causing nas device wake up
|
||||
//logger.LogDebug("Querying interface: {0}. Type: {1}. Status: {2}", network.Name, network.NetworkInterfaceType, network.OperationalStatus);
|
||||
|
||||
var ipProperties = network.GetIPProperties();
|
||||
|
||||
// Try to exclude virtual adapters
|
||||
// http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms
|
||||
var addr = ipProperties.GatewayAddresses.FirstOrDefault();
|
||||
if (addr == null || ignoreVirtualInterface && string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new List<IPAddress>();
|
||||
}
|
||||
|
||||
return ipProperties.UnicastAddresses
|
||||
.Select(i => i.Address)
|
||||
.Where(i => i.AddressFamily == AddressFamily.InterNetwork || i.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error querying network interface");
|
||||
return new List<IPAddress>();
|
||||
return Enumerable.Empty<IPAddress>();
|
||||
}
|
||||
|
||||
return ipProperties.UnicastAddresses
|
||||
.Select(i => i.Address)
|
||||
.Where(i => i.AddressFamily == AddressFamily.InterNetwork || i.AddressFamily == AddressFamily.InterNetworkV6);
|
||||
}).GroupBy(i => i.ToString())
|
||||
.Select(x => x.First())
|
||||
.ToList();
|
||||
.Select(x => x.First());
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<IPAddress>> GetLocalIpAddressesFallback()
|
||||
|
@ -462,47 +425,27 @@ namespace Emby.Server.Implementations.Networking
|
|||
var localEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
||||
using (var udpClient = new UdpClient(localEndPoint))
|
||||
{
|
||||
var port = ((IPEndPoint)(udpClient.Client.LocalEndPoint)).Port;
|
||||
var port = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port;
|
||||
return port;
|
||||
}
|
||||
}
|
||||
|
||||
private List<string> _macAddresses;
|
||||
public List<string> GetMacAddresses()
|
||||
private List<PhysicalAddress> _macAddresses;
|
||||
public List<PhysicalAddress> GetMacAddresses()
|
||||
{
|
||||
if (_macAddresses == null)
|
||||
{
|
||||
_macAddresses = GetMacAddressesInternal();
|
||||
_macAddresses = GetMacAddressesInternal().ToList();
|
||||
}
|
||||
|
||||
return _macAddresses;
|
||||
}
|
||||
|
||||
private static List<string> GetMacAddressesInternal()
|
||||
{
|
||||
return NetworkInterface.GetAllNetworkInterfaces()
|
||||
private static IEnumerable<PhysicalAddress> GetMacAddressesInternal()
|
||||
=> NetworkInterface.GetAllNetworkInterfaces()
|
||||
.Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback)
|
||||
.Select(i =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var physicalAddress = i.GetPhysicalAddress();
|
||||
|
||||
if (physicalAddress == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return physicalAddress.ToString();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//TODO Log exception.
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.Where(i => i != null)
|
||||
.ToList();
|
||||
}
|
||||
.Select(x => x.GetPhysicalAddress())
|
||||
.Where(x => x != null && x != PhysicalAddress.None);
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified endpointstring.
|
||||
|
@ -612,32 +555,10 @@ namespace Emby.Server.Implementations.Networking
|
|||
return hosts[0];
|
||||
}
|
||||
|
||||
public IpAddressInfo ParseIpAddress(string ipAddress)
|
||||
public bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask)
|
||||
{
|
||||
if (TryParseIpAddress(ipAddress, out var info))
|
||||
{
|
||||
return info;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Invalid ip address: " + ipAddress);
|
||||
}
|
||||
|
||||
public bool TryParseIpAddress(string ipAddress, out IpAddressInfo ipAddressInfo)
|
||||
{
|
||||
if (IPAddress.TryParse(ipAddress, out var address))
|
||||
{
|
||||
ipAddressInfo = ToIpAddressInfo(address);
|
||||
return true;
|
||||
}
|
||||
|
||||
ipAddressInfo = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsInSameSubnet(IpAddressInfo address1, IpAddressInfo address2, IpAddressInfo subnetMask)
|
||||
{
|
||||
IPAddress network1 = GetNetworkAddress(ToIPAddress(address1), ToIPAddress(subnetMask));
|
||||
IPAddress network2 = GetNetworkAddress(ToIPAddress(address2), ToIPAddress(subnetMask));
|
||||
IPAddress network1 = GetNetworkAddress(address1, subnetMask);
|
||||
IPAddress network2 = GetNetworkAddress(address2, subnetMask);
|
||||
return network1.Equals(network2);
|
||||
}
|
||||
|
||||
|
@ -656,13 +577,13 @@ namespace Emby.Server.Implementations.Networking
|
|||
{
|
||||
broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
|
||||
}
|
||||
|
||||
return new IPAddress(broadcastAddress);
|
||||
}
|
||||
|
||||
public IpAddressInfo GetLocalIpSubnetMask(IpAddressInfo address)
|
||||
public IPAddress GetLocalIpSubnetMask(IPAddress address)
|
||||
{
|
||||
NetworkInterface[] interfaces;
|
||||
IPAddress ipaddress = ToIPAddress(address);
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -674,7 +595,7 @@ namespace Emby.Server.Implementations.Networking
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error in GetAllNetworkInterfaces");
|
||||
_logger.LogError(ex, "Error in GetAllNetworkInterfaces");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -684,85 +605,17 @@ namespace Emby.Server.Implementations.Networking
|
|||
{
|
||||
foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
|
||||
{
|
||||
if (ip.Address.Equals(ipaddress) && ip.IPv4Mask != null)
|
||||
if (ip.Address.Equals(address) && ip.IPv4Mask != null)
|
||||
{
|
||||
return ToIpAddressInfo(ip.IPv4Mask);
|
||||
return ip.IPv4Mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint)
|
||||
{
|
||||
if (endpoint == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new IpEndPointInfo(ToIpAddressInfo(endpoint.Address), endpoint.Port);
|
||||
}
|
||||
|
||||
public static IPEndPoint ToIPEndPoint(IpEndPointInfo endpoint)
|
||||
{
|
||||
if (endpoint == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new IPEndPoint(ToIPAddress(endpoint.IpAddress), endpoint.Port);
|
||||
}
|
||||
|
||||
public static IPAddress ToIPAddress(IpAddressInfo address)
|
||||
{
|
||||
if (address.Equals(IpAddressInfo.Any))
|
||||
{
|
||||
return IPAddress.Any;
|
||||
}
|
||||
if (address.Equals(IpAddressInfo.IPv6Any))
|
||||
{
|
||||
return IPAddress.IPv6Any;
|
||||
}
|
||||
if (address.Equals(IpAddressInfo.Loopback))
|
||||
{
|
||||
return IPAddress.Loopback;
|
||||
}
|
||||
if (address.Equals(IpAddressInfo.IPv6Loopback))
|
||||
{
|
||||
return IPAddress.IPv6Loopback;
|
||||
}
|
||||
|
||||
return IPAddress.Parse(address.Address);
|
||||
}
|
||||
|
||||
public static IpAddressInfo ToIpAddressInfo(IPAddress address)
|
||||
{
|
||||
if (address.Equals(IPAddress.Any))
|
||||
{
|
||||
return IpAddressInfo.Any;
|
||||
}
|
||||
if (address.Equals(IPAddress.IPv6Any))
|
||||
{
|
||||
return IpAddressInfo.IPv6Any;
|
||||
}
|
||||
if (address.Equals(IPAddress.Loopback))
|
||||
{
|
||||
return IpAddressInfo.Loopback;
|
||||
}
|
||||
if (address.Equals(IPAddress.IPv6Loopback))
|
||||
{
|
||||
return IpAddressInfo.IPv6Loopback;
|
||||
}
|
||||
return new IpAddressInfo(address.ToString(), address.AddressFamily == AddressFamily.InterNetworkV6 ? IpAddressFamily.InterNetworkV6 : IpAddressFamily.InterNetwork);
|
||||
}
|
||||
|
||||
public async Task<IpAddressInfo[]> GetHostAddressesAsync(string host)
|
||||
{
|
||||
var addresses = await Dns.GetHostAddressesAsync(host).ConfigureAwait(false);
|
||||
return addresses.Select(ToIpAddressInfo).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the network shares.
|
||||
/// </summary>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
@ -129,7 +130,7 @@ namespace Emby.Server.Implementations.Playlists
|
|||
{
|
||||
new Share
|
||||
{
|
||||
UserId = options.UserId.Equals(Guid.Empty) ? null : options.UserId.ToString("N"),
|
||||
UserId = options.UserId.Equals(Guid.Empty) ? null : options.UserId.ToString("N", CultureInfo.InvariantCulture),
|
||||
CanEdit = true
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +145,7 @@ namespace Emby.Server.Implementations.Playlists
|
|||
|
||||
if (options.ItemIdList.Length > 0)
|
||||
{
|
||||
AddToPlaylistInternal(playlist.Id.ToString("N"), options.ItemIdList, user, new DtoOptions(false)
|
||||
AddToPlaylistInternal(playlist.Id.ToString("N", CultureInfo.InvariantCulture), options.ItemIdList, user, new DtoOptions(false)
|
||||
{
|
||||
EnableImages = true
|
||||
});
|
||||
|
@ -152,7 +153,7 @@ namespace Emby.Server.Implementations.Playlists
|
|||
|
||||
return new PlaylistCreationResult
|
||||
{
|
||||
Id = playlist.Id.ToString("N")
|
||||
Id = playlist.Id.ToString("N", CultureInfo.InvariantCulture)
|
||||
};
|
||||
}
|
||||
finally
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
@ -287,7 +287,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
{
|
||||
if (_id == null)
|
||||
{
|
||||
_id = ScheduledTask.GetType().FullName.GetMD5().ToString("N");
|
||||
_id = ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return _id;
|
||||
|
|
|
@ -97,7 +97,7 @@ namespace Emby.Server.Implementations.Security
|
|||
statement.TryBind("@AppName", info.AppName);
|
||||
statement.TryBind("@AppVersion", info.AppVersion);
|
||||
statement.TryBind("@DeviceName", info.DeviceName);
|
||||
statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N")));
|
||||
statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture)));
|
||||
statement.TryBind("@UserName", info.UserName);
|
||||
statement.TryBind("@IsActive", true);
|
||||
statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue());
|
||||
|
@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.Security
|
|||
statement.TryBind("@AppName", info.AppName);
|
||||
statement.TryBind("@AppVersion", info.AppVersion);
|
||||
statement.TryBind("@DeviceName", info.DeviceName);
|
||||
statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N")));
|
||||
statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture)));
|
||||
statement.TryBind("@UserName", info.UserName);
|
||||
statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue());
|
||||
statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue());
|
||||
|
@ -174,7 +174,7 @@ namespace Emby.Server.Implementations.Security
|
|||
|
||||
if (!query.UserId.Equals(Guid.Empty))
|
||||
{
|
||||
statement.TryBind("@UserId", query.UserId.ToString("N"));
|
||||
statement.TryBind("@UserId", query.UserId.ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(query.DeviceId))
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
@ -245,7 +246,7 @@ namespace Emby.Server.Implementations.Serialization
|
|||
return null;
|
||||
}
|
||||
|
||||
return guid.ToString("N");
|
||||
return guid.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -10,8 +10,12 @@ namespace Emby.Server.Implementations
|
|||
/// </summary>
|
||||
public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths
|
||||
{
|
||||
private string _defaultTranscodingTempPath;
|
||||
private string _transcodingTempPath;
|
||||
private string _internalMetadataPath;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseApplicationPaths" /> class.
|
||||
/// Initializes a new instance of the <see cref="ServerApplicationPaths" /> class.
|
||||
/// </summary>
|
||||
public ServerApplicationPaths(
|
||||
string programDataPath,
|
||||
|
@ -30,7 +34,7 @@ namespace Emby.Server.Implementations
|
|||
public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the base root media directory
|
||||
/// Gets the path to the base root media directory.
|
||||
/// </summary>
|
||||
/// <value>The root folder path.</value>
|
||||
public string RootFolderPath => Path.Combine(ProgramDataPath, "root");
|
||||
|
@ -48,7 +52,7 @@ namespace Emby.Server.Implementations
|
|||
public string LocalizationPath => Path.Combine(ProgramDataPath, "localization");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the People directory
|
||||
/// Gets the path to the People directory.
|
||||
/// </summary>
|
||||
/// <value>The people path.</value>
|
||||
public string PeoplePath => Path.Combine(InternalMetadataPath, "People");
|
||||
|
@ -56,37 +60,37 @@ namespace Emby.Server.Implementations
|
|||
public string ArtistsPath => Path.Combine(InternalMetadataPath, "artists");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the Genre directory
|
||||
/// Gets the path to the Genre directory.
|
||||
/// </summary>
|
||||
/// <value>The genre path.</value>
|
||||
public string GenrePath => Path.Combine(InternalMetadataPath, "Genre");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the Genre directory
|
||||
/// Gets the path to the Genre directory.
|
||||
/// </summary>
|
||||
/// <value>The genre path.</value>
|
||||
public string MusicGenrePath => Path.Combine(InternalMetadataPath, "MusicGenre");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the Studio directory
|
||||
/// Gets the path to the Studio directory.
|
||||
/// </summary>
|
||||
/// <value>The studio path.</value>
|
||||
public string StudioPath => Path.Combine(InternalMetadataPath, "Studio");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the Year directory
|
||||
/// Gets the path to the Year directory.
|
||||
/// </summary>
|
||||
/// <value>The year path.</value>
|
||||
public string YearPath => Path.Combine(InternalMetadataPath, "Year");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the General IBN directory
|
||||
/// Gets the path to the General IBN directory.
|
||||
/// </summary>
|
||||
/// <value>The general path.</value>
|
||||
public string GeneralPath => Path.Combine(InternalMetadataPath, "general");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the Ratings IBN directory
|
||||
/// Gets the path to the Ratings IBN directory.
|
||||
/// </summary>
|
||||
/// <value>The ratings path.</value>
|
||||
public string RatingsPath => Path.Combine(InternalMetadataPath, "ratings");
|
||||
|
@ -98,15 +102,13 @@ namespace Emby.Server.Implementations
|
|||
public string MediaInfoImagesPath => Path.Combine(InternalMetadataPath, "mediainfo");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the user configuration directory
|
||||
/// Gets the path to the user configuration directory.
|
||||
/// </summary>
|
||||
/// <value>The user configuration directory path.</value>
|
||||
public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users");
|
||||
|
||||
private string _defaultTranscodingTempPath;
|
||||
public string DefaultTranscodingTempPath => _defaultTranscodingTempPath ?? (_defaultTranscodingTempPath = Path.Combine(ProgramDataPath, "transcoding-temp"));
|
||||
|
||||
private string _transcodingTempPath;
|
||||
public string TranscodingTempPath
|
||||
{
|
||||
get => _transcodingTempPath ?? (_transcodingTempPath = DefaultTranscodingTempPath);
|
||||
|
@ -139,7 +141,6 @@ namespace Emby.Server.Implementations
|
|||
return path;
|
||||
}
|
||||
|
||||
private string _internalMetadataPath;
|
||||
public string InternalMetadataPath
|
||||
{
|
||||
get => _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata"));
|
||||
|
|
|
@ -10,8 +10,6 @@ namespace Emby.Server.Implementations.Services
|
|||
public class HttpResult
|
||||
: IHttpResult, IAsyncStreamWriter
|
||||
{
|
||||
public object Response { get; set; }
|
||||
|
||||
public HttpResult(object response, string contentType, HttpStatusCode statusCode)
|
||||
{
|
||||
this.Headers = new Dictionary<string, string>();
|
||||
|
@ -21,6 +19,8 @@ namespace Emby.Server.Implementations.Services
|
|||
this.StatusCode = statusCode;
|
||||
}
|
||||
|
||||
public object Response { get; set; }
|
||||
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public IDictionary<string, string> Headers { get; private set; }
|
||||
|
@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.Services
|
|||
|
||||
public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = RequestContext == null ? null : RequestContext.Response;
|
||||
var response = RequestContext?.Response;
|
||||
|
||||
if (this.Response is byte[] bytesResponse)
|
||||
{
|
||||
|
@ -45,13 +45,14 @@ namespace Emby.Server.Implementations.Services
|
|||
|
||||
if (response != null)
|
||||
{
|
||||
response.OriginalResponse.ContentLength = contentLength;
|
||||
response.ContentLength = contentLength;
|
||||
}
|
||||
|
||||
if (contentLength > 0)
|
||||
{
|
||||
await responseStream.WriteAsync(bytesResponse, 0, contentLength, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
|
@ -7,13 +6,14 @@ using System.Text;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using MediaBrowser.Model.Services;
|
||||
|
||||
namespace Emby.Server.Implementations.Services
|
||||
{
|
||||
public static class ResponseHelper
|
||||
{
|
||||
public static Task WriteToResponse(IResponse response, IRequest request, object result, CancellationToken cancellationToken)
|
||||
public static Task WriteToResponse(HttpResponse response, IRequest request, object result, CancellationToken cancellationToken)
|
||||
{
|
||||
if (result == null)
|
||||
{
|
||||
|
@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.Services
|
|||
response.StatusCode = (int)HttpStatusCode.NoContent;
|
||||
}
|
||||
|
||||
response.OriginalResponse.ContentLength = 0;
|
||||
response.ContentLength = 0;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,6 @@ namespace Emby.Server.Implementations.Services
|
|||
httpResult.RequestContext = request;
|
||||
|
||||
response.StatusCode = httpResult.Status;
|
||||
response.StatusDescription = httpResult.StatusCode.ToString();
|
||||
}
|
||||
|
||||
var responseOptions = result as IHasHeaders;
|
||||
|
@ -51,11 +50,11 @@ namespace Emby.Server.Implementations.Services
|
|||
{
|
||||
if (string.Equals(responseHeaders.Key, "Content-Length", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
response.OriginalResponse.ContentLength = long.Parse(responseHeaders.Value, CultureInfo.InvariantCulture);
|
||||
response.ContentLength = long.Parse(responseHeaders.Value, CultureInfo.InvariantCulture);
|
||||
continue;
|
||||
}
|
||||
|
||||
response.AddHeader(responseHeaders.Key, responseHeaders.Value);
|
||||
response.Headers.Add(responseHeaders.Key, responseHeaders.Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,31 +73,31 @@ namespace Emby.Server.Implementations.Services
|
|||
switch (result)
|
||||
{
|
||||
case IAsyncStreamWriter asyncStreamWriter:
|
||||
return asyncStreamWriter.WriteToAsync(response.OutputStream, cancellationToken);
|
||||
return asyncStreamWriter.WriteToAsync(response.Body, cancellationToken);
|
||||
case IStreamWriter streamWriter:
|
||||
streamWriter.WriteTo(response.OutputStream);
|
||||
streamWriter.WriteTo(response.Body);
|
||||
return Task.CompletedTask;
|
||||
case FileWriter fileWriter:
|
||||
return fileWriter.WriteToAsync(response, cancellationToken);
|
||||
case Stream stream:
|
||||
return CopyStream(stream, response.OutputStream);
|
||||
return CopyStream(stream, response.Body);
|
||||
case byte[] bytes:
|
||||
response.ContentType = "application/octet-stream";
|
||||
response.OriginalResponse.ContentLength = bytes.Length;
|
||||
response.ContentLength = bytes.Length;
|
||||
|
||||
if (bytes.Length > 0)
|
||||
{
|
||||
return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken);
|
||||
return response.Body.WriteAsync(bytes, 0, bytes.Length, cancellationToken);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
case string responseText:
|
||||
var responseTextAsBytes = Encoding.UTF8.GetBytes(responseText);
|
||||
response.OriginalResponse.ContentLength = responseTextAsBytes.Length;
|
||||
response.ContentLength = responseTextAsBytes.Length;
|
||||
|
||||
if (responseTextAsBytes.Length > 0)
|
||||
{
|
||||
return response.OutputStream.WriteAsync(responseTextAsBytes, 0, responseTextAsBytes.Length, cancellationToken);
|
||||
return response.Body.WriteAsync(responseTextAsBytes, 0, responseTextAsBytes.Length, cancellationToken);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
@ -115,7 +114,7 @@ namespace Emby.Server.Implementations.Services
|
|||
}
|
||||
}
|
||||
|
||||
public static async Task WriteObject(IRequest request, object result, IResponse response)
|
||||
public static async Task WriteObject(IRequest request, object result, HttpResponse response)
|
||||
{
|
||||
var contentType = request.ResponseContentType;
|
||||
var serializer = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType);
|
||||
|
@ -127,11 +126,11 @@ namespace Emby.Server.Implementations.Services
|
|||
ms.Position = 0;
|
||||
|
||||
var contentLength = ms.Length;
|
||||
response.OriginalResponse.ContentLength = contentLength;
|
||||
response.ContentLength = contentLength;
|
||||
|
||||
if (contentLength > 0)
|
||||
{
|
||||
await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false);
|
||||
await ms.CopyToAsync(response.Body).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -147,7 +147,6 @@ namespace Emby.Server.Implementations.Services
|
|||
|
||||
public Task<object> Execute(HttpListenerHost httpHost, object requestDto, IRequest req)
|
||||
{
|
||||
req.Dto = requestDto;
|
||||
var requestType = requestDto.GetType();
|
||||
req.OperationName = requestType.Name;
|
||||
|
||||
|
@ -161,9 +160,6 @@ namespace Emby.Server.Implementations.Services
|
|||
serviceRequiresContext.Request = req;
|
||||
}
|
||||
|
||||
if (req.Dto == null) // Don't override existing batched DTO[]
|
||||
req.Dto = requestDto;
|
||||
|
||||
//Executes the service and returns the result
|
||||
return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName());
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.Services
|
|||
foreach (var requestFilter in actionContext.RequestFilters)
|
||||
{
|
||||
requestFilter.RequestFilter(request, request.Response, requestDto);
|
||||
if (request.Response.OriginalResponse.HasStarted)
|
||||
if (request.Response.HasStarted)
|
||||
{
|
||||
Task.FromResult<object>(null);
|
||||
}
|
||||
|
|
|
@ -5,20 +5,21 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Services
|
||||
{
|
||||
public class ServiceHandler
|
||||
{
|
||||
public RestPath RestPath { get; }
|
||||
private RestPath _restPath;
|
||||
|
||||
public string ResponseContentType { get; }
|
||||
private string _responseContentType;
|
||||
|
||||
internal ServiceHandler(RestPath restPath, string responseContentType)
|
||||
{
|
||||
RestPath = restPath;
|
||||
ResponseContentType = responseContentType;
|
||||
_restPath = restPath;
|
||||
_responseContentType = responseContentType;
|
||||
}
|
||||
|
||||
protected static Task<object> CreateContentTypeRequest(HttpListenerHost host, IRequest httpReq, Type requestType, string contentType)
|
||||
|
@ -54,7 +55,7 @@ namespace Emby.Server.Implementations.Services
|
|||
|
||||
private static string GetFormatContentType(string format)
|
||||
{
|
||||
//built-in formats
|
||||
// built-in formats
|
||||
switch (format)
|
||||
{
|
||||
case "json": return "application/json";
|
||||
|
@ -63,16 +64,16 @@ namespace Emby.Server.Implementations.Services
|
|||
}
|
||||
}
|
||||
|
||||
public async Task ProcessRequestAsync(HttpListenerHost httpHost, IRequest httpReq, IResponse httpRes, ILogger logger, CancellationToken cancellationToken)
|
||||
public async Task ProcessRequestAsync(HttpListenerHost httpHost, IRequest httpReq, HttpResponse httpRes, ILogger logger, CancellationToken cancellationToken)
|
||||
{
|
||||
httpReq.Items["__route"] = RestPath;
|
||||
httpReq.Items["__route"] = _restPath;
|
||||
|
||||
if (ResponseContentType != null)
|
||||
if (_responseContentType != null)
|
||||
{
|
||||
httpReq.ResponseContentType = ResponseContentType;
|
||||
httpReq.ResponseContentType = _responseContentType;
|
||||
}
|
||||
|
||||
var request = httpReq.Dto = await CreateRequest(httpHost, httpReq, RestPath, logger).ConfigureAwait(false);
|
||||
var request = await CreateRequest(httpHost, httpReq, _restPath, logger).ConfigureAwait(false);
|
||||
|
||||
httpHost.ApplyRequestFilters(httpReq, httpRes, request);
|
||||
|
||||
|
@ -94,7 +95,7 @@ namespace Emby.Server.Implementations.Services
|
|||
if (RequireqRequestStream(requestType))
|
||||
{
|
||||
// Used by IRequiresRequestStream
|
||||
var requestParams = await GetRequestParams(httpReq).ConfigureAwait(false);
|
||||
var requestParams = GetRequestParams(httpReq.Response.HttpContext.Request);
|
||||
var request = ServiceHandler.CreateRequest(httpReq, restPath, requestParams, host.CreateInstance(requestType));
|
||||
|
||||
var rawReq = (IRequiresRequestStream)request;
|
||||
|
@ -103,7 +104,7 @@ namespace Emby.Server.Implementations.Services
|
|||
}
|
||||
else
|
||||
{
|
||||
var requestParams = await GetFlattenedRequestParams(httpReq).ConfigureAwait(false);
|
||||
var requestParams = GetFlattenedRequestParams(httpReq.Response.HttpContext.Request);
|
||||
|
||||
var requestDto = await CreateContentTypeRequest(host, httpReq, restPath.RequestType, httpReq.ContentType).ConfigureAwait(false);
|
||||
|
||||
|
@ -121,7 +122,7 @@ namespace Emby.Server.Implementations.Services
|
|||
public static object CreateRequest(IRequest httpReq, RestPath restPath, Dictionary<string, string> requestParams, object requestDto)
|
||||
{
|
||||
var pathInfo = !restPath.IsWildCardPath
|
||||
? GetSanitizedPathInfo(httpReq.PathInfo, out string contentType)
|
||||
? GetSanitizedPathInfo(httpReq.PathInfo, out _)
|
||||
: httpReq.PathInfo;
|
||||
|
||||
return restPath.CreateRequest(pathInfo, requestParams, requestDto);
|
||||
|
@ -130,56 +131,41 @@ namespace Emby.Server.Implementations.Services
|
|||
/// <summary>
|
||||
/// Duplicate Params are given a unique key by appending a #1 suffix
|
||||
/// </summary>
|
||||
private static async Task<Dictionary<string, string>> GetRequestParams(IRequest request)
|
||||
private static Dictionary<string, string> GetRequestParams(HttpRequest request)
|
||||
{
|
||||
var map = new Dictionary<string, string>();
|
||||
|
||||
foreach (var name in request.QueryString.Keys)
|
||||
foreach (var pair in request.Query)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
// thank you ASP.NET
|
||||
continue;
|
||||
}
|
||||
|
||||
var values = request.QueryString[name];
|
||||
var values = pair.Value;
|
||||
if (values.Count == 1)
|
||||
{
|
||||
map[name] = values[0];
|
||||
map[pair.Key] = values[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < values.Count; i++)
|
||||
{
|
||||
map[name + (i == 0 ? "" : "#" + i)] = values[i];
|
||||
map[pair.Key + (i == 0 ? string.Empty : "#" + i)] = values[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT")))
|
||||
if ((IsMethod(request.Method, "POST") || IsMethod(request.Method, "PUT"))
|
||||
&& request.HasFormContentType)
|
||||
{
|
||||
var formData = await request.GetFormData().ConfigureAwait(false);
|
||||
if (formData != null)
|
||||
foreach (var pair in request.Form)
|
||||
{
|
||||
foreach (var name in formData.Keys)
|
||||
var values = pair.Value;
|
||||
if (values.Count == 1)
|
||||
{
|
||||
if (name == null)
|
||||
map[pair.Key] = values[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < values.Count; i++)
|
||||
{
|
||||
// thank you ASP.NET
|
||||
continue;
|
||||
}
|
||||
|
||||
var values = formData.GetValues(name);
|
||||
if (values.Count == 1)
|
||||
{
|
||||
map[name] = values[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < values.Count; i++)
|
||||
{
|
||||
map[name + (i == 0 ? "" : "#" + i)] = values[i];
|
||||
}
|
||||
map[pair.Key + (i == 0 ? string.Empty : "#" + i)] = values[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -189,43 +175,26 @@ namespace Emby.Server.Implementations.Services
|
|||
}
|
||||
|
||||
private static bool IsMethod(string method, string expected)
|
||||
{
|
||||
return string.Equals(method, expected, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
=> string.Equals(method, expected, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Duplicate params have their values joined together in a comma-delimited string
|
||||
/// </summary>
|
||||
private static async Task<Dictionary<string, string>> GetFlattenedRequestParams(IRequest request)
|
||||
private static Dictionary<string, string> GetFlattenedRequestParams(HttpRequest request)
|
||||
{
|
||||
var map = new Dictionary<string, string>();
|
||||
|
||||
foreach (var name in request.QueryString.Keys)
|
||||
foreach (var pair in request.Query)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
// thank you ASP.NET
|
||||
continue;
|
||||
}
|
||||
|
||||
map[name] = request.QueryString[name];
|
||||
map[pair.Key] = pair.Value;
|
||||
}
|
||||
|
||||
if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT")))
|
||||
if ((IsMethod(request.Method, "POST") || IsMethod(request.Method, "PUT"))
|
||||
&& request.HasFormContentType)
|
||||
{
|
||||
var formData = await request.GetFormData().ConfigureAwait(false);
|
||||
if (formData != null)
|
||||
foreach (var pair in request.Form)
|
||||
{
|
||||
foreach (var name in formData.Keys)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
// thank you ASP.NET
|
||||
continue;
|
||||
}
|
||||
|
||||
map[name] = formData[name];
|
||||
}
|
||||
map[pair.Key] = pair.Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.Session
|
|||
{
|
||||
var dict = new Dictionary<string, string>();
|
||||
|
||||
dict["ItemIds"] = string.Join(",", command.ItemIds.Select(i => i.ToString("N")).ToArray());
|
||||
dict["ItemIds"] = string.Join(",", command.ItemIds.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray());
|
||||
|
||||
if (command.StartPositionTicks.HasValue)
|
||||
{
|
||||
|
|
|
@ -327,7 +327,7 @@ namespace Emby.Server.Implementations.Session
|
|||
{
|
||||
if (string.IsNullOrEmpty(info.MediaSourceId))
|
||||
{
|
||||
info.MediaSourceId = info.ItemId.ToString("N");
|
||||
info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (!info.ItemId.Equals(Guid.Empty) && info.Item == null && libraryItem != null)
|
||||
|
@ -463,7 +463,7 @@ namespace Emby.Server.Implementations.Session
|
|||
Client = appName,
|
||||
DeviceId = deviceId,
|
||||
ApplicationVersion = appVersion,
|
||||
Id = key.GetMD5().ToString("N"),
|
||||
Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
||||
ServerId = _appHost.SystemId
|
||||
};
|
||||
|
||||
|
@ -845,7 +845,7 @@ namespace Emby.Server.Implementations.Session
|
|||
// Normalize
|
||||
if (string.IsNullOrEmpty(info.MediaSourceId))
|
||||
{
|
||||
info.MediaSourceId = info.ItemId.ToString("N");
|
||||
info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (!info.ItemId.Equals(Guid.Empty) && info.Item == null && libraryItem != null)
|
||||
|
@ -1029,7 +1029,7 @@ namespace Emby.Server.Implementations.Session
|
|||
private static async Task SendMessageToSession<T>(SessionInfo session, string name, T data, CancellationToken cancellationToken)
|
||||
{
|
||||
var controllers = session.SessionControllers.ToArray();
|
||||
var messageId = Guid.NewGuid().ToString("N");
|
||||
var messageId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
foreach (var controller in controllers)
|
||||
{
|
||||
|
@ -1041,7 +1041,7 @@ namespace Emby.Server.Implementations.Session
|
|||
{
|
||||
IEnumerable<Task> GetTasks()
|
||||
{
|
||||
var messageId = Guid.NewGuid().ToString("N");
|
||||
var messageId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
foreach (var session in sessions)
|
||||
{
|
||||
var controllers = session.SessionControllers;
|
||||
|
@ -1234,7 +1234,7 @@ namespace Emby.Server.Implementations.Session
|
|||
AssertCanControl(session, controllingSession);
|
||||
if (!controllingSession.UserId.Equals(Guid.Empty))
|
||||
{
|
||||
command.ControllingUserId = controllingSession.UserId.ToString("N");
|
||||
command.ControllingUserId = controllingSession.UserId.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1484,7 +1484,7 @@ namespace Emby.Server.Implementations.Session
|
|||
DeviceId = deviceId,
|
||||
DeviceName = deviceName,
|
||||
UserId = user.Id,
|
||||
AccessToken = Guid.NewGuid().ToString("N"),
|
||||
AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture),
|
||||
UserName = user.Name
|
||||
};
|
||||
|
||||
|
@ -1822,6 +1822,7 @@ namespace Emby.Server.Implementations.Session
|
|||
CheckDisposed();
|
||||
|
||||
var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return SendMessageToSessions(sessions, name, data, cancellationToken);
|
||||
}
|
||||
|
||||
|
@ -1831,6 +1832,7 @@ namespace Emby.Server.Implementations.Session
|
|||
|
||||
var sessions = Sessions
|
||||
.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i));
|
||||
|
||||
return SendMessageToSessions(sessions, name, data, cancellationToken);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,647 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Emby.Server.Implementations.SocketSharp
|
||||
{
|
||||
public partial class WebSocketSharpRequest : IHttpRequest
|
||||
{
|
||||
internal static string GetParameter(ReadOnlySpan<char> header, string attr)
|
||||
{
|
||||
int ap = header.IndexOf(attr.AsSpan(), StringComparison.Ordinal);
|
||||
if (ap == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ap += attr.Length;
|
||||
if (ap >= header.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
char ending = header[ap];
|
||||
if (ending != '"')
|
||||
{
|
||||
ending = ' ';
|
||||
}
|
||||
|
||||
var slice = header.Slice(ap + 1);
|
||||
int end = slice.IndexOf(ending);
|
||||
if (end == -1)
|
||||
{
|
||||
return ending == '"' ? null : header.Slice(ap).ToString();
|
||||
}
|
||||
|
||||
return slice.Slice(0, end - ap - 1).ToString();
|
||||
}
|
||||
|
||||
private async Task LoadMultiPart(WebROCollection form)
|
||||
{
|
||||
string boundary = GetParameter(ContentType.AsSpan(), "; boundary=");
|
||||
if (boundary == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (var requestStream = InputStream)
|
||||
{
|
||||
// DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request
|
||||
// Not ending with \r\n?
|
||||
var ms = new MemoryStream(32 * 1024);
|
||||
await requestStream.CopyToAsync(ms).ConfigureAwait(false);
|
||||
|
||||
var input = ms;
|
||||
ms.WriteByte((byte)'\r');
|
||||
ms.WriteByte((byte)'\n');
|
||||
|
||||
input.Position = 0;
|
||||
|
||||
// Uncomment to debug
|
||||
// var content = new StreamReader(ms).ReadToEnd();
|
||||
// Console.WriteLine(boundary + "::" + content);
|
||||
// input.Position = 0;
|
||||
|
||||
var multi_part = new HttpMultipart(input, boundary, ContentEncoding);
|
||||
|
||||
HttpMultipart.Element e;
|
||||
while ((e = multi_part.ReadNextElement()) != null)
|
||||
{
|
||||
if (e.Filename == null)
|
||||
{
|
||||
byte[] copy = new byte[e.Length];
|
||||
|
||||
input.Position = e.Start;
|
||||
await input.ReadAsync(copy, 0, (int)e.Length).ConfigureAwait(false);
|
||||
|
||||
form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy, 0, copy.Length));
|
||||
}
|
||||
else
|
||||
{
|
||||
// We use a substream, as in 2.x we will support large uploads streamed to disk,
|
||||
files[e.Name] = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<QueryParamCollection> GetFormData()
|
||||
{
|
||||
var form = new WebROCollection();
|
||||
files = new Dictionary<string, HttpPostedFile>();
|
||||
|
||||
if (IsContentType("multipart/form-data"))
|
||||
{
|
||||
await LoadMultiPart(form).ConfigureAwait(false);
|
||||
}
|
||||
else if (IsContentType("application/x-www-form-urlencoded"))
|
||||
{
|
||||
await LoadWwwForm(form).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (validate_form && !checked_form)
|
||||
{
|
||||
checked_form = true;
|
||||
ValidateNameValueCollection("Form", form);
|
||||
}
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
public string Accept => StringValues.IsNullOrEmpty(request.Headers[HeaderNames.Accept]) ? null : request.Headers[HeaderNames.Accept].ToString();
|
||||
|
||||
public string Authorization => StringValues.IsNullOrEmpty(request.Headers[HeaderNames.Authorization]) ? null : request.Headers[HeaderNames.Authorization].ToString();
|
||||
|
||||
protected bool validate_form { get; set; }
|
||||
protected bool checked_form { get; set; }
|
||||
|
||||
private static void ThrowValidationException(string name, string key, string value)
|
||||
{
|
||||
string v = "\"" + value + "\"";
|
||||
if (v.Length > 20)
|
||||
{
|
||||
v = v.Substring(0, 16) + "...\"";
|
||||
}
|
||||
|
||||
string msg = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"A potentially dangerous Request.{0} value was detected from the client ({1}={2}).",
|
||||
name,
|
||||
key,
|
||||
v);
|
||||
|
||||
throw new Exception(msg);
|
||||
}
|
||||
|
||||
private static void ValidateNameValueCollection(string name, QueryParamCollection coll)
|
||||
{
|
||||
if (coll == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var pair in coll)
|
||||
{
|
||||
var key = pair.Name;
|
||||
var val = pair.Value;
|
||||
if (val != null && val.Length > 0 && IsInvalidString(val))
|
||||
{
|
||||
ThrowValidationException(name, key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsInvalidString(string val)
|
||||
=> IsInvalidString(val, out var validationFailureIndex);
|
||||
|
||||
internal static bool IsInvalidString(string val, out int validationFailureIndex)
|
||||
{
|
||||
validationFailureIndex = 0;
|
||||
|
||||
int len = val.Length;
|
||||
if (len < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
char current = val[0];
|
||||
for (int idx = 1; idx < len; idx++)
|
||||
{
|
||||
char next = val[idx];
|
||||
|
||||
// See http://secunia.com/advisories/14325
|
||||
if (current == '<' || current == '\xff1c')
|
||||
{
|
||||
if (next == '!' || next < ' '
|
||||
|| (next >= 'a' && next <= 'z')
|
||||
|| (next >= 'A' && next <= 'Z'))
|
||||
{
|
||||
validationFailureIndex = idx - 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (current == '&' && next == '#')
|
||||
{
|
||||
validationFailureIndex = idx - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
current = next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsContentType(string ct)
|
||||
{
|
||||
if (ContentType == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ContentType.StartsWith(ct, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private async Task LoadWwwForm(WebROCollection form)
|
||||
{
|
||||
using (var input = InputStream)
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
await input.CopyToAsync(ms).ConfigureAwait(false);
|
||||
ms.Position = 0;
|
||||
|
||||
using (var s = new StreamReader(ms, ContentEncoding))
|
||||
{
|
||||
var key = new StringBuilder();
|
||||
var value = new StringBuilder();
|
||||
int c;
|
||||
|
||||
while ((c = s.Read()) != -1)
|
||||
{
|
||||
if (c == '=')
|
||||
{
|
||||
value.Length = 0;
|
||||
while ((c = s.Read()) != -1)
|
||||
{
|
||||
if (c == '&')
|
||||
{
|
||||
AddRawKeyValue(form, key, value);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
value.Append((char)c);
|
||||
}
|
||||
}
|
||||
|
||||
if (c == -1)
|
||||
{
|
||||
AddRawKeyValue(form, key, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (c == '&')
|
||||
{
|
||||
AddRawKeyValue(form, key, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
key.Append((char)c);
|
||||
}
|
||||
}
|
||||
|
||||
if (c == -1)
|
||||
{
|
||||
AddRawKeyValue(form, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddRawKeyValue(WebROCollection form, StringBuilder key, StringBuilder value)
|
||||
{
|
||||
form.Add(WebUtility.UrlDecode(key.ToString()), WebUtility.UrlDecode(value.ToString()));
|
||||
|
||||
key.Length = 0;
|
||||
value.Length = 0;
|
||||
}
|
||||
|
||||
private Dictionary<string, HttpPostedFile> files;
|
||||
|
||||
private class WebROCollection : QueryParamCollection
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
foreach (var pair in this)
|
||||
{
|
||||
if (result.Length > 0)
|
||||
{
|
||||
result.Append('&');
|
||||
}
|
||||
|
||||
var key = pair.Name;
|
||||
if (key != null && key.Length > 0)
|
||||
{
|
||||
result.Append(key);
|
||||
result.Append('=');
|
||||
}
|
||||
|
||||
result.Append(pair.Value);
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
}
|
||||
private class HttpMultipart
|
||||
{
|
||||
|
||||
public class Element
|
||||
{
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Filename { get; set; }
|
||||
|
||||
public Encoding Encoding { get; set; }
|
||||
|
||||
public long Start { get; set; }
|
||||
|
||||
public long Length { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " +
|
||||
Start.ToString(CultureInfo.CurrentCulture) + ", Length " + Length.ToString(CultureInfo.CurrentCulture);
|
||||
}
|
||||
}
|
||||
|
||||
private const byte LF = (byte)'\n';
|
||||
|
||||
private const byte CR = (byte)'\r';
|
||||
|
||||
private Stream data;
|
||||
|
||||
private string boundary;
|
||||
|
||||
private byte[] boundaryBytes;
|
||||
|
||||
private byte[] buffer;
|
||||
|
||||
private bool atEof;
|
||||
|
||||
private Encoding encoding;
|
||||
|
||||
private StringBuilder sb;
|
||||
|
||||
// See RFC 2046
|
||||
// In the case of multipart entities, in which one or more different
|
||||
// sets of data are combined in a single body, a "multipart" media type
|
||||
// field must appear in the entity's header. The body must then contain
|
||||
// one or more body parts, each preceded by a boundary delimiter line,
|
||||
// and the last one followed by a closing boundary delimiter line.
|
||||
// After its boundary delimiter line, each body part then consists of a
|
||||
// header area, a blank line, and a body area. Thus a body part is
|
||||
// similar to an RFC 822 message in syntax, but different in meaning.
|
||||
|
||||
public HttpMultipart(Stream data, string b, Encoding encoding)
|
||||
{
|
||||
this.data = data;
|
||||
boundary = b;
|
||||
boundaryBytes = encoding.GetBytes(b);
|
||||
buffer = new byte[boundaryBytes.Length + 2]; // CRLF or '--'
|
||||
this.encoding = encoding;
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
|
||||
public Element ReadNextElement()
|
||||
{
|
||||
if (atEof || ReadBoundary())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var elem = new Element();
|
||||
ReadOnlySpan<char> header;
|
||||
while ((header = ReadLine().AsSpan()).Length != 0)
|
||||
{
|
||||
if (header.StartsWith("Content-Disposition:".AsSpan(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
elem.Name = GetContentDispositionAttribute(header, "name");
|
||||
elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
|
||||
}
|
||||
else if (header.StartsWith("Content-Type:".AsSpan(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
elem.ContentType = header.Slice("Content-Type:".Length).Trim().ToString();
|
||||
elem.Encoding = GetEncoding(elem.ContentType);
|
||||
}
|
||||
}
|
||||
|
||||
long start = data.Position;
|
||||
elem.Start = start;
|
||||
long pos = MoveToNextBoundary();
|
||||
if (pos == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
elem.Length = pos - start;
|
||||
return elem;
|
||||
}
|
||||
|
||||
private string ReadLine()
|
||||
{
|
||||
// CRLF or LF are ok as line endings.
|
||||
bool got_cr = false;
|
||||
int b = 0;
|
||||
sb.Length = 0;
|
||||
while (true)
|
||||
{
|
||||
b = data.ReadByte();
|
||||
if (b == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (b == LF)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
got_cr = b == CR;
|
||||
sb.Append((char)b);
|
||||
}
|
||||
|
||||
if (got_cr)
|
||||
{
|
||||
sb.Length--;
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string GetContentDispositionAttribute(ReadOnlySpan<char> l, string name)
|
||||
{
|
||||
int idx = l.IndexOf((name + "=\"").AsSpan(), StringComparison.Ordinal);
|
||||
if (idx < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int begin = idx + name.Length + "=\"".Length;
|
||||
int end = l.Slice(begin).IndexOf('"');
|
||||
if (end < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (begin == end)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return l.Slice(begin, end - begin).ToString();
|
||||
}
|
||||
|
||||
private string GetContentDispositionAttributeWithEncoding(ReadOnlySpan<char> l, string name)
|
||||
{
|
||||
int idx = l.IndexOf((name + "=\"").AsSpan(), StringComparison.Ordinal);
|
||||
if (idx < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int begin = idx + name.Length + "=\"".Length;
|
||||
int end = l.Slice(begin).IndexOf('"');
|
||||
if (end < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (begin == end)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
ReadOnlySpan<char> temp = l.Slice(begin, end - begin);
|
||||
byte[] source = new byte[temp.Length];
|
||||
for (int i = temp.Length - 1; i >= 0; i--)
|
||||
{
|
||||
source[i] = (byte)temp[i];
|
||||
}
|
||||
|
||||
return encoding.GetString(source, 0, source.Length);
|
||||
}
|
||||
|
||||
private bool ReadBoundary()
|
||||
{
|
||||
try
|
||||
{
|
||||
string line;
|
||||
do
|
||||
{
|
||||
line = ReadLine();
|
||||
}
|
||||
while (line.Length == 0);
|
||||
|
||||
if (line[0] != '-' || line[1] != '-')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!line.EndsWith(boundary, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool CompareBytes(byte[] orig, byte[] other)
|
||||
{
|
||||
for (int i = orig.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (orig[i] != other[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private long MoveToNextBoundary()
|
||||
{
|
||||
long retval = 0;
|
||||
bool got_cr = false;
|
||||
|
||||
int state = 0;
|
||||
int c = data.ReadByte();
|
||||
while (true)
|
||||
{
|
||||
if (c == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (state == 0 && c == LF)
|
||||
{
|
||||
retval = data.Position - 1;
|
||||
if (got_cr)
|
||||
{
|
||||
retval--;
|
||||
}
|
||||
|
||||
state = 1;
|
||||
c = data.ReadByte();
|
||||
}
|
||||
else if (state == 0)
|
||||
{
|
||||
got_cr = c == CR;
|
||||
c = data.ReadByte();
|
||||
}
|
||||
else if (state == 1 && c == '-')
|
||||
{
|
||||
c = data.ReadByte();
|
||||
if (c == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (c != '-')
|
||||
{
|
||||
state = 0;
|
||||
got_cr = false;
|
||||
continue; // no ReadByte() here
|
||||
}
|
||||
|
||||
int nread = data.Read(buffer, 0, buffer.Length);
|
||||
int bl = buffer.Length;
|
||||
if (nread != bl)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!CompareBytes(boundaryBytes, buffer))
|
||||
{
|
||||
state = 0;
|
||||
data.Position = retval + 2;
|
||||
if (got_cr)
|
||||
{
|
||||
data.Position++;
|
||||
got_cr = false;
|
||||
}
|
||||
|
||||
c = data.ReadByte();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-')
|
||||
{
|
||||
atEof = true;
|
||||
}
|
||||
else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF)
|
||||
{
|
||||
state = 0;
|
||||
data.Position = retval + 2;
|
||||
if (got_cr)
|
||||
{
|
||||
data.Position++;
|
||||
got_cr = false;
|
||||
}
|
||||
|
||||
c = data.ReadByte();
|
||||
continue;
|
||||
}
|
||||
|
||||
data.Position = retval + 2;
|
||||
if (got_cr)
|
||||
{
|
||||
data.Position++;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// state == 1
|
||||
state = 0; // no ReadByte() here
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
private static string StripPath(string path)
|
||||
{
|
||||
if (path == null || path.Length == 0)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
if (path.IndexOf(":\\", StringComparison.Ordinal) != 1
|
||||
&& !path.StartsWith("\\\\", StringComparison.Ordinal))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
return path.Substring(path.LastIndexOf('\\') + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +1,56 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using IHttpFile = MediaBrowser.Model.Services.IHttpFile;
|
||||
using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest;
|
||||
using IResponse = MediaBrowser.Model.Services.IResponse;
|
||||
|
||||
namespace Emby.Server.Implementations.SocketSharp
|
||||
{
|
||||
public partial class WebSocketSharpRequest : IHttpRequest
|
||||
{
|
||||
private readonly HttpRequest request;
|
||||
public const string FormUrlEncoded = "application/x-www-form-urlencoded";
|
||||
public const string MultiPartFormData = "multipart/form-data";
|
||||
public const string Soap11 = "text/xml; charset=utf-8";
|
||||
|
||||
public WebSocketSharpRequest(HttpRequest httpContext, HttpResponse response, string operationName, ILogger logger)
|
||||
private string _remoteIp;
|
||||
private Dictionary<string, object> _items;
|
||||
private string _responseContentType;
|
||||
|
||||
public WebSocketSharpRequest(HttpRequest httpRequest, HttpResponse httpResponse, string operationName, ILogger logger)
|
||||
{
|
||||
this.OperationName = operationName;
|
||||
this.request = httpContext;
|
||||
this.Response = new WebSocketSharpResponse(logger, response);
|
||||
this.Request = httpRequest;
|
||||
this.Response = httpResponse;
|
||||
}
|
||||
|
||||
public HttpRequest HttpRequest => request;
|
||||
public string Accept => StringValues.IsNullOrEmpty(Request.Headers[HeaderNames.Accept]) ? null : Request.Headers[HeaderNames.Accept].ToString();
|
||||
|
||||
public IResponse Response { get; }
|
||||
public string Authorization => StringValues.IsNullOrEmpty(Request.Headers[HeaderNames.Authorization]) ? null : Request.Headers[HeaderNames.Authorization].ToString();
|
||||
|
||||
public HttpRequest Request { get; }
|
||||
|
||||
public HttpResponse Response { get; }
|
||||
|
||||
public string OperationName { get; set; }
|
||||
|
||||
public object Dto { get; set; }
|
||||
public string RawUrl => Request.GetEncodedPathAndQuery();
|
||||
|
||||
public string RawUrl => request.GetEncodedPathAndQuery();
|
||||
public string AbsoluteUri => Request.GetDisplayUrl().TrimEnd('/');
|
||||
|
||||
public string AbsoluteUri => request.GetDisplayUrl().TrimEnd('/');
|
||||
// Header[name] returns "" when undefined
|
||||
|
||||
private string GetHeader(string name) => request.Headers[name].ToString();
|
||||
|
||||
private string remoteIp;
|
||||
public string RemoteIp
|
||||
{
|
||||
get
|
||||
{
|
||||
if (remoteIp != null)
|
||||
if (_remoteIp != null)
|
||||
{
|
||||
return remoteIp;
|
||||
return _remoteIp;
|
||||
}
|
||||
|
||||
IPAddress ip;
|
||||
|
@ -62,14 +61,51 @@ namespace Emby.Server.Implementations.SocketSharp
|
|||
{
|
||||
if (!IPAddress.TryParse(GetHeader(CustomHeaderNames.XRealIP), out ip))
|
||||
{
|
||||
ip = request.HttpContext.Connection.RemoteIpAddress;
|
||||
ip = Request.HttpContext.Connection.RemoteIpAddress;
|
||||
}
|
||||
}
|
||||
|
||||
return remoteIp = NormalizeIp(ip).ToString();
|
||||
return _remoteIp = NormalizeIp(ip).ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public string[] AcceptTypes => Request.Headers.GetCommaSeparatedValues(HeaderNames.Accept);
|
||||
|
||||
public Dictionary<string, object> Items => _items ?? (_items = new Dictionary<string, object>());
|
||||
|
||||
public string ResponseContentType
|
||||
{
|
||||
get =>
|
||||
_responseContentType
|
||||
?? (_responseContentType = GetResponseContentType(Request));
|
||||
set => this._responseContentType = value;
|
||||
}
|
||||
|
||||
public string PathInfo => Request.Path.Value;
|
||||
|
||||
public string UserAgent => Request.Headers[HeaderNames.UserAgent];
|
||||
|
||||
public IHeaderDictionary Headers => Request.Headers;
|
||||
|
||||
public IQueryCollection QueryString => Request.Query;
|
||||
|
||||
public bool IsLocal => Request.HttpContext.Connection.LocalIpAddress.Equals(Request.HttpContext.Connection.RemoteIpAddress);
|
||||
|
||||
|
||||
public string HttpMethod => Request.Method;
|
||||
|
||||
public string Verb => HttpMethod;
|
||||
|
||||
public string ContentType => Request.ContentType;
|
||||
|
||||
public Uri UrlReferrer => Request.GetTypedHeaders().Referer;
|
||||
|
||||
public Stream InputStream => Request.Body;
|
||||
|
||||
public long ContentLength => Request.ContentLength ?? 0;
|
||||
|
||||
private string GetHeader(string name) => Request.Headers[name].ToString();
|
||||
|
||||
private static IPAddress NormalizeIp(IPAddress ip)
|
||||
{
|
||||
if (ip.IsIPv4MappedToIPv6)
|
||||
|
@ -80,22 +116,6 @@ namespace Emby.Server.Implementations.SocketSharp
|
|||
return ip;
|
||||
}
|
||||
|
||||
public string[] AcceptTypes => request.Headers.GetCommaSeparatedValues(HeaderNames.Accept);
|
||||
|
||||
private Dictionary<string, object> items;
|
||||
public Dictionary<string, object> Items => items ?? (items = new Dictionary<string, object>());
|
||||
|
||||
private string responseContentType;
|
||||
public string ResponseContentType
|
||||
{
|
||||
get =>
|
||||
responseContentType
|
||||
?? (responseContentType = GetResponseContentType(HttpRequest));
|
||||
set => this.responseContentType = value;
|
||||
}
|
||||
|
||||
public const string FormUrlEncoded = "application/x-www-form-urlencoded";
|
||||
public const string MultiPartFormData = "multipart/form-data";
|
||||
public static string GetResponseContentType(HttpRequest httpReq)
|
||||
{
|
||||
var specifiedContentType = GetQueryStringContentType(httpReq);
|
||||
|
@ -152,8 +172,6 @@ namespace Emby.Server.Implementations.SocketSharp
|
|||
return serverDefaultContentType;
|
||||
}
|
||||
|
||||
public const string Soap11 = "text/xml; charset=utf-8";
|
||||
|
||||
public static bool HasAnyOfContentTypes(HttpRequest request, params string[] contentTypes)
|
||||
{
|
||||
if (contentTypes == null || request.ContentType == null)
|
||||
|
@ -224,105 +242,5 @@ namespace Emby.Server.Implementations.SocketSharp
|
|||
var pos = strVal.IndexOf(needle);
|
||||
return pos == -1 ? strVal : strVal.Slice(0, pos);
|
||||
}
|
||||
|
||||
public string PathInfo => this.request.Path.Value;
|
||||
|
||||
public string UserAgent => request.Headers[HeaderNames.UserAgent];
|
||||
|
||||
public IHeaderDictionary Headers => request.Headers;
|
||||
|
||||
public IQueryCollection QueryString => request.Query;
|
||||
|
||||
public bool IsLocal => string.Equals(request.HttpContext.Connection.LocalIpAddress.ToString(), request.HttpContext.Connection.RemoteIpAddress.ToString());
|
||||
|
||||
private string httpMethod;
|
||||
public string HttpMethod =>
|
||||
httpMethod
|
||||
?? (httpMethod = request.Method);
|
||||
|
||||
public string Verb => HttpMethod;
|
||||
|
||||
public string ContentType => request.ContentType;
|
||||
|
||||
private Encoding ContentEncoding
|
||||
{
|
||||
get
|
||||
{
|
||||
// TODO is this necessary?
|
||||
if (UserAgent != null && CultureInfo.InvariantCulture.CompareInfo.IsPrefix(UserAgent, "UP"))
|
||||
{
|
||||
string postDataCharset = Headers["x-up-devcap-post-charset"];
|
||||
if (!string.IsNullOrEmpty(postDataCharset))
|
||||
{
|
||||
try
|
||||
{
|
||||
return Encoding.GetEncoding(postDataCharset);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return request.GetTypedHeaders().ContentType.Encoding ?? Encoding.UTF8;
|
||||
}
|
||||
}
|
||||
|
||||
public Uri UrlReferrer => request.GetTypedHeaders().Referer;
|
||||
|
||||
public static Encoding GetEncoding(string contentTypeHeader)
|
||||
{
|
||||
var param = GetParameter(contentTypeHeader.AsSpan(), "charset=");
|
||||
if (param == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Encoding.GetEncoding(param);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream InputStream => request.Body;
|
||||
|
||||
public long ContentLength => request.ContentLength ?? 0;
|
||||
|
||||
private IHttpFile[] httpFiles;
|
||||
public IHttpFile[] Files
|
||||
{
|
||||
get
|
||||
{
|
||||
if (httpFiles != null)
|
||||
{
|
||||
return httpFiles;
|
||||
}
|
||||
|
||||
if (files == null)
|
||||
{
|
||||
return httpFiles = Array.Empty<IHttpFile>();
|
||||
}
|
||||
|
||||
var values = files.Values;
|
||||
httpFiles = new IHttpFile[values.Count];
|
||||
for (int i = 0; i < values.Count; i++)
|
||||
{
|
||||
var reqFile = values.ElementAt(i);
|
||||
httpFiles[i] = new HttpFile
|
||||
{
|
||||
ContentType = reqFile.ContentType,
|
||||
ContentLength = reqFile.ContentLength,
|
||||
FileName = reqFile.FileName,
|
||||
InputStream = reqFile.InputStream,
|
||||
};
|
||||
}
|
||||
|
||||
return httpFiles;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using IRequest = MediaBrowser.Model.Services.IRequest;
|
||||
|
||||
namespace Emby.Server.Implementations.SocketSharp
|
||||
{
|
||||
public class WebSocketSharpResponse : IResponse
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public WebSocketSharpResponse(ILogger logger, HttpResponse response)
|
||||
{
|
||||
_logger = logger;
|
||||
OriginalResponse = response;
|
||||
}
|
||||
|
||||
public HttpResponse OriginalResponse { get; }
|
||||
|
||||
public int StatusCode
|
||||
{
|
||||
get => OriginalResponse.StatusCode;
|
||||
set => OriginalResponse.StatusCode = value;
|
||||
}
|
||||
|
||||
public string StatusDescription { get; set; }
|
||||
|
||||
public string ContentType
|
||||
{
|
||||
get => OriginalResponse.ContentType;
|
||||
set => OriginalResponse.ContentType = value;
|
||||
}
|
||||
|
||||
public void AddHeader(string name, string value)
|
||||
{
|
||||
if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ContentType = value;
|
||||
return;
|
||||
}
|
||||
|
||||
OriginalResponse.Headers.Add(name, value);
|
||||
}
|
||||
|
||||
public void Redirect(string url)
|
||||
{
|
||||
OriginalResponse.Redirect(url);
|
||||
}
|
||||
|
||||
public Stream OutputStream => OriginalResponse.Body;
|
||||
|
||||
public bool SendChunked { get; set; }
|
||||
|
||||
const int StreamCopyToBufferSize = 81920;
|
||||
public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken)
|
||||
{
|
||||
var allowAsync = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
|
||||
//if (count <= 0)
|
||||
//{
|
||||
// allowAsync = true;
|
||||
//}
|
||||
|
||||
var fileOpenOptions = FileOpenOptions.SequentialScan;
|
||||
|
||||
if (allowAsync)
|
||||
{
|
||||
fileOpenOptions |= FileOpenOptions.Asynchronous;
|
||||
}
|
||||
|
||||
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
|
||||
|
||||
using (var fs = fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions))
|
||||
{
|
||||
if (offset > 0)
|
||||
{
|
||||
fs.Position = offset;
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
await streamHelper.CopyToAsync(fs, OutputStream, count, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await fs.CopyToAsync(OutputStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
|
@ -73,7 +74,7 @@ namespace Emby.Server.Implementations.TV
|
|||
{
|
||||
parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
||||
.Where(i => i is Folder)
|
||||
.Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N")))
|
||||
.Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture)))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -25,7 +26,7 @@ namespace Emby.Server.Implementations.Udp
|
|||
|
||||
private bool _isDisposed;
|
||||
|
||||
private readonly List<Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task>>> _responders = new List<Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task>>>();
|
||||
private readonly List<Tuple<string, bool, Func<string, IPEndPoint, Encoding, CancellationToken, Task>>> _responders = new List<Tuple<string, bool, Func<string, IPEndPoint, Encoding, CancellationToken, Task>>>();
|
||||
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IJsonSerializer _json;
|
||||
|
@ -43,9 +44,9 @@ namespace Emby.Server.Implementations.Udp
|
|||
AddMessageResponder("who is JellyfinServer?", true, RespondToV2Message);
|
||||
}
|
||||
|
||||
private void AddMessageResponder(string message, bool isSubstring, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task> responder)
|
||||
private void AddMessageResponder(string message, bool isSubstring, Func<string, IPEndPoint, Encoding, CancellationToken, Task> responder)
|
||||
{
|
||||
_responders.Add(new Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task>>(message, isSubstring, responder));
|
||||
_responders.Add(new Tuple<string, bool, Func<string, IPEndPoint, Encoding, CancellationToken, Task>>(message, isSubstring, responder));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -83,7 +84,7 @@ namespace Emby.Server.Implementations.Udp
|
|||
}
|
||||
}
|
||||
|
||||
private Tuple<string, Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task>>> GetResponder(byte[] buffer, int bytesReceived, Encoding encoding)
|
||||
private Tuple<string, Tuple<string, bool, Func<string, IPEndPoint, Encoding, CancellationToken, Task>>> GetResponder(byte[] buffer, int bytesReceived, Encoding encoding)
|
||||
{
|
||||
var text = encoding.GetString(buffer, 0, bytesReceived);
|
||||
var responder = _responders.FirstOrDefault(i =>
|
||||
|
@ -99,10 +100,10 @@ namespace Emby.Server.Implementations.Udp
|
|||
{
|
||||
return null;
|
||||
}
|
||||
return new Tuple<string, Tuple<string, bool, Func<string, IpEndPointInfo, Encoding, CancellationToken, Task>>>(text, responder);
|
||||
return new Tuple<string, Tuple<string, bool, Func<string, IPEndPoint, Encoding, CancellationToken, Task>>>(text, responder);
|
||||
}
|
||||
|
||||
private async Task RespondToV2Message(string messageText, IpEndPointInfo endpoint, Encoding encoding, CancellationToken cancellationToken)
|
||||
private async Task RespondToV2Message(string messageText, IPEndPoint endpoint, Encoding encoding, CancellationToken cancellationToken)
|
||||
{
|
||||
var parts = messageText.Split('|');
|
||||
|
||||
|
@ -254,7 +255,7 @@ namespace Emby.Server.Implementations.Udp
|
|||
}
|
||||
}
|
||||
|
||||
public async Task SendAsync(byte[] bytes, IpEndPointInfo remoteEndPoint, CancellationToken cancellationToken)
|
||||
public async Task SendAsync(byte[] bytes, IPEndPoint remoteEndPoint, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
|
|
|
@ -18,7 +18,6 @@ using Jellyfin.Drawing.Skia;
|
|||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -41,12 +40,12 @@ namespace Jellyfin.Server
|
|||
// For backwards compatibility.
|
||||
// Modify any input arguments now which start with single-hyphen to POSIX standard
|
||||
// double-hyphen to allow parsing by CommandLineParser package.
|
||||
const string pattern = @"^(-[^-\s]{2})"; // Match -xx, not -x, not --xx, not xx
|
||||
const string substitution = @"-$1"; // Prepend with additional single-hyphen
|
||||
var regex = new Regex(pattern);
|
||||
const string Pattern = @"^(-[^-\s]{2})"; // Match -xx, not -x, not --xx, not xx
|
||||
const string Substitution = @"-$1"; // Prepend with additional single-hyphen
|
||||
var regex = new Regex(Pattern);
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
args[i] = regex.Replace(args[i], substitution);
|
||||
args[i] = regex.Replace(args[i], Substitution);
|
||||
}
|
||||
|
||||
// Parse the command line arguments and either start the app or exit indicating error
|
||||
|
@ -134,7 +133,7 @@ namespace Jellyfin.Server
|
|||
Batteries_V2.Init();
|
||||
if (raw.sqlite3_enable_shared_cache(1) != raw.SQLITE_OK)
|
||||
{
|
||||
Console.WriteLine("WARN: Failed to enable shared cache for SQLite");
|
||||
_logger.LogWarning("Failed to enable shared cache for SQLite");
|
||||
}
|
||||
|
||||
using (var appHost = new CoreAppHost(
|
||||
|
@ -143,7 +142,7 @@ namespace Jellyfin.Server
|
|||
options,
|
||||
new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
|
||||
new NullImageEncoder(),
|
||||
new NetworkManager(_loggerFactory),
|
||||
new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()),
|
||||
appConfig))
|
||||
{
|
||||
await appHost.InitAsync(new ServiceCollection()).ConfigureAwait(false);
|
||||
|
|
|
@ -133,8 +133,21 @@ namespace MediaBrowser.Api.Devices
|
|||
var album = Request.QueryString["Album"];
|
||||
var id = Request.QueryString["Id"];
|
||||
var name = Request.QueryString["Name"];
|
||||
var req = Request.Response.HttpContext.Request;
|
||||
|
||||
if (Request.ContentType.IndexOf("multi", StringComparison.OrdinalIgnoreCase) == -1)
|
||||
if (req.HasFormContentType)
|
||||
{
|
||||
var file = req.Form.Files.Count == 0 ? null : req.Form.Files[0];
|
||||
|
||||
return _deviceManager.AcceptCameraUpload(deviceId, file.OpenReadStream(), new LocalFileInfo
|
||||
{
|
||||
MimeType = file.ContentType,
|
||||
Album = album,
|
||||
Name = name,
|
||||
Id = id
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo
|
||||
{
|
||||
|
@ -144,18 +157,6 @@ namespace MediaBrowser.Api.Devices
|
|||
Id = id
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var file = Request.Files.Length == 0 ? null : Request.Files[0];
|
||||
|
||||
return _deviceManager.AcceptCameraUpload(deviceId, file.InputStream, new LocalFileInfo
|
||||
{
|
||||
MimeType = file.ContentType,
|
||||
Album = album,
|
||||
Name = name,
|
||||
Id = id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
@ -537,7 +538,7 @@ namespace MediaBrowser.Api.Images
|
|||
|
||||
if (item == null)
|
||||
{
|
||||
throw new ResourceNotFoundException(string.Format("Item {0} not found.", itemId.ToString("N")));
|
||||
throw new ResourceNotFoundException(string.Format("Item {0} not found.", itemId.ToString("N", CultureInfo.InvariantCulture)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue