mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-09-06 03:26:07 +02: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
|
- task: CmdLine@2
|
||||||
displayName: Execute ABI compatibility check tool
|
displayName: Execute ABI compatibility check tool
|
||||||
inputs:
|
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
|
workingDirectory: $(System.ArtifactsDirectory) # Optional
|
||||||
#failOnStderr: false # 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)
|
- [pjeanjean](https://github.com/pjeanjean)
|
||||||
- [DrPandemic](https://github.com/drpandemic)
|
- [DrPandemic](https://github.com/drpandemic)
|
||||||
- [joern-h](https://github.com/joern-h)
|
- [joern-h](https://github.com/joern-h)
|
||||||
|
- [Khinenw](https://github.com/HelloWorld017)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
|
|
|
@ -21,10 +21,10 @@ RUN apt-get update \
|
||||||
COPY --from=ffmpeg / /
|
COPY --from=ffmpeg / /
|
||||||
COPY --from=builder /jellyfin /jellyfin
|
COPY --from=builder /jellyfin /jellyfin
|
||||||
|
|
||||||
ARG JELLYFIN_WEB_VERSION=10.3.7
|
ARG JELLYFIN_WEB_VERSION=v10.3.7
|
||||||
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||||
&& rm -rf /jellyfin/jellyfin-web \
|
&& rm -rf /jellyfin/jellyfin-web \
|
||||||
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
|
&& mv jellyfin-web-* /jellyfin/jellyfin-web
|
||||||
|
|
||||||
EXPOSE 8096
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
VOLUME /cache /config /media
|
||||||
|
|
|
@ -26,10 +26,10 @@ RUN apt-get update \
|
||||||
&& chmod 777 /cache /config /media
|
&& chmod 777 /cache /config /media
|
||||||
COPY --from=builder /jellyfin /jellyfin
|
COPY --from=builder /jellyfin /jellyfin
|
||||||
|
|
||||||
ARG JELLYFIN_WEB_VERSION=10.3.7
|
ARG JELLYFIN_WEB_VERSION=v10.3.7
|
||||||
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||||
&& rm -rf /jellyfin/jellyfin-web \
|
&& rm -rf /jellyfin/jellyfin-web \
|
||||||
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
|
&& mv jellyfin-web-* /jellyfin/jellyfin-web
|
||||||
|
|
||||||
EXPOSE 8096
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
VOLUME /cache /config /media
|
||||||
|
|
|
@ -26,10 +26,10 @@ RUN apt-get update \
|
||||||
&& chmod 777 /cache /config /media
|
&& chmod 777 /cache /config /media
|
||||||
COPY --from=builder /jellyfin /jellyfin
|
COPY --from=builder /jellyfin /jellyfin
|
||||||
|
|
||||||
ARG JELLYFIN_WEB_VERSION=10.3.7
|
ARG JELLYFIN_WEB_VERSION=v10.3.7
|
||||||
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||||
&& rm -rf /jellyfin/jellyfin-web \
|
&& rm -rf /jellyfin/jellyfin-web \
|
||||||
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
|
&& mv jellyfin-web-* /jellyfin/jellyfin-web
|
||||||
|
|
||||||
EXPOSE 8096
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
VOLUME /cache /config /media
|
||||||
|
|
|
@ -181,19 +181,6 @@ namespace Emby.Dlna.Didl
|
||||||
writer.WriteFullEndElement();
|
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)
|
private void AddVideoResource(DlnaOptions options, XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
|
||||||
{
|
{
|
||||||
if (streamInfo == null)
|
if (streamInfo == null)
|
||||||
|
@ -384,7 +371,7 @@ namespace Emby.Dlna.Didl
|
||||||
var filename = url.Substring(0, url.IndexOf('?'));
|
var filename = url.Substring(0, url.IndexOf('?'));
|
||||||
|
|
||||||
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
||||||
? GetMimeType(filename)
|
? MimeTypes.GetMimeType(filename)
|
||||||
: mediaProfile.MimeType;
|
: mediaProfile.MimeType;
|
||||||
|
|
||||||
writer.WriteAttributeString("protocolInfo", string.Format(
|
writer.WriteAttributeString("protocolInfo", string.Format(
|
||||||
|
@ -520,7 +507,7 @@ namespace Emby.Dlna.Didl
|
||||||
var filename = url.Substring(0, url.IndexOf('?'));
|
var filename = url.Substring(0, url.IndexOf('?'));
|
||||||
|
|
||||||
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
||||||
? GetMimeType(filename)
|
? MimeTypes.GetMimeType(filename)
|
||||||
: mediaProfile.MimeType;
|
: mediaProfile.MimeType;
|
||||||
|
|
||||||
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container,
|
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container,
|
||||||
|
@ -545,17 +532,10 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsIdRoot(string id)
|
public static bool IsIdRoot(string id)
|
||||||
{
|
=> string.IsNullOrWhiteSpace(id)
|
||||||
if (string.IsNullOrWhiteSpace(id)
|
|
||||||
|| string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
|
||||||
// Samsung sometimes uses 1 as root
|
// Samsung sometimes uses 1 as root
|
||||||
|| string.Equals(id, "1", StringComparison.OrdinalIgnoreCase))
|
|| string.Equals(id, "1", StringComparison.OrdinalIgnoreCase);
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null)
|
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(
|
writer.WriteAttributeString("protocolInfo", string.Format(
|
||||||
"http-get:*:{0}:{1}",
|
"http-get:*:{0}:{1}",
|
||||||
GetMimeType("file." + format),
|
MimeTypes.GetMimeType("file." + format),
|
||||||
contentFeatures
|
contentFeatures
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -1102,7 +1082,7 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
public static string GetClientId(Guid idValue, StubType? stubType)
|
public static string GetClientId(Guid idValue, StubType? stubType)
|
||||||
{
|
{
|
||||||
var id = idValue.ToString("N");
|
var id = idValue.ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
if (stubType.HasValue)
|
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",
|
var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
|
||||||
_serverAddress,
|
_serverAddress,
|
||||||
info.ItemId.ToString("N"),
|
info.ItemId.ToString("N", CultureInfo.InvariantCulture),
|
||||||
info.Type,
|
info.Type,
|
||||||
info.ImageTag,
|
info.ImageTag,
|
||||||
format,
|
format,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
@ -300,7 +301,7 @@ namespace Emby.Dlna
|
||||||
|
|
||||||
profile = ReserializeProfile(tempProfile);
|
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);
|
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
|
||||||
|
|
||||||
|
@ -352,7 +353,7 @@ namespace Emby.Dlna
|
||||||
|
|
||||||
Info = new DeviceProfileInfo
|
Info = new DeviceProfileInfo
|
||||||
{
|
{
|
||||||
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N"),
|
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
||||||
Name = _fileSystem.GetFileNameWithoutExtension(file),
|
Name = _fileSystem.GetFileNameWithoutExtension(file),
|
||||||
Type = type
|
Type = type
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ namespace Emby.Dlna.Eventing
|
||||||
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
|
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
|
||||||
{
|
{
|
||||||
var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
|
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
|
// Remove logging for now because some devices are sending this very frequently
|
||||||
// TODO re-enable with dlna debug logging setting
|
// TODO re-enable with dlna debug logging setting
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Globalization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.PlayTo;
|
using Emby.Dlna.PlayTo;
|
||||||
|
@ -247,7 +249,7 @@ namespace Emby.Dlna.Main
|
||||||
|
|
||||||
foreach (var address in addresses)
|
foreach (var address in addresses)
|
||||||
{
|
{
|
||||||
if (address.AddressFamily == IpAddressFamily.InterNetworkV6)
|
if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||||
{
|
{
|
||||||
// Not support IPv6 right now
|
// Not support IPv6 right now
|
||||||
continue;
|
continue;
|
||||||
|
@ -306,7 +308,7 @@ namespace Emby.Dlna.Main
|
||||||
{
|
{
|
||||||
guid = text.GetMD5();
|
guid = text.GetMD5();
|
||||||
}
|
}
|
||||||
return guid.ToString("N");
|
return guid.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetProperies(SsdpDevice device, string fullDeviceType)
|
private void SetProperies(SsdpDevice device, string fullDeviceType)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
|
@ -14,7 +16,6 @@ using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Net;
|
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
@ -141,7 +142,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
return usn;
|
return usn;
|
||||||
}
|
}
|
||||||
|
|
||||||
return usn.GetMD5().ToString("N");
|
return usn.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddDevice(UpnpDeviceInfo info, string location, CancellationToken cancellationToken)
|
private async Task AddDevice(UpnpDeviceInfo info, string location, CancellationToken cancellationToken)
|
||||||
|
@ -156,7 +157,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
else
|
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);
|
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);
|
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
|
||||||
|
|
||||||
string serverAddress;
|
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);
|
serverAddress = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Name = "Dish Hopper-Joey";
|
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
|
Identification = new DeviceIdentification
|
||||||
{
|
{
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<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>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -454,14 +454,14 @@ namespace Emby.Drawing
|
||||||
// Optimization
|
// Optimization
|
||||||
if (imageEnhancers.Length == 0)
|
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
|
// 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();
|
var cacheKeys = imageEnhancers.Select(i => i.GetConfigurationCacheKey(item, imageType)).ToList();
|
||||||
cacheKeys.Add(originalImagePath + dateModified.Ticks);
|
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)
|
private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
|
||||||
|
@ -480,7 +480,7 @@ namespace Emby.Drawing
|
||||||
{
|
{
|
||||||
try
|
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";
|
string cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png";
|
||||||
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
|
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -101,7 +102,7 @@ namespace Emby.Notifications
|
||||||
var config = GetConfiguration();
|
var config = GetConfiguration();
|
||||||
|
|
||||||
return _userManager.Users
|
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);
|
.Select(i => i.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,7 +198,7 @@ namespace Emby.Notifications
|
||||||
return _services.Select(i => new NameIdPair
|
return _services.Select(i => new NameIdPair
|
||||||
{
|
{
|
||||||
Name = i.Name,
|
Name = i.Name,
|
||||||
Id = i.Name.GetMD5().ToString("N")
|
Id = i.Name.GetMD5().ToString("N", CultureInfo.InvariantCulture)
|
||||||
|
|
||||||
}).OrderBy(i => i.Name);
|
}).OrderBy(i => i.Name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -75,7 +76,6 @@ namespace Emby.Server.Implementations.Activity
|
||||||
_sessionManager.AuthenticationFailed += OnAuthenticationFailed;
|
_sessionManager.AuthenticationFailed += OnAuthenticationFailed;
|
||||||
_sessionManager.AuthenticationSucceeded += OnAuthenticationSucceeded;
|
_sessionManager.AuthenticationSucceeded += OnAuthenticationSucceeded;
|
||||||
_sessionManager.SessionEnded += OnSessionEnded;
|
_sessionManager.SessionEnded += OnSessionEnded;
|
||||||
|
|
||||||
_sessionManager.PlaybackStart += OnPlaybackStart;
|
_sessionManager.PlaybackStart += OnPlaybackStart;
|
||||||
_sessionManager.PlaybackStopped += OnPlaybackStopped;
|
_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)),
|
Name = string.Format(_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), e.Provider, Notifications.Notifications.GetItemName(e.Item)),
|
||||||
Type = "SubtitleDownloadFailure",
|
Type = "SubtitleDownloadFailure",
|
||||||
ItemId = e.Item.Id.ToString("N"),
|
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||||
ShortOverview = e.Exception.Message
|
ShortOverview = e.Exception.Message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Activity
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
statement.TryBind("@UserId", entry.UserId.ToString("N"));
|
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
||||||
|
@ -141,7 +141,7 @@ namespace Emby.Server.Implementations.Activity
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
statement.TryBind("@UserId", entry.UserId.ToString("N"));
|
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
||||||
|
|
|
@ -10,6 +10,8 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class BaseApplicationPaths : IApplicationPaths
|
public abstract class BaseApplicationPaths : IApplicationPaths
|
||||||
{
|
{
|
||||||
|
private string _dataPath;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
|
/// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -30,27 +32,27 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the program data folder
|
/// Gets the path to the program data folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The program data path.</value>
|
/// <value>The program data path.</value>
|
||||||
public string ProgramDataPath { get; private set; }
|
public string ProgramDataPath { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the web UI resources folder
|
/// Gets the path to the web UI resources folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The web UI resources path.</value>
|
/// <value>The web UI resources path.</value>
|
||||||
public string WebPath { get; set; }
|
public string WebPath { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the system folder
|
/// Gets the path to the system folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <value>The path to the system folder.</value>
|
||||||
public string ProgramSystemPath { get; } = AppContext.BaseDirectory;
|
public string ProgramSystemPath { get; } = AppContext.BaseDirectory;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the folder path to the data directory
|
/// Gets the folder path to the data directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The data directory.</value>
|
/// <value>The data directory.</value>
|
||||||
private string _dataPath;
|
|
||||||
public string DataPath
|
public string DataPath
|
||||||
{
|
{
|
||||||
get => _dataPath;
|
get => _dataPath;
|
||||||
|
@ -58,8 +60,9 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the magic strings used for virtual path manipulation.
|
/// Gets the magic string used for virtual path manipulation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <value>The magic string used for virtual path manipulation.</value>
|
||||||
public string VirtualDataPath { get; } = "%AppDataPath%";
|
public string VirtualDataPath { get; } = "%AppDataPath%";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -69,43 +72,43 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
public string ImageCachePath => Path.Combine(CachePath, "images");
|
public string ImageCachePath => Path.Combine(CachePath, "images");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the plugin directory
|
/// Gets the path to the plugin directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The plugins path.</value>
|
/// <value>The plugins path.</value>
|
||||||
public string PluginsPath => Path.Combine(ProgramDataPath, "plugins");
|
public string PluginsPath => Path.Combine(ProgramDataPath, "plugins");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the plugin configurations directory
|
/// Gets the path to the plugin configurations directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The plugin configurations path.</value>
|
/// <value>The plugin configurations path.</value>
|
||||||
public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations");
|
public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the log directory
|
/// Gets the path to the log directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The log directory path.</value>
|
/// <value>The log directory path.</value>
|
||||||
public string LogDirectoryPath { get; private set; }
|
public string LogDirectoryPath { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the application configuration root directory
|
/// Gets the path to the application configuration root directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The configuration directory path.</value>
|
/// <value>The configuration directory path.</value>
|
||||||
public string ConfigurationDirectoryPath { get; private set; }
|
public string ConfigurationDirectoryPath { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the system configuration file
|
/// Gets the path to the system configuration file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The system configuration file path.</value>
|
/// <value>The system configuration file path.</value>
|
||||||
public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml");
|
public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the folder path to the cache directory
|
/// Gets or sets the folder path to the cache directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The cache directory.</value>
|
/// <value>The cache directory.</value>
|
||||||
public string CachePath { get; set; }
|
public string CachePath { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
/// <value>The temp directory.</value>
|
/// <value>The temp directory.</value>
|
||||||
public string TempDirectory => Path.Combine(CachePath, "temp");
|
public string TempDirectory => Path.Combine(CachePath, "temp");
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -19,11 +20,44 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class BaseConfigurationManager : IConfigurationManager
|
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>
|
/// <summary>
|
||||||
/// Gets the type of the configuration.
|
/// The _configuration loaded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The type of the configuration.</value>
|
private bool _configurationLoaded;
|
||||||
protected abstract Type ConfigurationType { get; }
|
|
||||||
|
/// <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>
|
/// <summary>
|
||||||
/// Occurs when [configuration updated].
|
/// Occurs when [configuration updated].
|
||||||
|
@ -40,6 +74,12 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdated;
|
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>
|
/// <summary>
|
||||||
/// Gets the logger.
|
/// Gets the logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -56,20 +96,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The application paths.</value>
|
/// <value>The application paths.</value>
|
||||||
public IApplicationPaths CommonApplicationPaths { get; private set; }
|
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>
|
/// <summary>
|
||||||
/// Gets the system configuration
|
/// Gets the system configuration
|
||||||
/// </summary>
|
/// </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)
|
public virtual void AddParts(IEnumerable<IConfigurationFactory> factories)
|
||||||
{
|
{
|
||||||
_configurationFactories = factories.ToArray();
|
_configurationFactories = factories.ToArray();
|
||||||
|
@ -171,6 +178,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
private void UpdateCachePath()
|
private void UpdateCachePath()
|
||||||
{
|
{
|
||||||
string cachePath;
|
string cachePath;
|
||||||
|
|
||||||
// If the configuration file has no entry (i.e. not set in UI)
|
// If the configuration file has no entry (i.e. not set in UI)
|
||||||
if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath))
|
if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath))
|
||||||
{
|
{
|
||||||
|
@ -207,12 +215,16 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
var newPath = newConfig.CachePath;
|
var newPath = newConfig.CachePath;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(newPath)
|
if (!string.IsNullOrWhiteSpace(newPath)
|
||||||
&& !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath))
|
&& !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
// Validate
|
// Validate
|
||||||
if (!Directory.Exists(newPath))
|
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);
|
EnsureWriteAccess(newPath);
|
||||||
|
@ -223,11 +235,9 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
{
|
{
|
||||||
var file = Path.Combine(path, Guid.NewGuid().ToString());
|
var file = Path.Combine(path, Guid.NewGuid().ToString());
|
||||||
File.WriteAllText(file, string.Empty);
|
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)
|
private string GetConfigurationFile(string key)
|
||||||
{
|
{
|
||||||
return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLowerInvariant() + ".xml");
|
return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLowerInvariant() + ".xml");
|
||||||
|
|
|
@ -7,6 +7,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Sockets;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
@ -107,9 +108,9 @@ using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Extensions;
|
using Microsoft.AspNetCore.Http.Extensions;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||||
|
|
||||||
|
@ -385,7 +386,7 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
|
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
|
||||||
|
|
||||||
NetworkManager.NetworkChanged += NetworkManager_NetworkChanged;
|
NetworkManager.NetworkChanged += OnNetworkChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ExpandVirtualPath(string path)
|
public string ExpandVirtualPath(string path)
|
||||||
|
@ -409,7 +410,7 @@ namespace Emby.Server.Implementations
|
||||||
return ServerConfigurationManager.Configuration.LocalNetworkSubnets;
|
return ServerConfigurationManager.Configuration.LocalNetworkSubnets;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NetworkManager_NetworkChanged(object sender, EventArgs e)
|
private void OnNetworkChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
_validAddressResults.Clear();
|
_validAddressResults.Clear();
|
||||||
}
|
}
|
||||||
|
@ -417,10 +418,10 @@ namespace Emby.Server.Implementations
|
||||||
public string ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
|
public string ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current application user agent
|
/// Gets the current application user agent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The application user agent.</value>
|
/// <value>The application user agent.</value>
|
||||||
public string ApplicationUserAgent => Name.Replace(' ','-') + '/' + ApplicationVersion;
|
public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersion;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the email address for use within a comment section of a user agent field.
|
/// Gets the email address for use within a comment section of a user agent field.
|
||||||
|
@ -428,14 +429,11 @@ namespace Emby.Server.Implementations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ApplicationUserAgentAddress { get; } = "team@jellyfin.org";
|
public string ApplicationUserAgentAddress { get; } = "team@jellyfin.org";
|
||||||
|
|
||||||
private string _productName;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current application name
|
/// Gets the current application name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The application name.</value>
|
/// <value>The application name.</value>
|
||||||
public string ApplicationProductName
|
public string ApplicationProductName { get; } = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName;
|
||||||
=> _productName ?? (_productName = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName);
|
|
||||||
|
|
||||||
private DeviceId _deviceId;
|
private DeviceId _deviceId;
|
||||||
|
|
||||||
|
@ -469,8 +467,8 @@ namespace Emby.Server.Implementations
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an instance of type and resolves all constructor dependencies
|
/// Creates an instance of type and resolves all constructor dependencies
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// /// <typeparam name="T">The type</typeparam>
|
/// /// <typeparam name="T">The type.</typeparam>
|
||||||
/// <returns>T</returns>
|
/// <returns>T.</returns>
|
||||||
public T CreateInstance<T>()
|
public T CreateInstance<T>()
|
||||||
=> ActivatorUtilities.CreateInstance<T>(_serviceProvider);
|
=> ActivatorUtilities.CreateInstance<T>(_serviceProvider);
|
||||||
|
|
||||||
|
@ -603,10 +601,15 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
foreach (var plugin in Plugins)
|
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();
|
DiscoverTypes();
|
||||||
|
@ -628,7 +631,7 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
if (EnableHttps && Certificate != null)
|
if (EnableHttps && Certificate != null)
|
||||||
{
|
{
|
||||||
options.ListenAnyIP(HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); });
|
options.ListenAnyIP(HttpsPort, listenOptions => listenOptions.UseHttps(Certificate));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.UseContentRoot(contentRoot)
|
.UseContentRoot(contentRoot)
|
||||||
|
@ -642,6 +645,7 @@ namespace Emby.Server.Implementations
|
||||||
app.UseWebSockets();
|
app.UseWebSockets();
|
||||||
|
|
||||||
app.UseResponseCompression();
|
app.UseResponseCompression();
|
||||||
|
|
||||||
// TODO app.UseMiddleware<WebSocketMiddleware>();
|
// TODO app.UseMiddleware<WebSocketMiddleware>();
|
||||||
app.Use(ExecuteWebsocketHandlerAsync);
|
app.Use(ExecuteWebsocketHandlerAsync);
|
||||||
app.Use(ExecuteHttpHandlerAsync);
|
app.Use(ExecuteHttpHandlerAsync);
|
||||||
|
@ -675,7 +679,7 @@ namespace Emby.Server.Implementations
|
||||||
var localPath = context.Request.Path.ToString();
|
var localPath = context.Request.Path.ToString();
|
||||||
|
|
||||||
var req = new WebSocketSharpRequest(request, response, request.Path, Logger);
|
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; }
|
public static IStreamHelper StreamHelper { get; set; }
|
||||||
|
@ -784,7 +788,7 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
HttpServer = new HttpListenerHost(
|
HttpServer = new HttpListenerHost(
|
||||||
this,
|
this,
|
||||||
LoggerFactory,
|
LoggerFactory.CreateLogger<HttpListenerHost>(),
|
||||||
ServerConfigurationManager,
|
ServerConfigurationManager,
|
||||||
_configuration,
|
_configuration,
|
||||||
NetworkManager,
|
NetworkManager,
|
||||||
|
@ -872,7 +876,7 @@ namespace Emby.Server.Implementations
|
||||||
serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
|
serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
|
||||||
serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
|
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);
|
serviceCollection.AddSingleton(AuthService);
|
||||||
|
|
||||||
SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory);
|
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>()
|
.Cast<IServerEntryPoint>()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
await Task.WhenAll(StartEntryPoints(entries, true));
|
await Task.WhenAll(StartEntryPoints(entries, true)).ConfigureAwait(false);
|
||||||
await Task.WhenAll(StartEntryPoints(entries, false));
|
await Task.WhenAll(StartEntryPoints(entries, false)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1219,7 +1223,7 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
// Generate self-signed cert
|
// Generate self-signed cert
|
||||||
var certHost = GetHostnameFromExternalDns(ServerConfigurationManager.Configuration.WanDdns);
|
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";
|
const string Password = "embycert";
|
||||||
|
|
||||||
return new CertificateInfo
|
return new CertificateInfo
|
||||||
|
@ -1457,15 +1461,10 @@ namespace Emby.Server.Implementations
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public WakeOnLanInfo[] GetWakeOnLanInfo()
|
public IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo()
|
||||||
{
|
=> NetworkManager.GetMacAddresses()
|
||||||
return NetworkManager.GetMacAddresses()
|
.Select(i => new WakeOnLanInfo(i))
|
||||||
.Select(i => new WakeOnLanInfo
|
.ToList();
|
||||||
{
|
|
||||||
MacAddress = i
|
|
||||||
})
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<PublicSystemInfo> GetPublicSystemInfo(CancellationToken cancellationToken)
|
public async Task<PublicSystemInfo> GetPublicSystemInfo(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
@ -1481,6 +1480,7 @@ namespace Emby.Server.Implementations
|
||||||
{
|
{
|
||||||
wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns);
|
wanAddress = GetWanApiUrl(ServerConfigurationManager.Configuration.WanDdns);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PublicSystemInfo
|
return new PublicSystemInfo
|
||||||
{
|
{
|
||||||
Version = ApplicationVersion,
|
Version = ApplicationVersion,
|
||||||
|
@ -1546,14 +1546,32 @@ namespace Emby.Server.Implementations
|
||||||
return null;
|
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)
|
public string GetLocalApiUrl(string host)
|
||||||
|
@ -1564,19 +1582,22 @@ namespace Emby.Server.Implementations
|
||||||
host,
|
host,
|
||||||
HttpsPort.ToString(CultureInfo.InvariantCulture));
|
HttpsPort.ToString(CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Format("http://{0}:{1}",
|
return string.Format("http://{0}:{1}",
|
||||||
host,
|
host,
|
||||||
HttpPort.ToString(CultureInfo.InvariantCulture));
|
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)
|
public string GetWanApiUrl(string host)
|
||||||
|
@ -1587,17 +1608,18 @@ namespace Emby.Server.Implementations
|
||||||
host,
|
host,
|
||||||
ServerConfigurationManager.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture));
|
ServerConfigurationManager.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Format("http://{0}:{1}",
|
return string.Format("http://{0}:{1}",
|
||||||
host,
|
host,
|
||||||
ServerConfigurationManager.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture));
|
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);
|
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
|
var addresses = ServerConfigurationManager
|
||||||
.Configuration
|
.Configuration
|
||||||
|
@ -1611,13 +1633,13 @@ namespace Emby.Server.Implementations
|
||||||
addresses.AddRange(NetworkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
|
addresses.AddRange(NetworkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultList = new List<IpAddressInfo>();
|
var resultList = new List<IPAddress>();
|
||||||
|
|
||||||
foreach (var address in addresses)
|
foreach (var address in addresses)
|
||||||
{
|
{
|
||||||
if (!allowLoopback)
|
if (!allowLoopback)
|
||||||
{
|
{
|
||||||
if (address.Equals(IpAddressInfo.Loopback) || address.Equals(IpAddressInfo.IPv6Loopback))
|
if (address.Equals(IPAddress.Loopback) || address.Equals(IPAddress.IPv6Loopback))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1638,7 +1660,7 @@ namespace Emby.Server.Implementations
|
||||||
return resultList;
|
return resultList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IpAddressInfo NormalizeConfiguredLocalAddress(string address)
|
private IPAddress NormalizeConfiguredLocalAddress(string address)
|
||||||
{
|
{
|
||||||
var index = address.Trim('/').IndexOf('/');
|
var index = address.Trim('/').IndexOf('/');
|
||||||
|
|
||||||
|
@ -1647,7 +1669,7 @@ namespace Emby.Server.Implementations
|
||||||
address = address.Substring(index + 1);
|
address = address.Substring(index + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NetworkManager.TryParseIpAddress(address.Trim('/'), out IpAddressInfo result))
|
if (IPAddress.TryParse(address.Trim('/'), out IPAddress result))
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1657,10 +1679,10 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
|
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) ||
|
if (address.Equals(IPAddress.Loopback) ||
|
||||||
address.Equals(IpAddressInfo.IPv6Loopback))
|
address.Equals(IPAddress.IPv6Loopback))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -206,7 +207,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return GetChannelProvider(i).IsEnabledFor(user.Id.ToString("N"));
|
return GetChannelProvider(i).IsEnabledFor(user.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -511,7 +512,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
IncludeItemTypes = new[] { typeof(Channel).Name },
|
IncludeItemTypes = new[] { typeof(Channel).Name },
|
||||||
OrderBy = new ValueTuple<string, SortOrder>[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) }
|
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)
|
public ChannelFeatures GetChannelFeatures(string id)
|
||||||
|
@ -552,7 +553,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
SupportsSortOrderToggle = features.SupportsSortOrderToggle,
|
SupportsSortOrderToggle = features.SupportsSortOrderToggle,
|
||||||
SupportsLatestMedia = supportsLatest,
|
SupportsLatestMedia = supportsLatest,
|
||||||
Name = channel.Name,
|
Name = channel.Name,
|
||||||
Id = channel.Id.ToString("N"),
|
Id = channel.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||||
SupportsContentDownloading = features.SupportsContentDownloading,
|
SupportsContentDownloading = features.SupportsContentDownloading,
|
||||||
AutoRefreshLevels = features.AutoRefreshLevels
|
AutoRefreshLevels = features.AutoRefreshLevels
|
||||||
};
|
};
|
||||||
|
@ -740,7 +741,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
bool sortDescending,
|
bool sortDescending,
|
||||||
CancellationToken cancellationToken)
|
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 cacheLength = CacheLength;
|
||||||
var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
|
var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
|
||||||
|
@ -836,7 +837,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
ChannelItemSortField? sortField,
|
ChannelItemSortField? sortField,
|
||||||
bool sortDescending)
|
bool sortDescending)
|
||||||
{
|
{
|
||||||
var channelId = GetInternalChannelId(channel.Name).ToString("N");
|
var channelId = GetInternalChannelId(channel.Name).ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
var userCacheKey = string.Empty;
|
var userCacheKey = string.Empty;
|
||||||
|
|
||||||
|
@ -846,10 +847,10 @@ namespace Emby.Server.Implementations.Channels
|
||||||
userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty;
|
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;
|
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)
|
if (sortField.HasValue)
|
||||||
{
|
{
|
||||||
|
@ -860,7 +861,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
filename += "-sortDescending";
|
filename += "-sortDescending";
|
||||||
}
|
}
|
||||||
|
|
||||||
filename = filename.GetMD5().ToString("N");
|
filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
return Path.Combine(_config.ApplicationPaths.CachePath,
|
return Path.Combine(_config.ApplicationPaths.CachePath,
|
||||||
"channels",
|
"channels",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -182,7 +183,7 @@ namespace Emby.Server.Implementations.Collections
|
||||||
|
|
||||||
public void AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
|
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)
|
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>
|
public static readonly Dictionary<string, string> Configuration = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
{"HttpListenerHost:DefaultRedirectPath", "web/index.html"},
|
{ "HttpListenerHost:DefaultRedirectPath", "web/index.html" },
|
||||||
{"MusicBrainz:BaseUrl", "https://www.musicbrainz.org"}
|
{ "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ using MediaBrowser.Model.Cryptography;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Cryptography
|
namespace Emby.Server.Implementations.Cryptography
|
||||||
{
|
{
|
||||||
public class CryptographyProvider : ICryptoProvider
|
public class CryptographyProvider : ICryptoProvider, IDisposable
|
||||||
{
|
{
|
||||||
private static readonly HashSet<string> _supportedHashMethods = new HashSet<string>()
|
private static readonly HashSet<string> _supportedHashMethods = new HashSet<string>()
|
||||||
{
|
{
|
||||||
|
@ -28,26 +28,28 @@ namespace Emby.Server.Implementations.Cryptography
|
||||||
"System.Security.Cryptography.SHA512"
|
"System.Security.Cryptography.SHA512"
|
||||||
};
|
};
|
||||||
|
|
||||||
public string DefaultHashMethod => "PBKDF2";
|
|
||||||
|
|
||||||
private RandomNumberGenerator _randomNumberGenerator;
|
private RandomNumberGenerator _randomNumberGenerator;
|
||||||
|
|
||||||
private const int _defaultIterations = 1000;
|
private const int _defaultIterations = 1000;
|
||||||
|
|
||||||
|
private bool _disposed = false;
|
||||||
|
|
||||||
public CryptographyProvider()
|
public CryptographyProvider()
|
||||||
{
|
{
|
||||||
//FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
|
// 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
|
// 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
|
// 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
|
// 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();
|
_randomNumberGenerator = RandomNumberGenerator.Create();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guid GetMD5(string str)
|
public string DefaultHashMethod => "PBKDF2";
|
||||||
{
|
|
||||||
return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str)));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
[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)
|
public byte[] ComputeSHA1(byte[] bytes)
|
||||||
{
|
{
|
||||||
using (var provider = SHA1.Create())
|
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)
|
public byte[] ComputeMD5(Stream str)
|
||||||
{
|
{
|
||||||
using (var provider = MD5.Create())
|
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)
|
public byte[] ComputeMD5(byte[] bytes)
|
||||||
{
|
{
|
||||||
using (var provider = MD5.Create())
|
using (var provider = MD5.Create())
|
||||||
|
@ -73,9 +77,7 @@ namespace Emby.Server.Implementations.Cryptography
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> GetSupportedHashMethods()
|
public IEnumerable<string> GetSupportedHashMethods()
|
||||||
{
|
=> _supportedHashMethods;
|
||||||
return _supportedHashMethods;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations)
|
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)
|
public byte[] ComputeHash(string hashMethod, byte[] bytes)
|
||||||
{
|
=> ComputeHash(hashMethod, bytes, Array.Empty<byte>());
|
||||||
return ComputeHash(hashMethod, bytes, Array.Empty<byte>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] ComputeHashWithDefaultMethod(byte[] bytes)
|
public byte[] ComputeHashWithDefaultMethod(byte[] bytes)
|
||||||
{
|
=> ComputeHash(DefaultHashMethod, bytes);
|
||||||
return ComputeHash(DefaultHashMethod, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt)
|
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)
|
public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
|
||||||
{
|
=> PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations);
|
||||||
return PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] ComputeHash(PasswordHash hash)
|
public byte[] ComputeHash(PasswordHash hash)
|
||||||
{
|
{
|
||||||
int iterations = _defaultIterations;
|
int iterations = _defaultIterations;
|
||||||
if (!hash.Parameters.ContainsKey("iterations"))
|
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
|
throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}");
|
||||||
{
|
|
||||||
iterations = int.Parse(hash.Parameters["iterations"]);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations);
|
return PBKDF2(hash.Id, hash.Hash, hash.Salt, iterations);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] GenerateSalt()
|
public byte[] GenerateSalt()
|
||||||
|
@ -164,5 +152,29 @@ namespace Emby.Server.Implementations.Cryptography
|
||||||
_randomNumberGenerator.GetBytes(salt);
|
_randomNumberGenerator.GetBytes(salt);
|
||||||
return 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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
@ -182,7 +183,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
return new DisplayPreferences
|
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.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)
|
if (item is IHasProgramAttributes hasProgramAttributes)
|
||||||
{
|
{
|
||||||
|
@ -851,7 +851,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
}
|
}
|
||||||
else
|
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)
|
if (item is Trailer trailer && trailer.TrailerTypes.Length > 0)
|
||||||
|
@ -3548,12 +3548,12 @@ namespace Emby.Server.Implementations.Data
|
||||||
whereClauses.Add("ChannelId=@ChannelId");
|
whereClauses.Add("ChannelId=@ChannelId");
|
||||||
if (statement != null)
|
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)
|
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})");
|
whereClauses.Add($"ChannelId in ({inClause})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4537,12 +4537,12 @@ namespace Emby.Server.Implementations.Data
|
||||||
}
|
}
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind("@TopParentId", queryTopParentIds[0].ToString("N"));
|
statement.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (queryTopParentIds.Length > 1)
|
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)
|
if (enableItemsByName && includedItemByNameTypes.Count == 1)
|
||||||
{
|
{
|
||||||
|
@ -4574,7 +4574,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
}
|
}
|
||||||
if (query.AncestorIds.Length > 1)
|
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));
|
whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause));
|
||||||
}
|
}
|
||||||
if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey))
|
if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey))
|
||||||
|
@ -4637,7 +4637,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
foreach (var folderId in query.BoxSetLibraryFolders)
|
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) + ")");
|
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];
|
var ancestorId = ancestorIds[i];
|
||||||
|
|
||||||
statement.TryBind("@AncestorId" + index, ancestorId.ToGuidBlob());
|
statement.TryBind("@AncestorId" + index, ancestorId.ToGuidBlob());
|
||||||
statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N"));
|
statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N", CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
statement.Reset();
|
statement.Reset();
|
||||||
|
@ -5579,6 +5579,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||||
{
|
{
|
||||||
counts.TrailerCount = value;
|
counts.TrailerCount = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
counts.ItemCount += value;
|
counts.ItemCount += value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Devices
|
namespace Emby.Server.Implementations.Devices
|
||||||
|
@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.Devices
|
||||||
|
|
||||||
private static string GetNewId()
|
private static string GetNewId()
|
||||||
{
|
{
|
||||||
return Guid.NewGuid().ToString("N");
|
return Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetDeviceId()
|
private string GetDeviceId()
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Devices;
|
using MediaBrowser.Controller.Devices;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
@ -195,7 +195,7 @@ namespace Emby.Server.Implementations.Devices
|
||||||
|
|
||||||
private string GetDevicePath(string id)
|
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)
|
public ContentUploadHistory GetCameraUploadHistory(string deviceId)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -213,7 +214,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
|
|
||||||
if (options.ContainsField(ItemFields.DisplayPreferencesId))
|
if (options.ContainsField(ItemFields.DisplayPreferencesId))
|
||||||
{
|
{
|
||||||
dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N");
|
dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
|
@ -444,7 +445,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
/// <exception cref="ArgumentNullException">item</exception>
|
/// <exception cref="ArgumentNullException">item</exception>
|
||||||
public string GetDtoId(BaseItem item)
|
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)
|
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))
|
if (dictionary.TryGetValue(person.Name, out Person entity))
|
||||||
{
|
{
|
||||||
baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary);
|
baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary);
|
||||||
baseItemPerson.Id = entity.Id.ToString("N");
|
baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
list.Add(baseItemPerson);
|
list.Add(baseItemPerson);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -893,7 +894,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
//var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
|
//var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
|
||||||
//{
|
//{
|
||||||
// EnableTotalRecordCount = false,
|
// EnableTotalRecordCount = false,
|
||||||
// ItemIds = new[] { item.Id.ToString("N") }
|
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
|
||||||
//});
|
//});
|
||||||
|
|
||||||
//dto.ArtistItems = artistItems.Items
|
//dto.ArtistItems = artistItems.Items
|
||||||
|
@ -903,7 +904,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
// return new NameIdPair
|
// return new NameIdPair
|
||||||
// {
|
// {
|
||||||
// Name = artist.Name,
|
// Name = artist.Name,
|
||||||
// Id = artist.Id.ToString("N")
|
// Id = artist.Id.ToString("N", CultureInfo.InvariantCulture)
|
||||||
// };
|
// };
|
||||||
// })
|
// })
|
||||||
// .ToList();
|
// .ToList();
|
||||||
|
@ -946,7 +947,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
//var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
|
//var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
|
||||||
//{
|
//{
|
||||||
// EnableTotalRecordCount = false,
|
// EnableTotalRecordCount = false,
|
||||||
// ItemIds = new[] { item.Id.ToString("N") }
|
// ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
|
||||||
//});
|
//});
|
||||||
|
|
||||||
//dto.AlbumArtists = artistItems.Items
|
//dto.AlbumArtists = artistItems.Items
|
||||||
|
@ -956,7 +957,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
// return new NameIdPair
|
// return new NameIdPair
|
||||||
// {
|
// {
|
||||||
// Name = artist.Name,
|
// Name = artist.Name,
|
||||||
// Id = artist.Id.ToString("N")
|
// Id = artist.Id.ToString("N", CultureInfo.InvariantCulture)
|
||||||
// };
|
// };
|
||||||
// })
|
// })
|
||||||
// .ToList();
|
// .ToList();
|
||||||
|
@ -1044,7 +1045,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
}
|
}
|
||||||
else
|
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))
|
mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase))
|
||||||
.SelectMany(i => i.MediaStreams)
|
.SelectMany(i => i.MediaStreams)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="IPNetwork2" Version="2.4.0.126" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" 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" />
|
<PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.2.0" />
|
||||||
|
@ -43,6 +44,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
|
|
|
@ -100,7 +100,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
_lastProgressMessageTimes[item.Id] = DateTime.UtcNow;
|
_lastProgressMessageTimes[item.Id] = DateTime.UtcNow;
|
||||||
|
|
||||||
var dict = new Dictionary<string, string>();
|
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);
|
dict["Progress"] = progress.ToString(CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
foreach (var collectionFolder in collectionFolders)
|
foreach (var collectionFolder in collectionFolders)
|
||||||
{
|
{
|
||||||
var collectionFolderDict = new Dictionary<string, string>();
|
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);
|
collectionFolderDict["Progress"] = (collectionFolder.GetRefreshProgress() ?? 0).ToString(CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -378,15 +378,15 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
|
|
||||||
return new LibraryUpdateInfo
|
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()
|
CollectionFolders = GetTopParentIds(newAndRemoved, allUserRootChildren).ToArray()
|
||||||
};
|
};
|
||||||
|
@ -422,7 +422,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
var collectionFolders = _libraryManager.GetCollectionFolders(item, allUserRootChildren);
|
var collectionFolders = _libraryManager.GetCollectionFolders(item, allUserRootChildren);
|
||||||
foreach (var folder in 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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Plugins;
|
using MediaBrowser.Common.Plugins;
|
||||||
|
@ -134,7 +135,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
/// <param name="e">The e.</param>
|
/// <param name="e">The e.</param>
|
||||||
void userManager_UserDeleted(object sender, GenericEventArgs<User> e)
|
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)
|
void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -8,7 +9,6 @@ using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
@ -125,12 +125,12 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
.Select(i =>
|
.Select(i =>
|
||||||
{
|
{
|
||||||
var dto = _userDataManager.GetUserDataDto(i, user);
|
var dto = _userDataManager.GetUserDataDto(i, user);
|
||||||
dto.ItemId = i.Id.ToString("N");
|
dto.ItemId = i.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
return dto;
|
return dto;
|
||||||
})
|
})
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
var userIdString = userId.ToString("N");
|
var userIdString = userId.ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
return new UserDataChangeInfo
|
return new UserDataChangeInfo
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
@ -195,7 +196,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = options.Url;
|
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);
|
var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash);
|
||||||
|
|
||||||
|
|
|
@ -1,50 +1,43 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Server.Implementations.IO;
|
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Services;
|
using MediaBrowser.Model.Services;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.HttpServer
|
namespace Emby.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
public class FileWriter : IHttpResult
|
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 readonly IStreamHelper _streamHelper;
|
||||||
private ILogger Logger { get; set; }
|
private readonly ILogger _logger;
|
||||||
private readonly IFileSystem _fileSystem;
|
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>
|
/// <summary>
|
||||||
/// The _options
|
/// The _options
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
|
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)
|
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;
|
_fileSystem = fileSystem;
|
||||||
|
|
||||||
Path = path;
|
Path = path;
|
||||||
Logger = logger;
|
_logger = logger;
|
||||||
RangeHeader = rangeHeader;
|
RangeHeader = rangeHeader;
|
||||||
|
|
||||||
Headers[HeaderNames.ContentType] = contentType;
|
Headers[HeaderNames.ContentType] = contentType;
|
||||||
|
@ -80,6 +73,88 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
Cookies = new List<Cookie>();
|
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>
|
/// <summary>
|
||||||
/// Sets the range values.
|
/// Sets the range values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -106,59 +181,10 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}";
|
var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}";
|
||||||
Headers[HeaderNames.ContentRange] = rangeString;
|
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>
|
public async Task WriteToAsync(HttpResponse response, CancellationToken cancellationToken)
|
||||||
/// 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)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -176,16 +202,16 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
var extension = System.IO.Path.GetExtension(path);
|
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;
|
offset = 0;
|
||||||
count = 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
|
finally
|
||||||
{
|
{
|
||||||
|
@ -193,18 +219,32 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ContentType { get; set; }
|
public async Task TransmitFile(Stream stream, string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
||||||
|
|
||||||
public IRequest RequestContext { get; set; }
|
|
||||||
|
|
||||||
public object Response { get; set; }
|
|
||||||
|
|
||||||
public int Status { get; set; }
|
|
||||||
|
|
||||||
public HttpStatusCode StatusCode
|
|
||||||
{
|
{
|
||||||
get => (HttpStatusCode)Status;
|
var fileOpenOptions = FileOpenOptions.SequentialScan;
|
||||||
set => Status = (int)value;
|
|
||||||
|
// 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.Linq;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Server.Implementations.Net;
|
using Emby.Server.Implementations.Net;
|
||||||
|
@ -30,11 +29,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
public class HttpListenerHost : IHttpServer, IDisposable
|
public class HttpListenerHost : IHttpServer, IDisposable
|
||||||
{
|
{
|
||||||
private string DefaultRedirectPath { get; set; }
|
private readonly ILogger _logger;
|
||||||
public string[] UrlPrefixes { get; private set; }
|
|
||||||
|
|
||||||
public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
|
|
||||||
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly INetworkManager _networkManager;
|
private readonly INetworkManager _networkManager;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
|
@ -42,18 +37,15 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
private readonly IXmlSerializer _xmlSerializer;
|
private readonly IXmlSerializer _xmlSerializer;
|
||||||
private readonly IHttpListener _socketListener;
|
private readonly IHttpListener _socketListener;
|
||||||
private readonly Func<Type, Func<string, object>> _funcParseFn;
|
private readonly Func<Type, Func<string, object>> _funcParseFn;
|
||||||
|
private readonly string _defaultRedirectPath;
|
||||||
public Action<IRequest, IResponse, object>[] ResponseFilters { get; set; }
|
|
||||||
|
|
||||||
private readonly Dictionary<Type, Type> ServiceOperationsMap = new Dictionary<Type, Type>();
|
private readonly Dictionary<Type, Type> ServiceOperationsMap = new Dictionary<Type, Type>();
|
||||||
public static HttpListenerHost Instance { get; protected set; }
|
|
||||||
|
|
||||||
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
|
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
|
||||||
private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>();
|
private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>();
|
||||||
|
private bool _disposed = false;
|
||||||
|
|
||||||
public HttpListenerHost(
|
public HttpListenerHost(
|
||||||
IServerApplicationHost applicationHost,
|
IServerApplicationHost applicationHost,
|
||||||
ILoggerFactory loggerFactory,
|
ILogger<HttpListenerHost> logger,
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
INetworkManager networkManager,
|
INetworkManager networkManager,
|
||||||
|
@ -62,9 +54,9 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
IHttpListener socketListener)
|
IHttpListener socketListener)
|
||||||
{
|
{
|
||||||
_appHost = applicationHost;
|
_appHost = applicationHost;
|
||||||
Logger = loggerFactory.CreateLogger("HttpServer");
|
_logger = logger;
|
||||||
_config = config;
|
_config = config;
|
||||||
DefaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"];
|
_defaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"];
|
||||||
_networkManager = networkManager;
|
_networkManager = networkManager;
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_xmlSerializer = xmlSerializer;
|
_xmlSerializer = xmlSerializer;
|
||||||
|
@ -74,12 +66,20 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
|
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
|
||||||
|
|
||||||
Instance = this;
|
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; }
|
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)
|
public object CreateInstance(Type type)
|
||||||
{
|
{
|
||||||
|
@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
/// and no more processing should be done.
|
/// and no more processing should be done.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <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
|
//Exec all RequestFilter attributes with Priority < 0
|
||||||
var attributes = GetRequestFilterAttributes(requestDto.GetType());
|
var attributes = GetRequestFilterAttributes(requestDto.GetType());
|
||||||
|
@ -145,7 +145,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, Logger)
|
var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger)
|
||||||
{
|
{
|
||||||
OnReceive = ProcessWebSocketMessageReceived,
|
OnReceive = ProcessWebSocketMessageReceived,
|
||||||
Url = e.Url,
|
Url = e.Url,
|
||||||
|
@ -215,16 +215,16 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
|
|
||||||
if (logExceptionStackTrace)
|
if (logExceptionStackTrace)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Error processing request");
|
_logger.LogError(ex, "Error processing request");
|
||||||
}
|
}
|
||||||
else if (logExceptionMessage)
|
else if (logExceptionMessage)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex.Message);
|
_logger.LogError(ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpRes = httpReq.Response;
|
var httpRes = httpReq.Response;
|
||||||
|
|
||||||
if (httpRes.OriginalResponse.HasStarted)
|
if (httpRes.HasStarted)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -233,11 +233,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
httpRes.StatusCode = statusCode;
|
httpRes.StatusCode = statusCode;
|
||||||
|
|
||||||
httpRes.ContentType = "text/html";
|
httpRes.ContentType = "text/html";
|
||||||
await Write(httpRes, NormalizeExceptionMessage(ex.Message)).ConfigureAwait(false);
|
await httpRes.WriteAsync(NormalizeExceptionMessage(ex.Message)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception errorEx)
|
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.StatusCode = 503;
|
||||||
httpRes.ContentType = "text/plain";
|
httpRes.ContentType = "text/plain";
|
||||||
await Write(httpRes, "Server shutting down").ConfigureAwait(false);
|
await httpRes.WriteAsync("Server shutting down", cancellationToken).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,7 +439,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
httpRes.StatusCode = 400;
|
httpRes.StatusCode = 400;
|
||||||
httpRes.ContentType = "text/plain";
|
httpRes.ContentType = "text/plain";
|
||||||
await Write(httpRes, "Invalid host").ConfigureAwait(false);
|
await httpRes.WriteAsync("Invalid host", cancellationToken).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,7 +447,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
httpRes.StatusCode = 403;
|
httpRes.StatusCode = 403;
|
||||||
httpRes.ContentType = "text/plain";
|
httpRes.ContentType = "text/plain";
|
||||||
await Write(httpRes, "Forbidden").ConfigureAwait(false);
|
await httpRes.WriteAsync("Forbidden", cancellationToken).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,28 +460,27 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
httpRes.StatusCode = 200;
|
httpRes.StatusCode = 200;
|
||||||
httpRes.AddHeader("Access-Control-Allow-Origin", "*");
|
httpRes.Headers.Add("Access-Control-Allow-Origin", "*");
|
||||||
httpRes.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
|
httpRes.Headers.Add("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-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization");
|
||||||
httpRes.ContentType = "text/plain";
|
httpRes.ContentType = "text/plain";
|
||||||
await Write(httpRes, string.Empty).ConfigureAwait(false);
|
await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
urlToLog = GetUrlToLog(urlString);
|
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) ||
|
if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
|
string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
RedirectToUrl(httpRes, DefaultRedirectPath);
|
httpRes.Redirect(_defaultRedirectPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
|
string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
RedirectToUrl(httpRes, "emby/" + DefaultRedirectPath);
|
httpRes.Redirect("emby/" + _defaultRedirectPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -494,9 +493,10 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
|
|
||||||
if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase))
|
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=\"" +
|
"<!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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -511,34 +511,35 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
|
|
||||||
if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase))
|
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=\"" +
|
"<!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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
RedirectToUrl(httpRes, DefaultRedirectPath);
|
httpRes.Redirect(_defaultRedirectPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
RedirectToUrl(httpRes, "../" + DefaultRedirectPath);
|
httpRes.Redirect("../" + _defaultRedirectPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
RedirectToUrl(httpRes, DefaultRedirectPath);
|
httpRes.Redirect(_defaultRedirectPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(localPath))
|
if (string.IsNullOrEmpty(localPath))
|
||||||
{
|
{
|
||||||
RedirectToUrl(httpRes, "/" + DefaultRedirectPath);
|
httpRes.Redirect("/" + _defaultRedirectPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -546,12 +547,12 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
if (localPath.EndsWith("web/dashboard.html", StringComparison.OrdinalIgnoreCase))
|
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))
|
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.StatusCode = 503;
|
||||||
httpRes.ContentType = "text/html";
|
httpRes.ContentType = "text/html";
|
||||||
await Write(httpRes, GlobalResponse).ConfigureAwait(false);
|
await httpRes.WriteAsync(GlobalResponse, cancellationToken).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -571,7 +572,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
|
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
{
|
{
|
||||||
await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, cancellationToken).ConfigureAwait(false);
|
await handler.ProcessRequestAsync(this, httpReq, httpRes, _logger, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -598,11 +599,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
var elapsed = stopWatch.Elapsed;
|
var elapsed = stopWatch.Elapsed;
|
||||||
if (elapsed.TotalMilliseconds > 500)
|
if (elapsed.TotalMilliseconds > 500)
|
||||||
{
|
{
|
||||||
Logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {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);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -619,18 +616,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
return new ServiceHandler(restPath, contentType);
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task Write(IResponse response, string text)
|
private void RedirectToSecureUrl(IHttpRequest httpReq, HttpResponse httpRes, string url)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri))
|
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri))
|
||||||
{
|
{
|
||||||
|
@ -640,23 +630,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
Scheme = "https"
|
Scheme = "https"
|
||||||
};
|
};
|
||||||
url = builder.Uri.ToString();
|
url = builder.Uri.ToString();
|
||||||
|
|
||||||
RedirectToUrl(httpRes, url);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
RedirectToUrl(httpRes, url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void RedirectToUrl(IResponse httpRes, string url)
|
httpRes.Redirect(url);
|
||||||
{
|
|
||||||
httpRes.StatusCode = 302;
|
|
||||||
httpRes.AddHeader("Location", url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceController ServiceController { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the rest handlers.
|
/// Adds the rest handlers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -672,9 +650,9 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
var types = services.Select(r => r.GetType());
|
var types = services.Select(r => r.GetType());
|
||||||
ServiceController.Init(this, types);
|
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;
|
return "emby/emby/" + path;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _disposed;
|
/// <inheritdoc />
|
||||||
private readonly object _disposeLock = new object();
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (_disposed) return;
|
if (_disposed) return;
|
||||||
|
|
||||||
lock (_disposeLock)
|
if (disposing)
|
||||||
{
|
{
|
||||||
if (_disposed) return;
|
Stop();
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -803,7 +780,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogDebug("Websocket message received: {0}", result.MessageType);
|
_logger.LogDebug("Websocket message received: {0}", result.MessageType);
|
||||||
|
|
||||||
IEnumerable<Task> GetTasks()
|
IEnumerable<Task> GetTasks()
|
||||||
{
|
{
|
||||||
|
@ -815,10 +792,5 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
|
|
||||||
return Task.WhenAll(GetTasks());
|
return Task.WhenAll(GetTasks());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MediaBrowser.Model.Services;
|
using MediaBrowser.Model.Services;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
@ -9,7 +10,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
public class ResponseFilter
|
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;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public ResponseFilter(ILogger logger)
|
public ResponseFilter(ILogger logger)
|
||||||
|
@ -23,12 +24,12 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
/// <param name="req">The req.</param>
|
/// <param name="req">The req.</param>
|
||||||
/// <param name="res">The res.</param>
|
/// <param name="res">The res.</param>
|
||||||
/// <param name="dto">The dto.</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
|
// 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.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.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
|
res.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
|
||||||
res.AddHeader("Access-Control-Allow-Origin", "*");
|
res.Headers.Add("Access-Control-Allow-Origin", "*");
|
||||||
|
|
||||||
if (dto is Exception exception)
|
if (dto is Exception exception)
|
||||||
{
|
{
|
||||||
|
@ -39,7 +40,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
var error = exception.Message.Replace(Environment.NewLine, " ");
|
var error = exception.Message.Replace(Environment.NewLine, " ");
|
||||||
error = RemoveControlCharacters(error);
|
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)
|
if (hasHeaders.Headers.TryGetValue(HeaderNames.ContentLength, out string contentLength)
|
||||||
&& !string.IsNullOrEmpty(contentLength))
|
&& !string.IsNullOrEmpty(contentLength))
|
||||||
{
|
{
|
||||||
var length = long.Parse(contentLength, UsCulture);
|
var length = long.Parse(contentLength, _usCulture);
|
||||||
|
|
||||||
if (length > 0)
|
if (length > 0)
|
||||||
{
|
{
|
||||||
res.OriginalResponse.ContentLength = length;
|
res.ContentLength = length;
|
||||||
res.SendChunked = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,9 +72,12 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
public static string RemoveControlCharacters(string inString)
|
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)
|
foreach (var ch in inString)
|
||||||
{
|
{
|
||||||
|
@ -83,6 +86,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
newString.Append(ch);
|
newString.Append(ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newString.ToString();
|
return newString.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ using System.Linq;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Security;
|
using MediaBrowser.Controller.Security;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
@ -13,28 +12,23 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
{
|
{
|
||||||
public class AuthService : IAuthService
|
public class AuthService : IAuthService
|
||||||
{
|
{
|
||||||
|
private readonly IAuthorizationContext _authorizationContext;
|
||||||
|
private readonly ISessionManager _sessionManager;
|
||||||
private readonly IServerConfigurationManager _config;
|
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;
|
_config = config;
|
||||||
SessionManager = sessionManager;
|
_sessionManager = sessionManager;
|
||||||
UserManager = userManager;
|
_networkManager = networkManager;
|
||||||
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)
|
public void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues)
|
||||||
{
|
{
|
||||||
ValidateUser(request, authAttribtues);
|
ValidateUser(request, authAttribtues);
|
||||||
|
@ -43,7 +37,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
private void ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
|
private void ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
|
||||||
{
|
{
|
||||||
// This code is executed before the service
|
// This code is executed before the service
|
||||||
var auth = AuthorizationContext.GetAuthorizationInfo(request);
|
var auth = _authorizationContext.GetAuthorizationInfo(request);
|
||||||
|
|
||||||
if (!IsExemptFromAuthenticationToken(authAttribtues, request))
|
if (!IsExemptFromAuthenticationToken(authAttribtues, request))
|
||||||
{
|
{
|
||||||
|
@ -80,7 +74,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
!string.IsNullOrEmpty(auth.Client) &&
|
!string.IsNullOrEmpty(auth.Client) &&
|
||||||
!string.IsNullOrEmpty(auth.Device))
|
!string.IsNullOrEmpty(auth.Device))
|
||||||
{
|
{
|
||||||
SessionManager.LogSessionActivity(auth.Client,
|
_sessionManager.LogSessionActivity(auth.Client,
|
||||||
auth.Version,
|
auth.Version,
|
||||||
auth.DeviceId,
|
auth.DeviceId,
|
||||||
auth.Device,
|
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,
|
IAuthenticationAttributes authAttribtues,
|
||||||
AuthorizationInfo auth)
|
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.")
|
throw new SecurityException("User account has been disabled.")
|
||||||
{
|
{
|
||||||
|
@ -109,11 +105,11 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.Policy.IsAdministrator &&
|
if (!user.Policy.IsAdministrator
|
||||||
!authAttribtues.EscapeParentalControl &&
|
&& !authAttribtues.EscapeParentalControl
|
||||||
!user.IsParentalScheduleAllowed())
|
&& !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.")
|
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 (roles.Contains("delete", StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (user == null || !user.Policy.EnableContentDeletion)
|
if (user == null || !user.Policy.EnableContentDeletion)
|
||||||
|
@ -193,6 +190,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roles.Contains("download", StringComparer.OrdinalIgnoreCase))
|
if (roles.Contains("download", StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (user == null || !user.Policy.EnableContentDownloading)
|
if (user == null || !user.Policy.EnableContentDownloading)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -555,7 +556,7 @@ namespace Emby.Server.Implementations.IO
|
||||||
throw new ArgumentNullException(nameof(file2));
|
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
|
// Copying over will fail against hidden files
|
||||||
SetHidden(file1, false);
|
SetHidden(file1, false);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -89,7 +90,7 @@ namespace Emby.Server.Implementations.Images
|
||||||
ImageType imageType,
|
ImageType imageType,
|
||||||
CancellationToken cancellationToken)
|
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));
|
Directory.CreateDirectory(Path.GetDirectoryName(outputPathWithoutExtension));
|
||||||
string outputPath = CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0);
|
string outputPath = CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ using System.Text.RegularExpressions;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Resolvers;
|
using MediaBrowser.Controller.Resolvers;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library
|
namespace Emby.Server.Implementations.Library
|
||||||
|
@ -17,12 +16,10 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
private bool _ignoreDotPrefix;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public static readonly string[] IgnoreFolders =
|
private static readonly string[] _ignoreFolders =
|
||||||
{
|
{
|
||||||
"metadata",
|
"metadata",
|
||||||
"ps3_update",
|
"ps3_update",
|
||||||
|
@ -43,25 +40,14 @@ namespace Emby.Server.Implementations.Library
|
||||||
"$RECYCLE.BIN",
|
"$RECYCLE.BIN",
|
||||||
"System Volume Information",
|
"System Volume Information",
|
||||||
".grab",
|
".grab",
|
||||||
|
|
||||||
// macos
|
|
||||||
".AppleDouble"
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public CoreResolutionIgnoreRule(ILibraryManager libraryManager)
|
public CoreResolutionIgnoreRule(ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
|
||||||
_ignoreDotPrefix = Environment.OSVersion.Platform != PlatformID.Win32NT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// 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>
|
|
||||||
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent)
|
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent)
|
||||||
{
|
{
|
||||||
// Don't ignore top level folders
|
// Don't ignore top level folders
|
||||||
|
@ -73,46 +59,17 @@ namespace Emby.Server.Implementations.Library
|
||||||
var filename = fileInfo.Name;
|
var filename = fileInfo.Name;
|
||||||
var path = fileInfo.FullName;
|
var path = fileInfo.FullName;
|
||||||
|
|
||||||
// Handle mac .DS_Store
|
// Ignore hidden files on UNIX
|
||||||
// https://github.com/MediaBrowser/MediaBrowser/issues/427
|
if (Environment.OSVersion.Platform != PlatformID.Win32NT
|
||||||
if (_ignoreDotPrefix)
|
&& 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)
|
if (fileInfo.IsDirectory)
|
||||||
{
|
{
|
||||||
// Ignore any folders in our list
|
// Ignore any folders in our list
|
||||||
if (IgnoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
|
if (_ignoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -120,8 +77,9 @@ namespace Emby.Server.Implementations.Library
|
||||||
if (parent != null)
|
if (parent != null)
|
||||||
{
|
{
|
||||||
// Ignore trailer folders but allow it at the collection level
|
// Ignore trailer folders but allow it at the collection level
|
||||||
if (string.Equals(filename, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase) &&
|
if (string.Equals(filename, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase)
|
||||||
!(parent is AggregateFolder) && !(parent is UserRootFolder))
|
&& !(parent is AggregateFolder)
|
||||||
|
&& !(parent is UserRootFolder))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -142,14 +100,15 @@ namespace Emby.Server.Implementations.Library
|
||||||
if (parent != null)
|
if (parent != null)
|
||||||
{
|
{
|
||||||
// Don't resolve these into audio files
|
// 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;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore samples
|
// Ignore samples
|
||||||
Match m = Regex.Match(filename,@"\bsample\b",RegexOptions.IgnoreCase);
|
Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
return m.Success;
|
return m.Success;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,9 @@ namespace Emby.Server.Implementations.Library
|
||||||
public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser
|
public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser
|
||||||
{
|
{
|
||||||
private readonly ICryptoProvider _cryptographyProvider;
|
private readonly ICryptoProvider _cryptographyProvider;
|
||||||
public DefaultAuthenticationProvider(ICryptoProvider crypto)
|
public DefaultAuthenticationProvider(ICryptoProvider cryptographyProvider)
|
||||||
{
|
{
|
||||||
_cryptographyProvider = crypto;
|
_cryptographyProvider = cryptographyProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "Default";
|
public string Name => "Default";
|
||||||
|
@ -28,17 +28,17 @@ namespace Emby.Server.Implementations.Library
|
||||||
throw new NotImplementedException();
|
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)
|
public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)
|
||||||
{
|
{
|
||||||
bool success = false;
|
bool success = false;
|
||||||
if (resolvedUser == null)
|
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
|
// 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
|
return Task.FromResult(new ProviderAuthenticationResult
|
||||||
{
|
{
|
||||||
|
@ -50,37 +50,24 @@ namespace Emby.Server.Implementations.Library
|
||||||
byte[] passwordbytes = Encoding.UTF8.GetBytes(password);
|
byte[] passwordbytes = Encoding.UTF8.GetBytes(password);
|
||||||
|
|
||||||
PasswordHash readyHash = new PasswordHash(resolvedUser.Password);
|
PasswordHash readyHash = new PasswordHash(resolvedUser.Password);
|
||||||
byte[] calculatedHash;
|
if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)
|
||||||
string calculatedHashString;
|
|| _cryptographyProvider.DefaultHashMethod == readyHash.Id)
|
||||||
if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) || _cryptographyProvider.DefaultHashMethod == readyHash.Id)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(readyHash.Salt))
|
byte[] calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (calculatedHashString == readyHash.Hash)
|
if (calculatedHash.SequenceEqual(readyHash.Hash))
|
||||||
{
|
{
|
||||||
success = true;
|
success = true;
|
||||||
// throw new Exception("Invalid username or password");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
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)
|
if (!success)
|
||||||
{
|
{
|
||||||
throw new Exception("Invalid username or password");
|
throw new AuthenticationException("Invalid username or password");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(new ProviderAuthenticationResult
|
return Task.FromResult(new ProviderAuthenticationResult
|
||||||
|
@ -98,29 +85,22 @@ namespace Emby.Server.Implementations.Library
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.Password.Contains("$"))
|
if (user.Password.IndexOf('$') == -1)
|
||||||
{
|
{
|
||||||
string hash = user.Password;
|
string hash = user.Password;
|
||||||
user.Password = string.Format("$SHA1${0}", hash);
|
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;
|
string hash = user.EasyPassword;
|
||||||
user.EasyPassword = string.Format("$SHA1${0}", hash);
|
user.EasyPassword = string.Format("$SHA1${0}", hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> HasPassword(User user)
|
public bool HasPassword(User user)
|
||||||
{
|
=> !string.IsNullOrEmpty(user.Password);
|
||||||
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 Task ChangePassword(User user, string newPassword)
|
public Task ChangePassword(User user, string newPassword)
|
||||||
{
|
{
|
||||||
|
@ -129,30 +109,24 @@ namespace Emby.Server.Implementations.Library
|
||||||
if (string.IsNullOrEmpty(user.Password))
|
if (string.IsNullOrEmpty(user.Password))
|
||||||
{
|
{
|
||||||
PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider);
|
PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider);
|
||||||
newPasswordHash.SaltBytes = _cryptographyProvider.GenerateSalt();
|
newPasswordHash.Salt = _cryptographyProvider.GenerateSalt();
|
||||||
newPasswordHash.Salt = PasswordHash.ConvertToByteString(newPasswordHash.SaltBytes);
|
|
||||||
newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod;
|
newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod;
|
||||||
newPasswordHash.Hash = GetHashedStringChangeAuth(newPassword, newPasswordHash);
|
newPasswordHash.Hash = GetHashedChangeAuth(newPassword, newPasswordHash);
|
||||||
user.Password = newPasswordHash.ToString();
|
user.Password = newPasswordHash.ToString();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
PasswordHash passwordHash = new PasswordHash(user.Password);
|
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 = _cryptographyProvider.GenerateSalt();
|
||||||
passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes);
|
|
||||||
passwordHash.Id = _cryptographyProvider.DefaultHashMethod;
|
passwordHash.Id = _cryptographyProvider.DefaultHashMethod;
|
||||||
passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash);
|
passwordHash.Hash = GetHashedChangeAuth(newPassword, passwordHash);
|
||||||
}
|
}
|
||||||
else if (newPassword != null)
|
else if (newPassword != null)
|
||||||
{
|
{
|
||||||
passwordHash.Hash = GetHashedString(user, newPassword);
|
passwordHash.Hash = GetHashed(user, newPassword);
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(passwordHash.Hash))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(passwordHash.Hash));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Password = passwordHash.ToString();
|
user.Password = passwordHash.ToString();
|
||||||
|
@ -160,11 +134,6 @@ namespace Emby.Server.Implementations.Library
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetPasswordHash(User user)
|
|
||||||
{
|
|
||||||
return user.Password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
|
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
|
||||||
{
|
{
|
||||||
ConvertPasswordFormat(user);
|
ConvertPasswordFormat(user);
|
||||||
|
@ -190,13 +159,13 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
return string.IsNullOrEmpty(user.EasyPassword)
|
return string.IsNullOrEmpty(user.EasyPassword)
|
||||||
? null
|
? 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);
|
passwordHash.Hash = Encoding.UTF8.GetBytes(newPassword);
|
||||||
return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash));
|
return _cryptographyProvider.ComputeHash(passwordHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -215,10 +184,10 @@ namespace Emby.Server.Implementations.Library
|
||||||
passwordHash = new PasswordHash(user.Password);
|
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
|
// 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));
|
return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -227,5 +196,31 @@ namespace Emby.Server.Implementations.Library
|
||||||
return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str)));
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Authentication;
|
using MediaBrowser.Controller.Authentication;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Cryptography;
|
using MediaBrowser.Model.Cryptography;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using MediaBrowser.Model.Users;
|
using MediaBrowser.Model.Users;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library
|
namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
public class DefaultPasswordResetProvider : IPasswordResetProvider
|
public class DefaultPasswordResetProvider : IPasswordResetProvider
|
||||||
{
|
{
|
||||||
public string Name => "Default Password Reset Provider";
|
public string Name => "Default Password Reset Provider";
|
||||||
|
|
||||||
public bool IsEnabled => true;
|
public bool IsEnabled => true;
|
||||||
|
|
||||||
private readonly string _passwordResetFileBase;
|
private readonly string _passwordResetFileBase;
|
||||||
private readonly string _passwordResetFileBaseDir;
|
private readonly string _passwordResetFileBaseDir;
|
||||||
private readonly string _passwordResetFileBaseName = "passwordreset";
|
private readonly string _passwordResetFileBaseName = "passwordreset";
|
||||||
|
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly ICryptoProvider _crypto;
|
private readonly ICryptoProvider _crypto;
|
||||||
|
|
||||||
public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager, ICryptoProvider cryptoProvider)
|
public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager, ICryptoProvider cryptoProvider)
|
||||||
{
|
{
|
||||||
_passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
|
_passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
|
||||||
_passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName);
|
_passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName);
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_crypto = cryptoProvider;
|
_crypto = cryptoProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
|
public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
|
||||||
{
|
{
|
||||||
SerializablePasswordReset spr;
|
SerializablePasswordReset spr;
|
||||||
HashSet<string> usersreset = new HashSet<string>();
|
HashSet<string> usersreset = new HashSet<string>();
|
||||||
foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*"))
|
foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*"))
|
||||||
{
|
{
|
||||||
using (var str = File.OpenRead(resetfile))
|
using (var str = File.OpenRead(resetfile))
|
||||||
{
|
{
|
||||||
spr = await _jsonSerializer.DeserializeFromStreamAsync<SerializablePasswordReset>(str).ConfigureAwait(false);
|
spr = await _jsonSerializer.DeserializeFromStreamAsync<SerializablePasswordReset>(str).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spr.ExpirationDate < DateTime.Now)
|
if (spr.ExpirationDate < DateTime.Now)
|
||||||
{
|
{
|
||||||
File.Delete(resetfile);
|
File.Delete(resetfile);
|
||||||
}
|
}
|
||||||
else if (spr.Pin.Replace("-", "").Equals(pin.Replace("-", ""), StringComparison.InvariantCultureIgnoreCase))
|
else if (spr.Pin.Replace("-", "").Equals(pin.Replace("-", ""), StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
var resetUser = _userManager.GetUserByName(spr.UserName);
|
var resetUser = _userManager.GetUserByName(spr.UserName);
|
||||||
if (resetUser == null)
|
if (resetUser == null)
|
||||||
{
|
{
|
||||||
throw new Exception($"User with a username of {spr.UserName} not found");
|
throw new Exception($"User with a username of {spr.UserName} not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
|
await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
|
||||||
usersreset.Add(resetUser.Name);
|
usersreset.Add(resetUser.Name);
|
||||||
File.Delete(resetfile);
|
File.Delete(resetfile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usersreset.Count < 1)
|
if (usersreset.Count < 1)
|
||||||
{
|
{
|
||||||
throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}");
|
throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return new PinRedeemResult
|
return new PinRedeemResult
|
||||||
{
|
{
|
||||||
Success = true,
|
Success = true,
|
||||||
UsersReset = usersreset.ToArray()
|
UsersReset = usersreset.ToArray()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ForgotPasswordResult> StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
|
public async Task<ForgotPasswordResult> StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
|
||||||
{
|
{
|
||||||
string pin = string.Empty;
|
string pin = string.Empty;
|
||||||
using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create())
|
using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create())
|
||||||
{
|
{
|
||||||
byte[] bytes = new byte[4];
|
byte[] bytes = new byte[4];
|
||||||
cryptoRandom.GetBytes(bytes);
|
cryptoRandom.GetBytes(bytes);
|
||||||
pin = BitConverter.ToString(bytes);
|
pin = BitConverter.ToString(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime expireTime = DateTime.Now.AddMinutes(30);
|
DateTime expireTime = DateTime.Now.AddMinutes(30);
|
||||||
string filePath = _passwordResetFileBase + user.InternalId + ".json";
|
string filePath = _passwordResetFileBase + user.InternalId + ".json";
|
||||||
SerializablePasswordReset spr = new SerializablePasswordReset
|
SerializablePasswordReset spr = new SerializablePasswordReset
|
||||||
{
|
{
|
||||||
ExpirationDate = expireTime,
|
ExpirationDate = expireTime,
|
||||||
Pin = pin,
|
Pin = pin,
|
||||||
PinFile = filePath,
|
PinFile = filePath,
|
||||||
UserName = user.Name
|
UserName = user.Name
|
||||||
};
|
};
|
||||||
|
|
||||||
try
|
using (FileStream fileStream = File.OpenWrite(filePath))
|
||||||
{
|
{
|
||||||
using (FileStream fileStream = File.OpenWrite(filePath))
|
_jsonSerializer.SerializeToStream(spr, fileStream);
|
||||||
{
|
await fileStream.FlushAsync().ConfigureAwait(false);
|
||||||
_jsonSerializer.SerializeToStream(spr, fileStream);
|
}
|
||||||
await fileStream.FlushAsync().ConfigureAwait(false);
|
|
||||||
}
|
return new ForgotPasswordResult
|
||||||
}
|
{
|
||||||
catch (Exception e)
|
Action = ForgotPasswordAction.PinCode,
|
||||||
{
|
PinExpirationDate = expireTime,
|
||||||
throw new Exception($"Error serializing or writing password reset for {user.Name} to location: {filePath}", e);
|
PinFile = filePath
|
||||||
}
|
};
|
||||||
|
}
|
||||||
return new ForgotPasswordResult
|
|
||||||
{
|
private class SerializablePasswordReset : PasswordPinCreationResult
|
||||||
Action = ForgotPasswordAction.PinCode,
|
{
|
||||||
PinExpirationDate = expireTime,
|
public string Pin { get; set; }
|
||||||
PinFile = filePath
|
|
||||||
};
|
public string UserName { get; set; }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private class SerializablePasswordReset : PasswordPinCreationResult
|
}
|
||||||
{
|
|
||||||
public string Pin { get; set; }
|
|
||||||
|
|
||||||
public string UserName { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
@ -26,7 +27,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
EnableStreamSharing = false;
|
EnableStreamSharing = false;
|
||||||
_closeFn = closeFn;
|
_closeFn = closeFn;
|
||||||
ConsumerCount = 1;
|
ConsumerCount = 1;
|
||||||
UniqueId = Guid.NewGuid().ToString("N");
|
UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Close()
|
public Task Close()
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Authentication;
|
using MediaBrowser.Controller.Authentication;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
@ -16,12 +13,12 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
|
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)
|
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)
|
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
|
||||||
{
|
{
|
||||||
// Nothing here
|
// Nothing here
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetPasswordHash(User user)
|
public string GetPasswordHash(User user)
|
||||||
|
|
|
@ -1187,12 +1187,12 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
if (libraryFolder != null && libraryFolder.HasImage(ImageType.Primary))
|
if (libraryFolder != null && libraryFolder.HasImage(ImageType.Primary))
|
||||||
{
|
{
|
||||||
info.PrimaryImageItemId = libraryFolder.Id.ToString("N");
|
info.PrimaryImageItemId = libraryFolder.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (libraryFolder != null)
|
if (libraryFolder != null)
|
||||||
{
|
{
|
||||||
info.ItemId = libraryFolder.Id.ToString("N");
|
info.ItemId = libraryFolder.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
info.LibraryOptions = GetLibraryOptions(libraryFolder);
|
info.LibraryOptions = GetLibraryOptions(libraryFolder);
|
||||||
|
|
||||||
if (refreshQueue != null)
|
if (refreshQueue != null)
|
||||||
|
@ -2135,12 +2135,12 @@ namespace Emby.Server.Implementations.Library
|
||||||
string viewType,
|
string viewType,
|
||||||
string sortName)
|
string sortName)
|
||||||
{
|
{
|
||||||
var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N");
|
var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N", CultureInfo.InvariantCulture);
|
||||||
var idValues = "38_namedview_" + name + user.Id.ToString("N") + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
|
var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
|
||||||
|
|
||||||
var id = GetNewItemId(idValues, typeof(UserView));
|
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;
|
var item = GetItemById(id) as UserView;
|
||||||
|
|
||||||
|
@ -2271,7 +2271,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
throw new ArgumentNullException(nameof(name));
|
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);
|
var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
|
||||||
if (!string.IsNullOrEmpty(uniqueId))
|
if (!string.IsNullOrEmpty(uniqueId))
|
||||||
{
|
{
|
||||||
|
@ -2280,7 +2280,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
var id = GetNewItemId(idValues, typeof(UserView));
|
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;
|
var item = GetItemById(id) as UserView;
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
|
||||||
MediaInfo mediaInfo = null;
|
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))
|
if (!string.IsNullOrEmpty(cacheKey))
|
||||||
{
|
{
|
||||||
|
|
|
@ -269,7 +269,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
private static void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource)
|
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))
|
if (!string.IsNullOrEmpty(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
@ -626,7 +626,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
|
||||||
MediaInfo mediaInfo = null;
|
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))
|
if (!string.IsNullOrEmpty(cacheKey))
|
||||||
{
|
{
|
||||||
|
@ -854,7 +854,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
var keys = key.Split(new[] { LiveStreamIdDelimeter }, 2);
|
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 splitIndex = key.IndexOf(LiveStreamIdDelimeter);
|
||||||
var keyId = key.Substring(splitIndex + 1);
|
var keyId = key.Substring(splitIndex + 1);
|
||||||
|
|
|
@ -12,7 +12,6 @@ using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Controller.Resolvers;
|
using MediaBrowser.Controller.Resolvers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
|
@ -28,7 +27,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
/// <value>The priority.</value>
|
/// <value>The priority.</value>
|
||||||
public override ResolverPriority Priority => ResolverPriority.Third;
|
public override ResolverPriority Priority => ResolverPriority.Third;
|
||||||
|
|
||||||
public MultiItemResolverResult ResolveMultiple(Folder parent,
|
public MultiItemResolverResult ResolveMultiple(
|
||||||
|
Folder parent,
|
||||||
List<FileSystemMetadata> files,
|
List<FileSystemMetadata> files,
|
||||||
string collectionType,
|
string collectionType,
|
||||||
IDirectoryService directoryService)
|
IDirectoryService directoryService)
|
||||||
|
@ -46,7 +46,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MultiItemResolverResult ResolveMultipleInternal(Folder parent,
|
private MultiItemResolverResult ResolveMultipleInternal(
|
||||||
|
Folder parent,
|
||||||
List<FileSystemMetadata> files,
|
List<FileSystemMetadata> files,
|
||||||
string collectionType,
|
string collectionType,
|
||||||
IDirectoryService directoryService)
|
IDirectoryService directoryService)
|
||||||
|
@ -91,7 +92,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
return null;
|
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()
|
where T : Video, new()
|
||||||
{
|
{
|
||||||
var files = new List<FileSystemMetadata>();
|
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
|
// This is a hack but currently no better way to resolve a sometimes ambiguous situation
|
||||||
if (string.IsNullOrEmpty(collectionType))
|
if (string.IsNullOrEmpty(collectionType))
|
||||||
{
|
{
|
||||||
if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase)
|
||||||
string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase))
|
|| string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -115,11 +122,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
{
|
{
|
||||||
leftOver.Add(child);
|
leftOver.Add(child);
|
||||||
}
|
}
|
||||||
else if (IsIgnored(child.Name))
|
else if (!IsIgnored(child.Name))
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
files.Add(child);
|
files.Add(child);
|
||||||
}
|
}
|
||||||
|
@ -168,7 +171,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
private static bool IsIgnored(string filename)
|
private static bool IsIgnored(string filename)
|
||||||
{
|
{
|
||||||
// Ignore samples
|
// Ignore samples
|
||||||
Match m = Regex.Match(filename,@"\bsample\b",RegexOptions.IgnoreCase);
|
Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
return m.Success;
|
return m.Success;
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,7 +152,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
private static string GetCacheKey(long internalUserId, Guid itemId)
|
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)
|
public UserItemData GetUserData(User user, BaseItem item)
|
||||||
|
|
|
@ -266,6 +266,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
builder.Append(c);
|
builder.Append(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,17 +287,17 @@ namespace Emby.Server.Implementations.Library
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false);
|
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false);
|
||||||
authenticationProvider = authResult.Item1;
|
authenticationProvider = authResult.authenticationProvider;
|
||||||
updatedUsername = authResult.Item2;
|
updatedUsername = authResult.username;
|
||||||
success = authResult.Item3;
|
success = authResult.success;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// user is null
|
// user is null
|
||||||
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false);
|
var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false);
|
||||||
authenticationProvider = authResult.Item1;
|
authenticationProvider = authResult.authenticationProvider;
|
||||||
updatedUsername = authResult.Item2;
|
updatedUsername = authResult.username;
|
||||||
success = authResult.Item3;
|
success = authResult.success;
|
||||||
|
|
||||||
if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider))
|
if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider))
|
||||||
{
|
{
|
||||||
|
@ -331,22 +332,25 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
throw new SecurityException("Invalid username or password entered.");
|
throw new AuthenticationException("Invalid username or password entered.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.Policy.IsDisabled)
|
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))
|
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
|
||||||
{
|
{
|
||||||
throw new SecurityException("Forbidden.");
|
throw new AuthenticationException("Forbidden.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.IsParentalScheduleAllowed())
|
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
|
// Update LastActivityDate and LastLoginDate, then save
|
||||||
|
@ -357,6 +361,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow;
|
user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow;
|
||||||
UpdateUser(user);
|
UpdateUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateInvalidLoginAttemptCount(user, 0);
|
UpdateInvalidLoginAttemptCount(user, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -429,7 +434,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
return providers;
|
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
|
try
|
||||||
{
|
{
|
||||||
|
@ -444,23 +449,23 @@ namespace Emby.Server.Implementations.Library
|
||||||
authenticationResult = await provider.Authenticate(username, password).ConfigureAwait(false);
|
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);
|
_logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username);
|
||||||
username = 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;
|
string updatedUsername = null;
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
@ -475,15 +480,15 @@ namespace Emby.Server.Implementations.Library
|
||||||
if (password == null)
|
if (password == null)
|
||||||
{
|
{
|
||||||
// legacy
|
// 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
|
else
|
||||||
{
|
{
|
||||||
foreach (var provider in GetAuthenticationProviders(user))
|
foreach (var provider in GetAuthenticationProviders(user))
|
||||||
{
|
{
|
||||||
var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
|
var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
|
||||||
updatedUsername = providerAuthResult.Item1;
|
updatedUsername = providerAuthResult.username;
|
||||||
success = providerAuthResult.Item2;
|
success = providerAuthResult.success;
|
||||||
|
|
||||||
if (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)
|
private void UpdateInvalidLoginAttemptCount(User user, int newValue)
|
||||||
|
@ -593,7 +598,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
throw new ArgumentNullException(nameof(user));
|
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 hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user));
|
||||||
|
|
||||||
bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ?
|
bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ?
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
|
@ -117,7 +118,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
if (!query.IncludeHidden)
|
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();
|
var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList();
|
||||||
|
@ -127,7 +128,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
return list
|
return list
|
||||||
.OrderBy(i =>
|
.OrderBy(i =>
|
||||||
{
|
{
|
||||||
var index = orders.IndexOf(i.Id.ToString("N"));
|
var index = orders.IndexOf(i.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
if (index == -1)
|
if (index == -1)
|
||||||
{
|
{
|
||||||
|
@ -136,7 +137,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
if (!view.DisplayParentId.Equals(Guid.Empty))
|
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)
|
parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
||||||
.Where(i => i is Folder)
|
.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();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||||
continue;
|
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
|
_libraryManager.DeleteItem(item, new DeleteOptions
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Configuration;
|
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
|
@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||||
|
|
||||||
foreach (var item in deadEntities)
|
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
|
_libraryManager.DeleteItem(item, new DeleteOptions
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
@ -76,7 +77,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||||
|
|
||||||
foreach (var item in deadEntities)
|
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
|
_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;
|
LiveTvProgram programInfo = null;
|
||||||
|
|
||||||
|
@ -713,7 +713,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
public async Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken)
|
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
|
// populate info.seriesID
|
||||||
var program = GetProgramInfoFromCache(info.ProgramId);
|
var program = GetProgramInfoFromCache(info.ProgramId);
|
||||||
|
@ -1059,7 +1059,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
var json = _jsonSerializer.SerializeToString(mediaSource);
|
var json = _jsonSerializer.SerializeToString(mediaSource);
|
||||||
mediaSource = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
|
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)
|
//if (mediaSource.DateLiveStreamOpened.HasValue && enableStreamSharing)
|
||||||
//{
|
//{
|
||||||
|
@ -2529,7 +2529,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
var timer = new TimerInfo
|
var timer = new TimerInfo
|
||||||
{
|
{
|
||||||
ChannelId = channelId,
|
ChannelId = channelId,
|
||||||
Id = (seriesTimer.Id + parent.ExternalId).GetMD5().ToString("N"),
|
Id = (seriesTimer.Id + parent.ExternalId).GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
||||||
StartDate = parent.StartDate,
|
StartDate = parent.StartDate,
|
||||||
EndDate = parent.EndDate.Value,
|
EndDate = parent.EndDate.Value,
|
||||||
ProgramId = parent.ExternalId,
|
ProgramId = parent.ExternalId,
|
||||||
|
|
|
@ -211,7 +211,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
HasImage = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source),
|
HasImage = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source),
|
||||||
OfficialRating = program.Rating != null && !string.IsNullOrEmpty(program.Rating.Value) ? program.Rating.Value : null,
|
OfficialRating = program.Rating != null && !string.IsNullOrEmpty(program.Rating.Value) ? program.Rating.Value : null,
|
||||||
CommunityRating = program.StarRating,
|
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))
|
if (string.IsNullOrWhiteSpace(program.ProgramId))
|
||||||
|
@ -227,7 +227,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
uniqueString = "-" + programInfo.EpisodeNumber.Value.ToString(CultureInfo.InvariantCulture);
|
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 we don't have valid episode info, assume it's a unique program, otherwise recordings might be skipped
|
||||||
if (programInfo.IsSeries
|
if (programInfo.IsSeries
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -52,7 +53,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
ExternalId = info.Id,
|
ExternalId = info.Id,
|
||||||
ChannelId = GetInternalChannelId(service.Name, info.ChannelId),
|
ChannelId = GetInternalChannelId(service.Name, info.ChannelId),
|
||||||
Status = info.Status,
|
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,
|
PrePaddingSeconds = info.PrePaddingSeconds,
|
||||||
PostPaddingSeconds = info.PostPaddingSeconds,
|
PostPaddingSeconds = info.PostPaddingSeconds,
|
||||||
IsPostPaddingRequired = info.IsPostPaddingRequired,
|
IsPostPaddingRequired = info.IsPostPaddingRequired,
|
||||||
|
@ -69,7 +70,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(info.ProgramId))
|
if (!string.IsNullOrEmpty(info.ProgramId))
|
||||||
{
|
{
|
||||||
dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N");
|
dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (program != null)
|
if (program != null)
|
||||||
|
@ -107,7 +108,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
{
|
{
|
||||||
var dto = new SeriesTimerInfoDto
|
var dto = new SeriesTimerInfoDto
|
||||||
{
|
{
|
||||||
Id = GetInternalSeriesTimerId(info.Id).ToString("N"),
|
Id = GetInternalSeriesTimerId(info.Id).ToString("N", CultureInfo.InvariantCulture),
|
||||||
Overview = info.Overview,
|
Overview = info.Overview,
|
||||||
EndDate = info.EndDate,
|
EndDate = info.EndDate,
|
||||||
Name = info.Name,
|
Name = info.Name,
|
||||||
|
@ -139,7 +140,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(info.ProgramId))
|
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());
|
dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days.ToArray());
|
||||||
|
@ -169,7 +170,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image);
|
dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image);
|
||||||
dto.ParentThumbItemId = librarySeries.Id.ToString("N");
|
dto.ParentThumbItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -185,7 +186,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
{
|
{
|
||||||
_imageProcessor.GetImageCacheTag(librarySeries, image)
|
_imageProcessor.GetImageCacheTag(librarySeries, image)
|
||||||
};
|
};
|
||||||
dto.ParentBackdropItemId = librarySeries.Id.ToString("N");
|
dto.ParentBackdropItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -213,7 +214,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image);
|
dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image);
|
||||||
dto.ParentPrimaryImageItemId = program.Id.ToString("N");
|
dto.ParentPrimaryImageItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -232,7 +233,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
{
|
{
|
||||||
_imageProcessor.GetImageCacheTag(program, image)
|
_imageProcessor.GetImageCacheTag(program, image)
|
||||||
};
|
};
|
||||||
dto.ParentBackdropItemId = program.Id.ToString("N");
|
dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -263,7 +264,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image);
|
dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image);
|
||||||
dto.ParentThumbItemId = librarySeries.Id.ToString("N");
|
dto.ParentThumbItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -279,7 +280,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
{
|
{
|
||||||
_imageProcessor.GetImageCacheTag(librarySeries, image)
|
_imageProcessor.GetImageCacheTag(librarySeries, image)
|
||||||
};
|
};
|
||||||
dto.ParentBackdropItemId = librarySeries.Id.ToString("N");
|
dto.ParentBackdropItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -320,7 +321,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image);
|
dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image);
|
||||||
dto.ParentPrimaryImageItemId = program.Id.ToString("N");
|
dto.ParentPrimaryImageItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -339,7 +340,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
{
|
{
|
||||||
_imageProcessor.GetImageCacheTag(program, image)
|
_imageProcessor.GetImageCacheTag(program, image)
|
||||||
};
|
};
|
||||||
dto.ParentBackdropItemId = program.Id.ToString("N");
|
dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -407,7 +408,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
{
|
{
|
||||||
var name = ServiceName + externalId + InternalVersionNumber;
|
var name = ServiceName + externalId + InternalVersionNumber;
|
||||||
|
|
||||||
return name.ToLowerInvariant().GetMD5().ToString("N");
|
return name.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guid GetInternalSeriesTimerId(string externalId)
|
public Guid GetInternalSeriesTimerId(string externalId)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -258,7 +259,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
}
|
}
|
||||||
info.RequiresClosing = true;
|
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;
|
info.LiveStreamId = idPrefix + info.Id;
|
||||||
|
|
||||||
|
@ -820,7 +821,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
if (!string.IsNullOrWhiteSpace(query.SeriesTimerId))
|
if (!string.IsNullOrWhiteSpace(query.SeriesTimerId))
|
||||||
{
|
{
|
||||||
var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery { }, cancellationToken).ConfigureAwait(false);
|
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)
|
if (seriesTimer != null)
|
||||||
{
|
{
|
||||||
internalQuery.ExternalSeriesId = seriesTimer.SeriesId;
|
internalQuery.ExternalSeriesId = seriesTimer.SeriesId;
|
||||||
|
@ -997,7 +998,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
if (!string.IsNullOrEmpty(timer.SeriesTimerId))
|
if (!string.IsNullOrEmpty(timer.SeriesTimerId))
|
||||||
{
|
{
|
||||||
program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(timer.SeriesTimerId)
|
program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(timer.SeriesTimerId)
|
||||||
.ToString("N");
|
.ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
foundSeriesTimer = true;
|
foundSeriesTimer = true;
|
||||||
}
|
}
|
||||||
|
@ -1018,7 +1019,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
if (seriesTimer != null)
|
if (seriesTimer != null)
|
||||||
{
|
{
|
||||||
program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(seriesTimer.Id)
|
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)
|
dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId)
|
||||||
? null
|
? null
|
||||||
: _tvDtoService.GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N");
|
: _tvDtoService.GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
dto.TimerId = string.IsNullOrEmpty(info.Id)
|
dto.TimerId = string.IsNullOrEmpty(info.Id)
|
||||||
? null
|
? null
|
||||||
|
@ -2027,7 +2028,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
info.StartDate = program.StartDate;
|
info.StartDate = program.StartDate;
|
||||||
info.Name = program.Name;
|
info.Name = program.Name;
|
||||||
info.Overview = program.Overview;
|
info.Overview = program.Overview;
|
||||||
info.ProgramId = programDto.Id.ToString("N");
|
info.ProgramId = programDto.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
info.ExternalProgramId = program.ExternalId;
|
info.ExternalProgramId = program.ExternalId;
|
||||||
|
|
||||||
if (program.EndDate.HasValue)
|
if (program.EndDate.HasValue)
|
||||||
|
@ -2088,7 +2089,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
if (service is ISupportsNewTimerIds supportsNewTimerIds)
|
if (service is ISupportsNewTimerIds supportsNewTimerIds)
|
||||||
{
|
{
|
||||||
newTimerId = await supportsNewTimerIds.CreateSeriesTimer(info, cancellationToken).ConfigureAwait(false);
|
newTimerId = await supportsNewTimerIds.CreateSeriesTimer(info, cancellationToken).ConfigureAwait(false);
|
||||||
newTimerId = _tvDtoService.GetInternalSeriesTimerId(newTimerId).ToString("N");
|
newTimerId = _tvDtoService.GetInternalSeriesTimerId(newTimerId).ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -2192,7 +2193,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
|
|
||||||
info.EnabledUsers = _userManager.Users
|
info.EnabledUsers = _userManager.Users
|
||||||
.Where(IsLiveTvEnabled)
|
.Where(IsLiveTvEnabled)
|
||||||
.Select(i => i.Id.ToString("N"))
|
.Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
|
@ -2219,7 +2220,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
{
|
{
|
||||||
var parts = id.Split(new[] { '_' }, 2);
|
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)
|
if (service == null)
|
||||||
{
|
{
|
||||||
|
@ -2269,7 +2270,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
|
|
||||||
if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
|
if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
|
||||||
{
|
{
|
||||||
info.Id = Guid.NewGuid().ToString("N");
|
info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||||
list.Add(info);
|
list.Add(info);
|
||||||
config.TunerHosts = list.ToArray();
|
config.TunerHosts = list.ToArray();
|
||||||
}
|
}
|
||||||
|
@ -2312,7 +2313,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
|
|
||||||
if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
|
if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
|
||||||
{
|
{
|
||||||
info.Id = Guid.NewGuid().ToString("N");
|
info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||||
list.Add(info);
|
list.Add(info);
|
||||||
config.ListingProviders = list.ToArray();
|
config.ListingProviders = list.ToArray();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -101,7 +102,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
{
|
{
|
||||||
var openKeys = new List<string>();
|
var openKeys = new List<string>();
|
||||||
openKeys.Add(item.GetType().Name);
|
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);
|
openKeys.Add(source.Id ?? string.Empty);
|
||||||
source.OpenToken = string.Join(StreamIdDelimeterString, openKeys.ToArray());
|
source.OpenToken = string.Join(StreamIdDelimeterString, openKeys.ToArray());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
@ -11,7 +13,6 @@ using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
@ -20,7 +21,6 @@ using MediaBrowser.Model.LiveTv;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using MediaBrowser.Model.System;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
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))
|
using (var manager = new HdHomerunManager(_socketFactory, Logger))
|
||||||
{
|
{
|
||||||
// Legacy HdHomeruns are IPv4 only
|
// 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)
|
for (int i = 0; i < model.TunerCount; ++i)
|
||||||
{
|
{
|
||||||
|
@ -461,7 +461,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
id = "native";
|
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
|
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?
|
// Need a way to set the Receive timeout on the socket otherwise this might never timeout?
|
||||||
try
|
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];
|
var receiveBuffer = new byte[8192];
|
||||||
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
var response = await udpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
|
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
|
// 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)
|
if (response.ReceivedBytes > 13 && response.Buffer[1] == 3)
|
||||||
|
|
|
@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
private uint? _lockkey = null;
|
private uint? _lockkey = null;
|
||||||
private int _activeTuner = -1;
|
private int _activeTuner = -1;
|
||||||
private readonly ISocketFactory _socketFactory;
|
private readonly ISocketFactory _socketFactory;
|
||||||
private IpAddressInfo _remoteIp;
|
private IPAddress _remoteIp;
|
||||||
|
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
private ISocket _currentTcpSocket;
|
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))
|
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");
|
var lockkeyMsg = CreateGetMessage(tuner, "lockkey");
|
||||||
await socket.SendToAsync(lockkeyMsg, 0, lockkeyMsg.Length, ipEndPoint, cancellationToken);
|
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);
|
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;
|
_remoteIp = remoteIp;
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
|
||||||
var lockKeyValue = _lockkey.Value;
|
var lockKeyValue = _lockkey.Value;
|
||||||
|
|
||||||
var ipEndPoint = new IpEndPointInfo(_remoteIp, HdHomeRunPort);
|
var ipEndPoint = new IPEndPoint(_remoteIp, HdHomeRunPort);
|
||||||
|
|
||||||
for (int i = 0; i < numTuners; ++i)
|
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)
|
foreach (Tuple<string, string> command in commandList)
|
||||||
{
|
{
|
||||||
var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey);
|
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);
|
var response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
|
||||||
// parse response to make sure it worked
|
// parse response to make sure it worked
|
||||||
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out string returnVal))
|
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);
|
_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);
|
var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue);
|
||||||
await tcpClient.SendToAsync(releaseTarget, 0, releaseTarget.Length, ipEndPoint, CancellationToken.None).ConfigureAwait(false);
|
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 int _numTuners;
|
||||||
private readonly INetworkManager _networkManager;
|
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)
|
: base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths)
|
||||||
{
|
{
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
|
@ -58,7 +70,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
Logger.LogInformation("Opening HDHR UDP Live stream from {host}", uri.Host);
|
Logger.LogInformation("Opening HDHR UDP Live stream from {host}", uri.Host);
|
||||||
|
|
||||||
var remoteAddress = IPAddress.Parse(uri.Host);
|
var remoteAddress = IPAddress.Parse(uri.Host);
|
||||||
var embyRemoteAddress = _networkManager.ParseIpAddress(uri.Host);
|
|
||||||
IPAddress localAddress = null;
|
IPAddress localAddress = null;
|
||||||
using (var tcpSocket = CreateSocket(remoteAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
|
using (var tcpSocket = CreateSocket(remoteAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
|
||||||
{
|
{
|
||||||
|
@ -81,7 +92,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// send url to start streaming
|
// 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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -42,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
MediaSource = mediaSource;
|
MediaSource = mediaSource;
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
EnableStreamSharing = true;
|
EnableStreamSharing = true;
|
||||||
UniqueId = Guid.NewGuid().ToString("N");
|
UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
if (tuner != null)
|
if (tuner != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -43,7 +44,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
|
|
||||||
private string GetFullChannelIdPrefix(TunerHostInfo info)
|
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)
|
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
|
||||||
|
@ -61,7 +62,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
Name = Name,
|
Name = Name,
|
||||||
SourceType = Type,
|
SourceType = Type,
|
||||||
Status = LiveTvTunerStatus.Available,
|
Status = LiveTvTunerStatus.Available,
|
||||||
Id = i.Url.GetMD5().ToString("N"),
|
Id = i.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
||||||
Url = i.Url
|
Url = i.Url
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
@ -173,7 +174,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
|
|
||||||
ReadAtNativeFramerate = false,
|
ReadAtNativeFramerate = false,
|
||||||
|
|
||||||
Id = channel.Path.GetMD5().ToString("N"),
|
Id = channel.Path.GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
||||||
IsInfiniteStream = true,
|
IsInfiniteStream = true,
|
||||||
IsRemote = isRemote,
|
IsRemote = isRemote,
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
UserAgent = _appHost.ApplicationUserAgent
|
UserAgent = _appHost.ApplicationUserAgent
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult((Stream)File.OpenRead(url));
|
return Task.FromResult((Stream)File.OpenRead(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,11 +93,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
var channel = GetChannelnfo(extInf, tunerHostId, line);
|
var channel = GetChannelnfo(extInf, tunerHostId, line);
|
||||||
if (string.IsNullOrWhiteSpace(channel.Id))
|
if (string.IsNullOrWhiteSpace(channel.Id))
|
||||||
{
|
{
|
||||||
channel.Id = channelIdPrefix + line.GetMD5().ToString("N");
|
channel.Id = channelIdPrefix + line.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
channel.Id = channelIdPrefix + channel.Id.GetMD5().ToString("N");
|
channel.Id = channelIdPrefix + channel.Id.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.Path = line;
|
channel.Path = line;
|
||||||
|
|
|
@ -18,11 +18,11 @@
|
||||||
"HeaderAlbumArtists": "Albumkunstnere",
|
"HeaderAlbumArtists": "Albumkunstnere",
|
||||||
"HeaderCameraUploads": "Kamera Uploads",
|
"HeaderCameraUploads": "Kamera Uploads",
|
||||||
"HeaderContinueWatching": "Fortsæt Afspilning",
|
"HeaderContinueWatching": "Fortsæt Afspilning",
|
||||||
"HeaderFavoriteAlbums": "Favoritalbum",
|
"HeaderFavoriteAlbums": "Favoritalbummer",
|
||||||
"HeaderFavoriteArtists": "Favoritkunstnere",
|
"HeaderFavoriteArtists": "Favoritkunstnere",
|
||||||
"HeaderFavoriteEpisodes": "Favorit-afsnit",
|
"HeaderFavoriteEpisodes": "Favoritepisoder",
|
||||||
"HeaderFavoriteShows": "Favorit-serier",
|
"HeaderFavoriteShows": "Favoritserier",
|
||||||
"HeaderFavoriteSongs": "Favorit-sange",
|
"HeaderFavoriteSongs": "Favoritsange",
|
||||||
"HeaderLiveTV": "Live TV",
|
"HeaderLiveTV": "Live TV",
|
||||||
"HeaderNextUp": "Næste",
|
"HeaderNextUp": "Næste",
|
||||||
"HeaderRecordingGroups": "Optagelsesgrupper",
|
"HeaderRecordingGroups": "Optagelsesgrupper",
|
||||||
|
|
|
@ -19,9 +19,9 @@
|
||||||
"HeaderCameraUploads": "Kamera feltöltések",
|
"HeaderCameraUploads": "Kamera feltöltések",
|
||||||
"HeaderContinueWatching": "Folyamatban lévő filmek",
|
"HeaderContinueWatching": "Folyamatban lévő filmek",
|
||||||
"HeaderFavoriteAlbums": "Kedvenc Albumok",
|
"HeaderFavoriteAlbums": "Kedvenc Albumok",
|
||||||
"HeaderFavoriteArtists": "Kedvenc Művészek",
|
"HeaderFavoriteArtists": "Kedvenc Előadók",
|
||||||
"HeaderFavoriteEpisodes": "Kedvenc Epizódok",
|
"HeaderFavoriteEpisodes": "Kedvenc Epizódok",
|
||||||
"HeaderFavoriteShows": "Kedvenc Műsorok",
|
"HeaderFavoriteShows": "Kedvenc Sorozatok",
|
||||||
"HeaderFavoriteSongs": "Kedvenc Dalok",
|
"HeaderFavoriteSongs": "Kedvenc Dalok",
|
||||||
"HeaderLiveTV": "Élő TV",
|
"HeaderLiveTV": "Élő TV",
|
||||||
"HeaderNextUp": "Következik",
|
"HeaderNextUp": "Következik",
|
||||||
|
|
|
@ -18,11 +18,11 @@
|
||||||
"HeaderAlbumArtists": "Wykonawcy albumów",
|
"HeaderAlbumArtists": "Wykonawcy albumów",
|
||||||
"HeaderCameraUploads": "Przekazane obrazy",
|
"HeaderCameraUploads": "Przekazane obrazy",
|
||||||
"HeaderContinueWatching": "Kontynuuj odtwarzanie",
|
"HeaderContinueWatching": "Kontynuuj odtwarzanie",
|
||||||
"HeaderFavoriteAlbums": "Albumy ulubione",
|
"HeaderFavoriteAlbums": "Ulubione albumy",
|
||||||
"HeaderFavoriteArtists": "Wykonawcy ulubieni",
|
"HeaderFavoriteArtists": "Ulubieni wykonawcy",
|
||||||
"HeaderFavoriteEpisodes": "Odcinki ulubione",
|
"HeaderFavoriteEpisodes": "Ulubione odcinki",
|
||||||
"HeaderFavoriteShows": "Seriale ulubione",
|
"HeaderFavoriteShows": "Ulubione seriale",
|
||||||
"HeaderFavoriteSongs": "Utwory ulubione",
|
"HeaderFavoriteSongs": "Ulubione utwory",
|
||||||
"HeaderLiveTV": "Telewizja",
|
"HeaderLiveTV": "Telewizja",
|
||||||
"HeaderNextUp": "Do obejrzenia",
|
"HeaderNextUp": "Do obejrzenia",
|
||||||
"HeaderRecordingGroups": "Grupy nagrań",
|
"HeaderRecordingGroups": "Grupy nagrań",
|
||||||
|
@ -41,26 +41,26 @@
|
||||||
"Movies": "Filmy",
|
"Movies": "Filmy",
|
||||||
"Music": "Muzyka",
|
"Music": "Muzyka",
|
||||||
"MusicVideos": "Teledyski",
|
"MusicVideos": "Teledyski",
|
||||||
"NameInstallFailed": "Instalacja {0} nieudana.",
|
"NameInstallFailed": "Instalacja {0} nieudana",
|
||||||
"NameSeasonNumber": "Sezon {0}",
|
"NameSeasonNumber": "Sezon {0}",
|
||||||
"NameSeasonUnknown": "Sezon nieznany",
|
"NameSeasonUnknown": "Nieznany sezon",
|
||||||
"NewVersionIsAvailable": "Nowa wersja serwera Jellyfin jest dostępna do pobrania.",
|
"NewVersionIsAvailable": "Nowa wersja serwera Jellyfin jest dostępna do pobrania.",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "Dostępna aktualizacja aplikacji",
|
"NotificationOptionApplicationUpdateAvailable": "Dostępna aktualizacja aplikacji",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Zainstalowano aktualizację aplikacji",
|
"NotificationOptionApplicationUpdateInstalled": "Zaktualizowano aplikację",
|
||||||
"NotificationOptionAudioPlayback": "Rozpoczęto odtwarzanie muzyki",
|
"NotificationOptionAudioPlayback": "Rozpoczęto odtwarzanie muzyki",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Odtwarzane dźwięku zatrzymane",
|
"NotificationOptionAudioPlaybackStopped": "Odtwarzane dźwięku zatrzymane",
|
||||||
"NotificationOptionCameraImageUploaded": "Przekazano obraz z urządzenia mobilnego",
|
"NotificationOptionCameraImageUploaded": "Przekazano obraz z urządzenia przenośnego",
|
||||||
"NotificationOptionInstallationFailed": "Niepowodzenie instalacji",
|
"NotificationOptionInstallationFailed": "Nieudana instalacja",
|
||||||
"NotificationOptionNewLibraryContent": "Dodano nową zawartość",
|
"NotificationOptionNewLibraryContent": "Dodano nową zawartość",
|
||||||
"NotificationOptionPluginError": "Awaria wtyczki",
|
"NotificationOptionPluginError": "Awaria wtyczki",
|
||||||
"NotificationOptionPluginInstalled": "Zainstalowano wtyczkę",
|
"NotificationOptionPluginInstalled": "Zainstalowano wtyczkę",
|
||||||
"NotificationOptionPluginUninstalled": "Odinstalowano wtyczkę",
|
"NotificationOptionPluginUninstalled": "Odinstalowano wtyczkę",
|
||||||
"NotificationOptionPluginUpdateInstalled": "Zainstalowano aktualizację wtyczki",
|
"NotificationOptionPluginUpdateInstalled": "Zaktualizowano wtyczkę",
|
||||||
"NotificationOptionServerRestartRequired": "Wymagane ponowne uruchomienie serwera",
|
"NotificationOptionServerRestartRequired": "Wymagane ponowne uruchomienie serwera",
|
||||||
"NotificationOptionTaskFailed": "Awaria zaplanowanego zadania",
|
"NotificationOptionTaskFailed": "Awaria zaplanowanego zadania",
|
||||||
"NotificationOptionUserLockedOut": "Użytkownik zablokowany",
|
"NotificationOptionUserLockedOut": "Użytkownik zablokowany",
|
||||||
"NotificationOptionVideoPlayback": "Rozpoczęto odtwarzanie wideo",
|
"NotificationOptionVideoPlayback": "Rozpoczęto odtwarzanie wideo",
|
||||||
"NotificationOptionVideoPlaybackStopped": "Odtwarzanie wideo zatrzymane",
|
"NotificationOptionVideoPlaybackStopped": "Zatrzymano odtwarzanie wideo",
|
||||||
"Photos": "Zdjęcia",
|
"Photos": "Zdjęcia",
|
||||||
"Playlists": "Listy odtwarzania",
|
"Playlists": "Listy odtwarzania",
|
||||||
"Plugin": "Wtyczka",
|
"Plugin": "Wtyczka",
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
"ServerNameNeedsToBeRestarted": "{0} wymaga ponownego uruchomienia",
|
"ServerNameNeedsToBeRestarted": "{0} wymaga ponownego uruchomienia",
|
||||||
"Shows": "Seriale",
|
"Shows": "Seriale",
|
||||||
"Songs": "Utwory",
|
"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",
|
"SubtitleDownloadFailureForItem": "Pobieranie napisów dla {0} zakończone niepowodzeniem",
|
||||||
"SubtitleDownloadFailureFromForItem": "Nieudane pobieranie napisów z {0} dla {1}",
|
"SubtitleDownloadFailureFromForItem": "Nieudane pobieranie napisów z {0} dla {1}",
|
||||||
"SubtitlesDownloadedForItem": "Pobrano napisy dla {0}",
|
"SubtitlesDownloadedForItem": "Pobrano napisy dla {0}",
|
||||||
|
@ -91,7 +91,7 @@
|
||||||
"UserPolicyUpdatedWithName": "Zmieniono zasady użytkowania dla {0}",
|
"UserPolicyUpdatedWithName": "Zmieniono zasady użytkowania dla {0}",
|
||||||
"UserStartedPlayingItemWithValues": "{0} odtwarza {1} na {2}",
|
"UserStartedPlayingItemWithValues": "{0} odtwarza {1} na {2}",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} zakończył odtwarzanie {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}",
|
"ValueSpecialEpisodeName": "Specjalne - {0}",
|
||||||
"VersionNumber": "Wersja {0}"
|
"VersionNumber": "Wersja {0}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@
|
||||||
"HeaderFavoriteAlbums": "Álbuns Favoritos",
|
"HeaderFavoriteAlbums": "Álbuns Favoritos",
|
||||||
"HeaderFavoriteArtists": "Artistas Favoritos",
|
"HeaderFavoriteArtists": "Artistas Favoritos",
|
||||||
"HeaderFavoriteEpisodes": "Episódios Favoritos",
|
"HeaderFavoriteEpisodes": "Episódios Favoritos",
|
||||||
"HeaderFavoriteShows": "Shows Favoritos",
|
"HeaderFavoriteShows": "Séries Favoritas",
|
||||||
"HeaderFavoriteSongs": "Musicas Favoritas",
|
"HeaderFavoriteSongs": "Músicas Favoritas",
|
||||||
"HeaderLiveTV": "TV ao Vivo",
|
"HeaderLiveTV": "TV ao Vivo",
|
||||||
"HeaderNextUp": "Próximos",
|
"HeaderNextUp": "Próximos",
|
||||||
"HeaderRecordingGroups": "Grupos de Gravação",
|
"HeaderRecordingGroups": "Grupos de Gravação",
|
||||||
|
@ -32,19 +32,19 @@
|
||||||
"ItemRemovedWithName": "{0} foi removido da biblioteca",
|
"ItemRemovedWithName": "{0} foi removido da biblioteca",
|
||||||
"LabelIpAddressValue": "Endereço IP: {0}",
|
"LabelIpAddressValue": "Endereço IP: {0}",
|
||||||
"LabelRunningTimeValue": "Tempo de execução: {0}",
|
"LabelRunningTimeValue": "Tempo de execução: {0}",
|
||||||
"Latest": "Recente",
|
"Latest": "Recentes",
|
||||||
"MessageApplicationUpdated": "O servidor Jellyfin foi atualizado",
|
"MessageApplicationUpdated": "Servidor Jellyfin atualizado",
|
||||||
"MessageApplicationUpdatedTo": "O Servidor Jellyfin foi atualizado para {0}",
|
"MessageApplicationUpdatedTo": "Servidor Jellyfin atualizado para {0}",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "A seção {0} da configuração do servidor foi atualizada",
|
"MessageNamedServerConfigurationUpdatedWithValue": "A seção {0} da configuração do servidor foi atualizada",
|
||||||
"MessageServerConfigurationUpdated": "A configuração do servidor foi atualizada",
|
"MessageServerConfigurationUpdated": "A configuração do servidor foi atualizada",
|
||||||
"MixedContent": "Conteúdo misto",
|
"MixedContent": "Conteúdo misto",
|
||||||
"Movies": "Filmes",
|
"Movies": "Filmes",
|
||||||
"Music": "Música",
|
"Music": "Música",
|
||||||
"MusicVideos": "Vídeos musicais",
|
"MusicVideos": "Clipes",
|
||||||
"NameInstallFailed": "A instalação de {0} falhou",
|
"NameInstallFailed": "A instalação de {0} falhou",
|
||||||
"NameSeasonNumber": "Temporada {0}",
|
"NameSeasonNumber": "Temporada {0}",
|
||||||
"NameSeasonUnknown": "Temporada Desconhecida",
|
"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",
|
"NotificationOptionApplicationUpdateAvailable": "Atualização de aplicativo disponível",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Atualização de aplicativo instalada",
|
"NotificationOptionApplicationUpdateInstalled": "Atualização de aplicativo instalada",
|
||||||
"NotificationOptionAudioPlayback": "Reprodução de áudio iniciada",
|
"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
|
// 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.
|
// 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)
|
if (remotePort < 0)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("remotePort cannot be less than zero.", nameof(remotePort));
|
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.InterNetwork
|
||||||
: AddressFamily.InterNetworkV6;
|
: AddressFamily.InterNetworkV6;
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.Net
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new UdpSocket(retVal, new IpEndPointInfo(remoteAddress, remotePort));
|
return new UdpSocket(retVal, new IPEndPoint(remoteAddress, remotePort));
|
||||||
}
|
}
|
||||||
catch
|
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.
|
/// 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>
|
/// </summary>
|
||||||
/// <returns>An implementation of the <see cref="ISocket"/> interface used by RSSDP components to perform acceptSocket operations.</returns>
|
/// <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)
|
if (localPort < 0)
|
||||||
{
|
{
|
||||||
|
@ -115,10 +115,8 @@ namespace Emby.Server.Implementations.Net
|
||||||
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||||
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
|
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"), localIpAddress));
|
||||||
|
return new UdpSocket(retVal, localPort, localIpAddress);
|
||||||
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIp));
|
|
||||||
return new UdpSocket(retVal, localPort, localIp);
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,7 +3,6 @@ using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Server.Implementations.Networking;
|
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Net
|
namespace Emby.Server.Implementations.Net
|
||||||
|
@ -19,7 +18,7 @@ namespace Emby.Server.Implementations.Net
|
||||||
|
|
||||||
public Socket Socket => _socket;
|
public Socket Socket => _socket;
|
||||||
|
|
||||||
public IpAddressInfo LocalIPAddress { get; }
|
public IPAddress LocalIPAddress { get; }
|
||||||
|
|
||||||
private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs()
|
private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs()
|
||||||
{
|
{
|
||||||
|
@ -40,7 +39,7 @@ namespace Emby.Server.Implementations.Net
|
||||||
|
|
||||||
_socket = socket;
|
_socket = socket;
|
||||||
_localPort = localPort;
|
_localPort = localPort;
|
||||||
LocalIPAddress = NetworkManager.ToIpAddressInfo(ip);
|
LocalIPAddress = ip;
|
||||||
|
|
||||||
_socket.Bind(new IPEndPoint(ip, _localPort));
|
_socket.Bind(new IPEndPoint(ip, _localPort));
|
||||||
|
|
||||||
|
@ -71,7 +70,7 @@ namespace Emby.Server.Implementations.Net
|
||||||
{
|
{
|
||||||
Buffer = e.Buffer,
|
Buffer = e.Buffer,
|
||||||
ReceivedBytes = e.BytesTransferred,
|
ReceivedBytes = e.BytesTransferred,
|
||||||
RemoteEndPoint = ToIpEndPointInfo(e.RemoteEndPoint as IPEndPoint),
|
RemoteEndPoint = e.RemoteEndPoint as IPEndPoint,
|
||||||
LocalIPAddress = LocalIPAddress
|
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));
|
if (socket == null) throw new ArgumentNullException(nameof(socket));
|
||||||
|
|
||||||
_socket = socket;
|
_socket = socket;
|
||||||
_socket.Connect(NetworkManager.ToIPEndPoint(endPoint));
|
_socket.Connect(endPoint);
|
||||||
|
|
||||||
InitReceiveSocketAsyncEventArgs();
|
InitReceiveSocketAsyncEventArgs();
|
||||||
}
|
}
|
||||||
|
@ -140,7 +139,7 @@ namespace Emby.Server.Implementations.Net
|
||||||
return new SocketReceiveResult
|
return new SocketReceiveResult
|
||||||
{
|
{
|
||||||
ReceivedBytes = receivedBytes,
|
ReceivedBytes = receivedBytes,
|
||||||
RemoteEndPoint = ToIpEndPointInfo((IPEndPoint)remoteEndPoint),
|
RemoteEndPoint = (IPEndPoint)remoteEndPoint,
|
||||||
Buffer = buffer,
|
Buffer = buffer,
|
||||||
LocalIPAddress = LocalIPAddress
|
LocalIPAddress = LocalIPAddress
|
||||||
};
|
};
|
||||||
|
@ -191,7 +190,7 @@ namespace Emby.Server.Implementations.Net
|
||||||
return ReceiveAsync(buffer, 0, buffer.Length, cancellationToken);
|
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();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
|
@ -227,13 +226,11 @@ namespace Emby.Server.Implementations.Net
|
||||||
return taskCompletion.Task;
|
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();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
var ipEndPoint = NetworkManager.ToIPEndPoint(endPoint);
|
return _socket.BeginSendTo(buffer, offset, size, SocketFlags.None, endPoint, callback, state);
|
||||||
|
|
||||||
return _socket.BeginSendTo(buffer, offset, size, SocketFlags.None, ipEndPoint, callback, state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int EndSendTo(IAsyncResult result)
|
public int EndSendTo(IAsyncResult result)
|
||||||
|
@ -268,15 +265,5 @@ namespace Emby.Server.Implementations.Net
|
||||||
|
|
||||||
_disposed = true;
|
_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.Common.Net;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using MediaBrowser.Model.System;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Networking
|
namespace Emby.Server.Implementations.Networking
|
||||||
{
|
{
|
||||||
public class NetworkManager : INetworkManager
|
public class NetworkManager : INetworkManager
|
||||||
{
|
{
|
||||||
protected ILogger Logger { get; private set; }
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public event EventHandler NetworkChanged;
|
private IPAddress[] _localIpAddresses;
|
||||||
public Func<string[]> LocalSubnetsFn { get; set; }
|
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
|
NetworkChange.NetworkAddressChanged += OnNetworkAddressChanged;
|
||||||
if (OperatingSystem.Id != OperatingSystemId.BSD)
|
NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
|
||||||
{
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
OnNetworkChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
|
private void OnNetworkAddressChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("NetworkAddressChanged");
|
_logger.LogDebug("NetworkAddressChanged");
|
||||||
OnNetworkChanged();
|
OnNetworkChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,39 +51,35 @@ namespace Emby.Server.Implementations.Networking
|
||||||
_localIpAddresses = null;
|
_localIpAddresses = null;
|
||||||
_macAddresses = null;
|
_macAddresses = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NetworkChanged != null)
|
if (NetworkChanged != null)
|
||||||
{
|
{
|
||||||
NetworkChanged(this, EventArgs.Empty);
|
NetworkChanged(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IpAddressInfo[] _localIpAddresses;
|
public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
|
||||||
private readonly object _localIpAddressSyncLock = new object();
|
|
||||||
|
|
||||||
public IpAddressInfo[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
|
|
||||||
{
|
{
|
||||||
lock (_localIpAddressSyncLock)
|
lock (_localIpAddressSyncLock)
|
||||||
{
|
{
|
||||||
if (_localIpAddresses == null)
|
if (_localIpAddresses == null)
|
||||||
{
|
{
|
||||||
var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).Result.Select(ToIpAddressInfo).ToArray();
|
var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).ToArray();
|
||||||
|
|
||||||
_localIpAddresses = addresses;
|
_localIpAddresses = addresses;
|
||||||
|
|
||||||
return addresses;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return _localIpAddresses;
|
return _localIpAddresses;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<IPAddress>> GetLocalIpAddressesInternal(bool ignoreVirtualInterface)
|
private List<IPAddress> GetLocalIpAddressesInternal(bool ignoreVirtualInterface)
|
||||||
{
|
{
|
||||||
var list = GetIPsDefault(ignoreVirtualInterface)
|
var list = GetIPsDefault(ignoreVirtualInterface).ToList();
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (list.Count == 0)
|
if (list.Count == 0)
|
||||||
{
|
{
|
||||||
list.AddRange(await GetLocalIpAddressesFallback().ConfigureAwait(false));
|
list = GetLocalIpAddressesFallback().GetAwaiter().GetResult().ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
var listClone = list.ToList();
|
var listClone = list.ToList();
|
||||||
|
@ -116,9 +95,8 @@ namespace Emby.Server.Implementations.Networking
|
||||||
|
|
||||||
private static bool FilterIpAddress(IPAddress address)
|
private static bool FilterIpAddress(IPAddress address)
|
||||||
{
|
{
|
||||||
var addressString = address.ToString();
|
if (address.IsIPv6LinkLocal
|
||||||
|
|| address.ToString().StartsWith("169.", StringComparison.OrdinalIgnoreCase))
|
||||||
if (addressString.StartsWith("169.", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -279,7 +257,7 @@ namespace Emby.Server.Implementations.Networking
|
||||||
|
|
||||||
if (normalizedSubnet.IndexOf('/') != -1)
|
if (normalizedSubnet.IndexOf('/') != -1)
|
||||||
{
|
{
|
||||||
var ipnetwork = IPNetwork.IPNetwork.Parse(normalizedSubnet);
|
var ipnetwork = IPNetwork.Parse(normalizedSubnet);
|
||||||
if (ipnetwork.Contains(address))
|
if (ipnetwork.Contains(address))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
@ -351,13 +329,13 @@ namespace Emby.Server.Implementations.Networking
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var host = uri.DnsSafeHost;
|
var host = uri.DnsSafeHost;
|
||||||
Logger.LogDebug("Resolving host {0}", host);
|
_logger.LogDebug("Resolving host {0}", host);
|
||||||
|
|
||||||
address = GetIpAddresses(host).Result.FirstOrDefault();
|
address = GetIpAddresses(host).Result.FirstOrDefault();
|
||||||
|
|
||||||
if (address != null)
|
if (address != null)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("{0} resolved to {1}", host, address);
|
_logger.LogDebug("{0} resolved to {1}", host, address);
|
||||||
|
|
||||||
return IsInLocalNetworkInternal(address.ToString(), false);
|
return IsInLocalNetworkInternal(address.ToString(), false);
|
||||||
}
|
}
|
||||||
|
@ -368,7 +346,7 @@ namespace Emby.Server.Implementations.Networking
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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);
|
return Dns.GetHostAddressesAsync(hostName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<IPAddress> GetIPsDefault(bool ignoreVirtualInterface)
|
private IEnumerable<IPAddress> GetIPsDefault(bool ignoreVirtualInterface)
|
||||||
{
|
{
|
||||||
NetworkInterface[] interfaces;
|
IEnumerable<NetworkInterface> interfaces;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var validStatuses = new[] { OperationalStatus.Up, OperationalStatus.Unknown };
|
|
||||||
|
|
||||||
interfaces = NetworkInterface.GetAllNetworkInterfaces()
|
interfaces = NetworkInterface.GetAllNetworkInterfaces()
|
||||||
.Where(i => validStatuses.Contains(i.OperationalStatus))
|
.Where(x => x.OperationalStatus == OperationalStatus.Up
|
||||||
.ToArray();
|
|| x.OperationalStatus == OperationalStatus.Unknown);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (NetworkInformationException ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Error in GetAllNetworkInterfaces");
|
_logger.LogError(ex, "Error in GetAllNetworkInterfaces");
|
||||||
return new List<IPAddress>();
|
return Enumerable.Empty<IPAddress>();
|
||||||
}
|
}
|
||||||
|
|
||||||
return interfaces.SelectMany(network =>
|
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
|
return Enumerable.Empty<IPAddress>();
|
||||||
//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 ipProperties.UnicastAddresses
|
||||||
|
.Select(i => i.Address)
|
||||||
|
.Where(i => i.AddressFamily == AddressFamily.InterNetwork || i.AddressFamily == AddressFamily.InterNetworkV6);
|
||||||
}).GroupBy(i => i.ToString())
|
}).GroupBy(i => i.ToString())
|
||||||
.Select(x => x.First())
|
.Select(x => x.First());
|
||||||
.ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<IEnumerable<IPAddress>> GetLocalIpAddressesFallback()
|
private static async Task<IEnumerable<IPAddress>> GetLocalIpAddressesFallback()
|
||||||
|
@ -462,47 +425,27 @@ namespace Emby.Server.Implementations.Networking
|
||||||
var localEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
var localEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
||||||
using (var udpClient = new UdpClient(localEndPoint))
|
using (var udpClient = new UdpClient(localEndPoint))
|
||||||
{
|
{
|
||||||
var port = ((IPEndPoint)(udpClient.Client.LocalEndPoint)).Port;
|
var port = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port;
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> _macAddresses;
|
private List<PhysicalAddress> _macAddresses;
|
||||||
public List<string> GetMacAddresses()
|
public List<PhysicalAddress> GetMacAddresses()
|
||||||
{
|
{
|
||||||
if (_macAddresses == null)
|
if (_macAddresses == null)
|
||||||
{
|
{
|
||||||
_macAddresses = GetMacAddressesInternal();
|
_macAddresses = GetMacAddressesInternal().ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return _macAddresses;
|
return _macAddresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<string> GetMacAddressesInternal()
|
private static IEnumerable<PhysicalAddress> GetMacAddressesInternal()
|
||||||
{
|
=> NetworkInterface.GetAllNetworkInterfaces()
|
||||||
return NetworkInterface.GetAllNetworkInterfaces()
|
|
||||||
.Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback)
|
.Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback)
|
||||||
.Select(i =>
|
.Select(x => x.GetPhysicalAddress())
|
||||||
{
|
.Where(x => x != null && x != PhysicalAddress.None);
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parses the specified endpointstring.
|
/// Parses the specified endpointstring.
|
||||||
|
@ -612,32 +555,10 @@ namespace Emby.Server.Implementations.Networking
|
||||||
return hosts[0];
|
return hosts[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
public IpAddressInfo ParseIpAddress(string ipAddress)
|
public bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask)
|
||||||
{
|
{
|
||||||
if (TryParseIpAddress(ipAddress, out var info))
|
IPAddress network1 = GetNetworkAddress(address1, subnetMask);
|
||||||
{
|
IPAddress network2 = GetNetworkAddress(address2, subnetMask);
|
||||||
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));
|
|
||||||
return network1.Equals(network2);
|
return network1.Equals(network2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -656,13 +577,13 @@ namespace Emby.Server.Implementations.Networking
|
||||||
{
|
{
|
||||||
broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
|
broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new IPAddress(broadcastAddress);
|
return new IPAddress(broadcastAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IpAddressInfo GetLocalIpSubnetMask(IpAddressInfo address)
|
public IPAddress GetLocalIpSubnetMask(IPAddress address)
|
||||||
{
|
{
|
||||||
NetworkInterface[] interfaces;
|
NetworkInterface[] interfaces;
|
||||||
IPAddress ipaddress = ToIPAddress(address);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -674,7 +595,7 @@ namespace Emby.Server.Implementations.Networking
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Error in GetAllNetworkInterfaces");
|
_logger.LogError(ex, "Error in GetAllNetworkInterfaces");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -684,85 +605,17 @@ namespace Emby.Server.Implementations.Networking
|
||||||
{
|
{
|
||||||
foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
|
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;
|
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>
|
/// <summary>
|
||||||
/// Gets the network shares.
|
/// Gets the network shares.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -129,7 +130,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||||
{
|
{
|
||||||
new Share
|
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
|
CanEdit = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,7 +145,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||||
|
|
||||||
if (options.ItemIdList.Length > 0)
|
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
|
EnableImages = true
|
||||||
});
|
});
|
||||||
|
@ -152,7 +153,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||||
|
|
||||||
return new PlaylistCreationResult
|
return new PlaylistCreationResult
|
||||||
{
|
{
|
||||||
Id = playlist.Id.ToString("N")
|
Id = playlist.Id.ToString("N", CultureInfo.InvariantCulture)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -287,7 +287,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
{
|
{
|
||||||
if (_id == null)
|
if (_id == null)
|
||||||
{
|
{
|
||||||
_id = ScheduledTask.GetType().FullName.GetMD5().ToString("N");
|
_id = ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _id;
|
return _id;
|
||||||
|
|
|
@ -97,7 +97,7 @@ namespace Emby.Server.Implementations.Security
|
||||||
statement.TryBind("@AppName", info.AppName);
|
statement.TryBind("@AppName", info.AppName);
|
||||||
statement.TryBind("@AppVersion", info.AppVersion);
|
statement.TryBind("@AppVersion", info.AppVersion);
|
||||||
statement.TryBind("@DeviceName", info.DeviceName);
|
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("@UserName", info.UserName);
|
||||||
statement.TryBind("@IsActive", true);
|
statement.TryBind("@IsActive", true);
|
||||||
statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue());
|
statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue());
|
||||||
|
@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.Security
|
||||||
statement.TryBind("@AppName", info.AppName);
|
statement.TryBind("@AppName", info.AppName);
|
||||||
statement.TryBind("@AppVersion", info.AppVersion);
|
statement.TryBind("@AppVersion", info.AppVersion);
|
||||||
statement.TryBind("@DeviceName", info.DeviceName);
|
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("@UserName", info.UserName);
|
||||||
statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue());
|
statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue());
|
||||||
statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue());
|
statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue());
|
||||||
|
@ -174,7 +174,7 @@ namespace Emby.Server.Implementations.Security
|
||||||
|
|
||||||
if (!query.UserId.Equals(Guid.Empty))
|
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))
|
if (!string.IsNullOrEmpty(query.DeviceId))
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
@ -245,7 +246,7 @@ namespace Emby.Server.Implementations.Serialization
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return guid.ToString("N");
|
return guid.ToString("N", CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -10,8 +10,12 @@ namespace Emby.Server.Implementations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths
|
public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths
|
||||||
{
|
{
|
||||||
|
private string _defaultTranscodingTempPath;
|
||||||
|
private string _transcodingTempPath;
|
||||||
|
private string _internalMetadataPath;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BaseApplicationPaths" /> class.
|
/// Initializes a new instance of the <see cref="ServerApplicationPaths" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ServerApplicationPaths(
|
public ServerApplicationPaths(
|
||||||
string programDataPath,
|
string programDataPath,
|
||||||
|
@ -30,7 +34,7 @@ namespace Emby.Server.Implementations
|
||||||
public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory;
|
public string ApplicationResourcesPath { get; } = AppContext.BaseDirectory;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the base root media directory
|
/// Gets the path to the base root media directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The root folder path.</value>
|
/// <value>The root folder path.</value>
|
||||||
public string RootFolderPath => Path.Combine(ProgramDataPath, "root");
|
public string RootFolderPath => Path.Combine(ProgramDataPath, "root");
|
||||||
|
@ -48,7 +52,7 @@ namespace Emby.Server.Implementations
|
||||||
public string LocalizationPath => Path.Combine(ProgramDataPath, "localization");
|
public string LocalizationPath => Path.Combine(ProgramDataPath, "localization");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the People directory
|
/// Gets the path to the People directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The people path.</value>
|
/// <value>The people path.</value>
|
||||||
public string PeoplePath => Path.Combine(InternalMetadataPath, "People");
|
public string PeoplePath => Path.Combine(InternalMetadataPath, "People");
|
||||||
|
@ -56,37 +60,37 @@ namespace Emby.Server.Implementations
|
||||||
public string ArtistsPath => Path.Combine(InternalMetadataPath, "artists");
|
public string ArtistsPath => Path.Combine(InternalMetadataPath, "artists");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the Genre directory
|
/// Gets the path to the Genre directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The genre path.</value>
|
/// <value>The genre path.</value>
|
||||||
public string GenrePath => Path.Combine(InternalMetadataPath, "Genre");
|
public string GenrePath => Path.Combine(InternalMetadataPath, "Genre");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the Genre directory
|
/// Gets the path to the Genre directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The genre path.</value>
|
/// <value>The genre path.</value>
|
||||||
public string MusicGenrePath => Path.Combine(InternalMetadataPath, "MusicGenre");
|
public string MusicGenrePath => Path.Combine(InternalMetadataPath, "MusicGenre");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the Studio directory
|
/// Gets the path to the Studio directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The studio path.</value>
|
/// <value>The studio path.</value>
|
||||||
public string StudioPath => Path.Combine(InternalMetadataPath, "Studio");
|
public string StudioPath => Path.Combine(InternalMetadataPath, "Studio");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the Year directory
|
/// Gets the path to the Year directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The year path.</value>
|
/// <value>The year path.</value>
|
||||||
public string YearPath => Path.Combine(InternalMetadataPath, "Year");
|
public string YearPath => Path.Combine(InternalMetadataPath, "Year");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the General IBN directory
|
/// Gets the path to the General IBN directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The general path.</value>
|
/// <value>The general path.</value>
|
||||||
public string GeneralPath => Path.Combine(InternalMetadataPath, "general");
|
public string GeneralPath => Path.Combine(InternalMetadataPath, "general");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the Ratings IBN directory
|
/// Gets the path to the Ratings IBN directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The ratings path.</value>
|
/// <value>The ratings path.</value>
|
||||||
public string RatingsPath => Path.Combine(InternalMetadataPath, "ratings");
|
public string RatingsPath => Path.Combine(InternalMetadataPath, "ratings");
|
||||||
|
@ -98,15 +102,13 @@ namespace Emby.Server.Implementations
|
||||||
public string MediaInfoImagesPath => Path.Combine(InternalMetadataPath, "mediainfo");
|
public string MediaInfoImagesPath => Path.Combine(InternalMetadataPath, "mediainfo");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the user configuration directory
|
/// Gets the path to the user configuration directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The user configuration directory path.</value>
|
/// <value>The user configuration directory path.</value>
|
||||||
public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users");
|
public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users");
|
||||||
|
|
||||||
private string _defaultTranscodingTempPath;
|
|
||||||
public string DefaultTranscodingTempPath => _defaultTranscodingTempPath ?? (_defaultTranscodingTempPath = Path.Combine(ProgramDataPath, "transcoding-temp"));
|
public string DefaultTranscodingTempPath => _defaultTranscodingTempPath ?? (_defaultTranscodingTempPath = Path.Combine(ProgramDataPath, "transcoding-temp"));
|
||||||
|
|
||||||
private string _transcodingTempPath;
|
|
||||||
public string TranscodingTempPath
|
public string TranscodingTempPath
|
||||||
{
|
{
|
||||||
get => _transcodingTempPath ?? (_transcodingTempPath = DefaultTranscodingTempPath);
|
get => _transcodingTempPath ?? (_transcodingTempPath = DefaultTranscodingTempPath);
|
||||||
|
@ -139,7 +141,6 @@ namespace Emby.Server.Implementations
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _internalMetadataPath;
|
|
||||||
public string InternalMetadataPath
|
public string InternalMetadataPath
|
||||||
{
|
{
|
||||||
get => _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata"));
|
get => _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata"));
|
||||||
|
|
|
@ -10,8 +10,6 @@ namespace Emby.Server.Implementations.Services
|
||||||
public class HttpResult
|
public class HttpResult
|
||||||
: IHttpResult, IAsyncStreamWriter
|
: IHttpResult, IAsyncStreamWriter
|
||||||
{
|
{
|
||||||
public object Response { get; set; }
|
|
||||||
|
|
||||||
public HttpResult(object response, string contentType, HttpStatusCode statusCode)
|
public HttpResult(object response, string contentType, HttpStatusCode statusCode)
|
||||||
{
|
{
|
||||||
this.Headers = new Dictionary<string, string>();
|
this.Headers = new Dictionary<string, string>();
|
||||||
|
@ -21,6 +19,8 @@ namespace Emby.Server.Implementations.Services
|
||||||
this.StatusCode = statusCode;
|
this.StatusCode = statusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public object Response { get; set; }
|
||||||
|
|
||||||
public string ContentType { get; set; }
|
public string ContentType { get; set; }
|
||||||
|
|
||||||
public IDictionary<string, string> Headers { get; private 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)
|
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)
|
if (this.Response is byte[] bytesResponse)
|
||||||
{
|
{
|
||||||
|
@ -45,13 +45,14 @@ namespace Emby.Server.Implementations.Services
|
||||||
|
|
||||||
if (response != null)
|
if (response != null)
|
||||||
{
|
{
|
||||||
response.OriginalResponse.ContentLength = contentLength;
|
response.ContentLength = contentLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contentLength > 0)
|
if (contentLength > 0)
|
||||||
{
|
{
|
||||||
await responseStream.WriteAsync(bytesResponse, 0, contentLength, cancellationToken).ConfigureAwait(false);
|
await responseStream.WriteAsync(bytesResponse, 0, contentLength, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
@ -7,13 +6,14 @@ using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Server.Implementations.HttpServer;
|
using Emby.Server.Implementations.HttpServer;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using MediaBrowser.Model.Services;
|
using MediaBrowser.Model.Services;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Services
|
namespace Emby.Server.Implementations.Services
|
||||||
{
|
{
|
||||||
public static class ResponseHelper
|
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)
|
if (result == null)
|
||||||
{
|
{
|
||||||
|
@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.Services
|
||||||
response.StatusCode = (int)HttpStatusCode.NoContent;
|
response.StatusCode = (int)HttpStatusCode.NoContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
response.OriginalResponse.ContentLength = 0;
|
response.ContentLength = 0;
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,6 @@ namespace Emby.Server.Implementations.Services
|
||||||
httpResult.RequestContext = request;
|
httpResult.RequestContext = request;
|
||||||
|
|
||||||
response.StatusCode = httpResult.Status;
|
response.StatusCode = httpResult.Status;
|
||||||
response.StatusDescription = httpResult.StatusCode.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var responseOptions = result as IHasHeaders;
|
var responseOptions = result as IHasHeaders;
|
||||||
|
@ -51,11 +50,11 @@ namespace Emby.Server.Implementations.Services
|
||||||
{
|
{
|
||||||
if (string.Equals(responseHeaders.Key, "Content-Length", StringComparison.OrdinalIgnoreCase))
|
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;
|
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)
|
switch (result)
|
||||||
{
|
{
|
||||||
case IAsyncStreamWriter asyncStreamWriter:
|
case IAsyncStreamWriter asyncStreamWriter:
|
||||||
return asyncStreamWriter.WriteToAsync(response.OutputStream, cancellationToken);
|
return asyncStreamWriter.WriteToAsync(response.Body, cancellationToken);
|
||||||
case IStreamWriter streamWriter:
|
case IStreamWriter streamWriter:
|
||||||
streamWriter.WriteTo(response.OutputStream);
|
streamWriter.WriteTo(response.Body);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
case FileWriter fileWriter:
|
case FileWriter fileWriter:
|
||||||
return fileWriter.WriteToAsync(response, cancellationToken);
|
return fileWriter.WriteToAsync(response, cancellationToken);
|
||||||
case Stream stream:
|
case Stream stream:
|
||||||
return CopyStream(stream, response.OutputStream);
|
return CopyStream(stream, response.Body);
|
||||||
case byte[] bytes:
|
case byte[] bytes:
|
||||||
response.ContentType = "application/octet-stream";
|
response.ContentType = "application/octet-stream";
|
||||||
response.OriginalResponse.ContentLength = bytes.Length;
|
response.ContentLength = bytes.Length;
|
||||||
|
|
||||||
if (bytes.Length > 0)
|
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;
|
return Task.CompletedTask;
|
||||||
case string responseText:
|
case string responseText:
|
||||||
var responseTextAsBytes = Encoding.UTF8.GetBytes(responseText);
|
var responseTextAsBytes = Encoding.UTF8.GetBytes(responseText);
|
||||||
response.OriginalResponse.ContentLength = responseTextAsBytes.Length;
|
response.ContentLength = responseTextAsBytes.Length;
|
||||||
|
|
||||||
if (responseTextAsBytes.Length > 0)
|
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;
|
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 contentType = request.ResponseContentType;
|
||||||
var serializer = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType);
|
var serializer = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType);
|
||||||
|
@ -127,11 +126,11 @@ namespace Emby.Server.Implementations.Services
|
||||||
ms.Position = 0;
|
ms.Position = 0;
|
||||||
|
|
||||||
var contentLength = ms.Length;
|
var contentLength = ms.Length;
|
||||||
response.OriginalResponse.ContentLength = contentLength;
|
response.ContentLength = contentLength;
|
||||||
|
|
||||||
if (contentLength > 0)
|
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)
|
public Task<object> Execute(HttpListenerHost httpHost, object requestDto, IRequest req)
|
||||||
{
|
{
|
||||||
req.Dto = requestDto;
|
|
||||||
var requestType = requestDto.GetType();
|
var requestType = requestDto.GetType();
|
||||||
req.OperationName = requestType.Name;
|
req.OperationName = requestType.Name;
|
||||||
|
|
||||||
|
@ -161,9 +160,6 @@ namespace Emby.Server.Implementations.Services
|
||||||
serviceRequiresContext.Request = req;
|
serviceRequiresContext.Request = req;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.Dto == null) // Don't override existing batched DTO[]
|
|
||||||
req.Dto = requestDto;
|
|
||||||
|
|
||||||
//Executes the service and returns the result
|
//Executes the service and returns the result
|
||||||
return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName());
|
return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName());
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.Services
|
||||||
foreach (var requestFilter in actionContext.RequestFilters)
|
foreach (var requestFilter in actionContext.RequestFilters)
|
||||||
{
|
{
|
||||||
requestFilter.RequestFilter(request, request.Response, requestDto);
|
requestFilter.RequestFilter(request, request.Response, requestDto);
|
||||||
if (request.Response.OriginalResponse.HasStarted)
|
if (request.Response.HasStarted)
|
||||||
{
|
{
|
||||||
Task.FromResult<object>(null);
|
Task.FromResult<object>(null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,20 +5,21 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Server.Implementations.HttpServer;
|
using Emby.Server.Implementations.HttpServer;
|
||||||
using MediaBrowser.Model.Services;
|
using MediaBrowser.Model.Services;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Services
|
namespace Emby.Server.Implementations.Services
|
||||||
{
|
{
|
||||||
public class ServiceHandler
|
public class ServiceHandler
|
||||||
{
|
{
|
||||||
public RestPath RestPath { get; }
|
private RestPath _restPath;
|
||||||
|
|
||||||
public string ResponseContentType { get; }
|
private string _responseContentType;
|
||||||
|
|
||||||
internal ServiceHandler(RestPath restPath, string responseContentType)
|
internal ServiceHandler(RestPath restPath, string responseContentType)
|
||||||
{
|
{
|
||||||
RestPath = restPath;
|
_restPath = restPath;
|
||||||
ResponseContentType = responseContentType;
|
_responseContentType = responseContentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Task<object> CreateContentTypeRequest(HttpListenerHost host, IRequest httpReq, Type requestType, string contentType)
|
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)
|
private static string GetFormatContentType(string format)
|
||||||
{
|
{
|
||||||
//built-in formats
|
// built-in formats
|
||||||
switch (format)
|
switch (format)
|
||||||
{
|
{
|
||||||
case "json": return "application/json";
|
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);
|
httpHost.ApplyRequestFilters(httpReq, httpRes, request);
|
||||||
|
|
||||||
|
@ -94,7 +95,7 @@ namespace Emby.Server.Implementations.Services
|
||||||
if (RequireqRequestStream(requestType))
|
if (RequireqRequestStream(requestType))
|
||||||
{
|
{
|
||||||
// Used by IRequiresRequestStream
|
// 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 request = ServiceHandler.CreateRequest(httpReq, restPath, requestParams, host.CreateInstance(requestType));
|
||||||
|
|
||||||
var rawReq = (IRequiresRequestStream)request;
|
var rawReq = (IRequiresRequestStream)request;
|
||||||
|
@ -103,7 +104,7 @@ namespace Emby.Server.Implementations.Services
|
||||||
}
|
}
|
||||||
else
|
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);
|
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)
|
public static object CreateRequest(IRequest httpReq, RestPath restPath, Dictionary<string, string> requestParams, object requestDto)
|
||||||
{
|
{
|
||||||
var pathInfo = !restPath.IsWildCardPath
|
var pathInfo = !restPath.IsWildCardPath
|
||||||
? GetSanitizedPathInfo(httpReq.PathInfo, out string contentType)
|
? GetSanitizedPathInfo(httpReq.PathInfo, out _)
|
||||||
: httpReq.PathInfo;
|
: httpReq.PathInfo;
|
||||||
|
|
||||||
return restPath.CreateRequest(pathInfo, requestParams, requestDto);
|
return restPath.CreateRequest(pathInfo, requestParams, requestDto);
|
||||||
|
@ -130,56 +131,41 @@ namespace Emby.Server.Implementations.Services
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Duplicate Params are given a unique key by appending a #1 suffix
|
/// Duplicate Params are given a unique key by appending a #1 suffix
|
||||||
/// </summary>
|
/// </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>();
|
var map = new Dictionary<string, string>();
|
||||||
|
|
||||||
foreach (var name in request.QueryString.Keys)
|
foreach (var pair in request.Query)
|
||||||
{
|
{
|
||||||
if (name == null)
|
var values = pair.Value;
|
||||||
{
|
|
||||||
// thank you ASP.NET
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var values = request.QueryString[name];
|
|
||||||
if (values.Count == 1)
|
if (values.Count == 1)
|
||||||
{
|
{
|
||||||
map[name] = values[0];
|
map[pair.Key] = values[0];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (var i = 0; i < values.Count; i++)
|
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);
|
foreach (var pair in request.Form)
|
||||||
if (formData != null)
|
|
||||||
{
|
{
|
||||||
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
|
map[pair.Key + (i == 0 ? string.Empty : "#" + i)] = values[i];
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,43 +175,26 @@ namespace Emby.Server.Implementations.Services
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsMethod(string method, string expected)
|
private static bool IsMethod(string method, string expected)
|
||||||
{
|
=> string.Equals(method, expected, StringComparison.OrdinalIgnoreCase);
|
||||||
return string.Equals(method, expected, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Duplicate params have their values joined together in a comma-delimited string
|
/// Duplicate params have their values joined together in a comma-delimited string
|
||||||
/// </summary>
|
/// </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>();
|
var map = new Dictionary<string, string>();
|
||||||
|
|
||||||
foreach (var name in request.QueryString.Keys)
|
foreach (var pair in request.Query)
|
||||||
{
|
{
|
||||||
if (name == null)
|
map[pair.Key] = pair.Value;
|
||||||
{
|
|
||||||
// thank you ASP.NET
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
map[name] = request.QueryString[name];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
foreach (var pair in request.Form)
|
||||||
if (formData != null)
|
|
||||||
{
|
{
|
||||||
foreach (var name in formData.Keys)
|
map[pair.Key] = pair.Value;
|
||||||
{
|
|
||||||
if (name == null)
|
|
||||||
{
|
|
||||||
// thank you ASP.NET
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
map[name] = formData[name];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
{
|
{
|
||||||
var dict = new Dictionary<string, string>();
|
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)
|
if (command.StartPositionTicks.HasValue)
|
||||||
{
|
{
|
||||||
|
|
|
@ -327,7 +327,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(info.MediaSourceId))
|
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)
|
if (!info.ItemId.Equals(Guid.Empty) && info.Item == null && libraryItem != null)
|
||||||
|
@ -463,7 +463,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
Client = appName,
|
Client = appName,
|
||||||
DeviceId = deviceId,
|
DeviceId = deviceId,
|
||||||
ApplicationVersion = appVersion,
|
ApplicationVersion = appVersion,
|
||||||
Id = key.GetMD5().ToString("N"),
|
Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
||||||
ServerId = _appHost.SystemId
|
ServerId = _appHost.SystemId
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -845,7 +845,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
// Normalize
|
// Normalize
|
||||||
if (string.IsNullOrEmpty(info.MediaSourceId))
|
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)
|
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)
|
private static async Task SendMessageToSession<T>(SessionInfo session, string name, T data, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var controllers = session.SessionControllers.ToArray();
|
var controllers = session.SessionControllers.ToArray();
|
||||||
var messageId = Guid.NewGuid().ToString("N");
|
var messageId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
foreach (var controller in controllers)
|
foreach (var controller in controllers)
|
||||||
{
|
{
|
||||||
|
@ -1041,7 +1041,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
{
|
{
|
||||||
IEnumerable<Task> GetTasks()
|
IEnumerable<Task> GetTasks()
|
||||||
{
|
{
|
||||||
var messageId = Guid.NewGuid().ToString("N");
|
var messageId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||||
foreach (var session in sessions)
|
foreach (var session in sessions)
|
||||||
{
|
{
|
||||||
var controllers = session.SessionControllers;
|
var controllers = session.SessionControllers;
|
||||||
|
@ -1234,7 +1234,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
AssertCanControl(session, controllingSession);
|
AssertCanControl(session, controllingSession);
|
||||||
if (!controllingSession.UserId.Equals(Guid.Empty))
|
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,
|
DeviceId = deviceId,
|
||||||
DeviceName = deviceName,
|
DeviceName = deviceName,
|
||||||
UserId = user.Id,
|
UserId = user.Id,
|
||||||
AccessToken = Guid.NewGuid().ToString("N"),
|
AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture),
|
||||||
UserName = user.Name
|
UserName = user.Name
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1822,6 +1822,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase));
|
var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
return SendMessageToSessions(sessions, name, data, cancellationToken);
|
return SendMessageToSessions(sessions, name, data, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1831,6 +1832,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
|
|
||||||
var sessions = Sessions
|
var sessions = Sessions
|
||||||
.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i));
|
.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i));
|
||||||
|
|
||||||
return SendMessageToSessions(sessions, name, data, cancellationToken);
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Model.Services;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Extensions;
|
using Microsoft.AspNetCore.Http.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
using IHttpFile = MediaBrowser.Model.Services.IHttpFile;
|
|
||||||
using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest;
|
using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest;
|
||||||
using IResponse = MediaBrowser.Model.Services.IResponse;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.SocketSharp
|
namespace Emby.Server.Implementations.SocketSharp
|
||||||
{
|
{
|
||||||
public partial class WebSocketSharpRequest : IHttpRequest
|
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.OperationName = operationName;
|
||||||
this.request = httpContext;
|
this.Request = httpRequest;
|
||||||
this.Response = new WebSocketSharpResponse(logger, response);
|
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 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
|
public string RemoteIp
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (remoteIp != null)
|
if (_remoteIp != null)
|
||||||
{
|
{
|
||||||
return remoteIp;
|
return _remoteIp;
|
||||||
}
|
}
|
||||||
|
|
||||||
IPAddress ip;
|
IPAddress ip;
|
||||||
|
@ -62,14 +61,51 @@ namespace Emby.Server.Implementations.SocketSharp
|
||||||
{
|
{
|
||||||
if (!IPAddress.TryParse(GetHeader(CustomHeaderNames.XRealIP), out ip))
|
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)
|
private static IPAddress NormalizeIp(IPAddress ip)
|
||||||
{
|
{
|
||||||
if (ip.IsIPv4MappedToIPv6)
|
if (ip.IsIPv4MappedToIPv6)
|
||||||
|
@ -80,22 +116,6 @@ namespace Emby.Server.Implementations.SocketSharp
|
||||||
return ip;
|
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)
|
public static string GetResponseContentType(HttpRequest httpReq)
|
||||||
{
|
{
|
||||||
var specifiedContentType = GetQueryStringContentType(httpReq);
|
var specifiedContentType = GetQueryStringContentType(httpReq);
|
||||||
|
@ -152,8 +172,6 @@ namespace Emby.Server.Implementations.SocketSharp
|
||||||
return serverDefaultContentType;
|
return serverDefaultContentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public const string Soap11 = "text/xml; charset=utf-8";
|
|
||||||
|
|
||||||
public static bool HasAnyOfContentTypes(HttpRequest request, params string[] contentTypes)
|
public static bool HasAnyOfContentTypes(HttpRequest request, params string[] contentTypes)
|
||||||
{
|
{
|
||||||
if (contentTypes == null || request.ContentType == null)
|
if (contentTypes == null || request.ContentType == null)
|
||||||
|
@ -224,105 +242,5 @@ namespace Emby.Server.Implementations.SocketSharp
|
||||||
var pos = strVal.IndexOf(needle);
|
var pos = strVal.IndexOf(needle);
|
||||||
return pos == -1 ? strVal : strVal.Slice(0, pos);
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
|
@ -73,7 +74,7 @@ namespace Emby.Server.Implementations.TV
|
||||||
{
|
{
|
||||||
parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
||||||
.Where(i => i is Folder)
|
.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();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -25,7 +26,7 @@ namespace Emby.Server.Implementations.Udp
|
||||||
|
|
||||||
private bool _isDisposed;
|
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 IServerApplicationHost _appHost;
|
||||||
private readonly IJsonSerializer _json;
|
private readonly IJsonSerializer _json;
|
||||||
|
@ -43,9 +44,9 @@ namespace Emby.Server.Implementations.Udp
|
||||||
AddMessageResponder("who is JellyfinServer?", true, RespondToV2Message);
|
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>
|
/// <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 text = encoding.GetString(buffer, 0, bytesReceived);
|
||||||
var responder = _responders.FirstOrDefault(i =>
|
var responder = _responders.FirstOrDefault(i =>
|
||||||
|
@ -99,10 +100,10 @@ namespace Emby.Server.Implementations.Udp
|
||||||
{
|
{
|
||||||
return null;
|
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('|');
|
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)
|
if (_isDisposed)
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,7 +18,6 @@ using Jellyfin.Drawing.Skia;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
@ -41,12 +40,12 @@ namespace Jellyfin.Server
|
||||||
// For backwards compatibility.
|
// For backwards compatibility.
|
||||||
// Modify any input arguments now which start with single-hyphen to POSIX standard
|
// Modify any input arguments now which start with single-hyphen to POSIX standard
|
||||||
// double-hyphen to allow parsing by CommandLineParser package.
|
// double-hyphen to allow parsing by CommandLineParser package.
|
||||||
const string pattern = @"^(-[^-\s]{2})"; // Match -xx, not -x, not --xx, not xx
|
const string Pattern = @"^(-[^-\s]{2})"; // Match -xx, not -x, not --xx, not xx
|
||||||
const string substitution = @"-$1"; // Prepend with additional single-hyphen
|
const string Substitution = @"-$1"; // Prepend with additional single-hyphen
|
||||||
var regex = new Regex(pattern);
|
var regex = new Regex(Pattern);
|
||||||
for (var i = 0; i < args.Length; i++)
|
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
|
// Parse the command line arguments and either start the app or exit indicating error
|
||||||
|
@ -134,7 +133,7 @@ namespace Jellyfin.Server
|
||||||
Batteries_V2.Init();
|
Batteries_V2.Init();
|
||||||
if (raw.sqlite3_enable_shared_cache(1) != raw.SQLITE_OK)
|
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(
|
using (var appHost = new CoreAppHost(
|
||||||
|
@ -143,7 +142,7 @@ namespace Jellyfin.Server
|
||||||
options,
|
options,
|
||||||
new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
|
new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
|
||||||
new NullImageEncoder(),
|
new NullImageEncoder(),
|
||||||
new NetworkManager(_loggerFactory),
|
new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()),
|
||||||
appConfig))
|
appConfig))
|
||||||
{
|
{
|
||||||
await appHost.InitAsync(new ServiceCollection()).ConfigureAwait(false);
|
await appHost.InitAsync(new ServiceCollection()).ConfigureAwait(false);
|
||||||
|
|
|
@ -133,8 +133,21 @@ namespace MediaBrowser.Api.Devices
|
||||||
var album = Request.QueryString["Album"];
|
var album = Request.QueryString["Album"];
|
||||||
var id = Request.QueryString["Id"];
|
var id = Request.QueryString["Id"];
|
||||||
var name = Request.QueryString["Name"];
|
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
|
return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo
|
||||||
{
|
{
|
||||||
|
@ -144,18 +157,6 @@ namespace MediaBrowser.Api.Devices
|
||||||
Id = id
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -537,7 +538,7 @@ namespace MediaBrowser.Api.Images
|
||||||
|
|
||||||
if (item == null)
|
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…
Reference in a new issue