aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Directory.Packages.props2
-rw-r--r--Emby.Naming/AudioBook/AudioBookFilePathParser.cs4
-rw-r--r--Emby.Naming/AudioBook/AudioBookNameParser.cs2
-rw-r--r--Emby.Naming/Common/NamingOptions.cs32
-rw-r--r--Emby.Naming/TV/EpisodePathParser.cs14
-rw-r--r--Emby.Naming/Video/CleanDateTimeParser.cs2
-rw-r--r--Emby.Naming/Video/ExtraRuleResolver.cs2
-rw-r--r--Emby.Naming/Video/VideoListResolver.cs9
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs9
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-AR.json4
-rw-r--r--Jellyfin.Api/Controllers/ArtistsController.cs9
-rw-r--r--Jellyfin.Api/Controllers/ChannelsController.cs9
-rw-r--r--Jellyfin.Api/Controllers/DevicesController.cs2
-rw-r--r--Jellyfin.Api/Controllers/FilterController.cs8
-rw-r--r--Jellyfin.Api/Controllers/GenresController.cs6
-rw-r--r--Jellyfin.Api/Controllers/InstantMixController.cs22
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs3
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs22
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs26
-rw-r--r--Jellyfin.Api/Controllers/MediaInfoController.cs5
-rw-r--r--Jellyfin.Api/Controllers/MoviesController.cs4
-rw-r--r--Jellyfin.Api/Controllers/MusicGenresController.cs6
-rw-r--r--Jellyfin.Api/Controllers/PersonsController.cs7
-rw-r--r--Jellyfin.Api/Controllers/PlaylistsController.cs8
-rw-r--r--Jellyfin.Api/Controllers/QuickConnectController.cs11
-rw-r--r--Jellyfin.Api/Controllers/SearchController.cs5
-rw-r--r--Jellyfin.Api/Controllers/StudiosController.cs6
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs15
-rw-r--r--Jellyfin.Api/Controllers/UniversalAudioController.cs6
-rw-r--r--Jellyfin.Api/Controllers/VideosController.cs5
-rw-r--r--Jellyfin.Api/Controllers/YearsController.cs6
-rw-r--r--Jellyfin.Api/Helpers/RequestHelpers.cs27
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs6
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs2
-rw-r--r--tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs80
-rw-r--r--tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs2
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs4
38 files changed, 258 insertions, 136 deletions
diff --git a/Directory.Packages.props b/Directory.Packages.props
index d5f762fe8..4024f0c05 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -53,7 +53,7 @@
<PackageVersion Include="NEbml" Version="0.11.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.2" />
<PackageVersion Include="PlaylistsNET" Version="1.3.1" />
- <PackageVersion Include="prometheus-net.AspNetCore" Version="7.0.0" />
+ <PackageVersion Include="prometheus-net.AspNetCore" Version="8.0.0" />
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
<PackageVersion Include="prometheus-net" Version="8.0.0" />
<PackageVersion Include="Serilog.AspNetCore" Version="6.1.0" />
diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs
index 7b4429ab1..219599d56 100644
--- a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs
+++ b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs
@@ -40,7 +40,7 @@ namespace Emby.Naming.AudioBook
var value = match.Groups["chapter"];
if (value.Success)
{
- if (int.TryParse(value.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
+ if (int.TryParse(value.ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
{
result.ChapterNumber = intValue;
}
@@ -52,7 +52,7 @@ namespace Emby.Naming.AudioBook
var value = match.Groups["part"];
if (value.Success)
{
- if (int.TryParse(value.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
+ if (int.TryParse(value.ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
{
result.PartNumber = intValue;
}
diff --git a/Emby.Naming/AudioBook/AudioBookNameParser.cs b/Emby.Naming/AudioBook/AudioBookNameParser.cs
index 97b34199e..f49c3f0e7 100644
--- a/Emby.Naming/AudioBook/AudioBookNameParser.cs
+++ b/Emby.Naming/AudioBook/AudioBookNameParser.cs
@@ -47,7 +47,7 @@ namespace Emby.Naming.AudioBook
var value = match.Groups["year"];
if (value.Success)
{
- if (int.TryParse(value.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
+ if (int.TryParse(value.ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
{
result.Year = intValue;
}
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index b9c98c942..e9161a6b7 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -461,16 +461,6 @@ namespace Emby.Naming.Common
},
};
- EpisodeWithoutSeasonExpressions = new[]
- {
- @"[/\._ \-]()([0-9]+)(-[0-9]+)?"
- };
-
- EpisodeMultiPartExpressions = new[]
- {
- @"^[-_ex]+([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)"
- };
-
VideoExtraRules = new[]
{
new ExtraRule(
@@ -806,16 +796,6 @@ namespace Emby.Naming.Common
public EpisodeExpression[] EpisodeExpressions { get; set; }
/// <summary>
- /// Gets or sets list of raw episode without season regular expressions strings.
- /// </summary>
- public string[] EpisodeWithoutSeasonExpressions { get; set; }
-
- /// <summary>
- /// Gets or sets list of raw multi-part episodes regular expressions strings.
- /// </summary>
- public string[] EpisodeMultiPartExpressions { get; set; }
-
- /// <summary>
/// Gets or sets list of video file extensions.
/// </summary>
public string[] VideoFileExtensions { get; set; }
@@ -886,24 +866,12 @@ namespace Emby.Naming.Common
public Regex[] CleanStringRegexes { get; private set; } = Array.Empty<Regex>();
/// <summary>
- /// Gets list of episode without season regular expressions.
- /// </summary>
- public Regex[] EpisodeWithoutSeasonRegexes { get; private set; } = Array.Empty<Regex>();
-
- /// <summary>
- /// Gets list of multi-part episode regular expressions.
- /// </summary>
- public Regex[] EpisodeMultiPartRegexes { get; private set; } = Array.Empty<Regex>();
-
- /// <summary>
/// Compiles raw regex strings into regexes.
/// </summary>
public void Compile()
{
CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray();
CleanStringRegexes = CleanStrings.Select(Compile).ToArray();
- EpisodeWithoutSeasonRegexes = EpisodeWithoutSeasonExpressions.Select(Compile).ToArray();
- EpisodeMultiPartRegexes = EpisodeMultiPartExpressions.Select(Compile).ToArray();
}
private Regex Compile(string exp)
diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs
index d706be280..8cd5a126e 100644
--- a/Emby.Naming/TV/EpisodePathParser.cs
+++ b/Emby.Naming/TV/EpisodePathParser.cs
@@ -113,7 +113,7 @@ namespace Emby.Naming.TV
if (expression.DateTimeFormats.Length > 0)
{
if (DateTime.TryParseExact(
- match.Groups[0].Value,
+ match.Groups[0].ValueSpan,
expression.DateTimeFormats,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
@@ -125,7 +125,7 @@ namespace Emby.Naming.TV
result.Success = true;
}
}
- else if (DateTime.TryParse(match.Groups[0].Value, out date))
+ else if (DateTime.TryParse(match.Groups[0].ValueSpan, out date))
{
result.Year = date.Year;
result.Month = date.Month;
@@ -138,12 +138,12 @@ namespace Emby.Naming.TV
}
else if (expression.IsNamed)
{
- if (int.TryParse(match.Groups["seasonnumber"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var num))
+ if (int.TryParse(match.Groups["seasonnumber"].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var num))
{
result.SeasonNumber = num;
}
- if (int.TryParse(match.Groups["epnumber"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
+ if (int.TryParse(match.Groups["epnumber"].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
{
result.EpisodeNumber = num;
}
@@ -158,7 +158,7 @@ namespace Emby.Naming.TV
if (nextIndex >= name.Length
|| !"0123456789iIpP".Contains(name[nextIndex], StringComparison.Ordinal))
{
- if (int.TryParse(endingNumberGroup.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
+ if (int.TryParse(endingNumberGroup.ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
{
result.EndingEpisodeNumber = num;
}
@@ -170,12 +170,12 @@ namespace Emby.Naming.TV
}
else
{
- if (int.TryParse(match.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var num))
+ if (int.TryParse(match.Groups[1].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var num))
{
result.SeasonNumber = num;
}
- if (int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
+ if (int.TryParse(match.Groups[2].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
{
result.EpisodeNumber = num;
}
diff --git a/Emby.Naming/Video/CleanDateTimeParser.cs b/Emby.Naming/Video/CleanDateTimeParser.cs
index 0ee633dcc..9a6c6e978 100644
--- a/Emby.Naming/Video/CleanDateTimeParser.cs
+++ b/Emby.Naming/Video/CleanDateTimeParser.cs
@@ -43,7 +43,7 @@ namespace Emby.Naming.Video
&& match.Groups.Count == 5
&& match.Groups[1].Success
&& match.Groups[2].Success
- && int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
+ && int.TryParse(match.Groups[2].ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
{
result = new CleanDateTimeResult(match.Groups[1].Value.TrimEnd(), year);
return true;
diff --git a/Emby.Naming/Video/ExtraRuleResolver.cs b/Emby.Naming/Video/ExtraRuleResolver.cs
index 21d0da364..3219472ef 100644
--- a/Emby.Naming/Video/ExtraRuleResolver.cs
+++ b/Emby.Naming/Video/ExtraRuleResolver.cs
@@ -56,7 +56,7 @@ namespace Emby.Naming.Video
}
else if (rule.RuleType == ExtraRuleType.Regex)
{
- var filename = Path.GetFileName(path);
+ var filename = Path.GetFileName(path.AsSpan());
var isMatch = Regex.IsMatch(filename, rule.Token, RegexOptions.IgnoreCase | RegexOptions.Compiled);
diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs
index 01e383d1c..8247c374d 100644
--- a/Emby.Naming/Video/VideoListResolver.cs
+++ b/Emby.Naming/Video/VideoListResolver.cs
@@ -176,16 +176,15 @@ namespace Emby.Naming.Video
}
// There are no span overloads for regex unfortunately
- var tmpTestFilename = testFilename.ToString();
- if (CleanStringParser.TryClean(tmpTestFilename, namingOptions.CleanStringRegexes, out var cleanName))
+ if (CleanStringParser.TryClean(testFilename.ToString(), namingOptions.CleanStringRegexes, out var cleanName))
{
- tmpTestFilename = cleanName.Trim();
+ testFilename = cleanName.AsSpan().Trim();
}
// The CleanStringParser should have removed common keywords etc.
- return string.IsNullOrEmpty(tmpTestFilename)
+ return testFilename.IsEmpty
|| testFilename[0] == '-'
- || Regex.IsMatch(tmpTestFilename, @"^\[([^]]*)\]", RegexOptions.Compiled);
+ || Regex.IsMatch(testFilename, @"^\[([^]]*)\]", RegexOptions.Compiled);
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 1522cd3ae..ef4fa1fd2 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -313,13 +313,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return result;
}
- private static bool IsIgnored(string filename)
- {
- // Ignore samples
- Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase | RegexOptions.Compiled);
-
- return m.Success;
- }
+ private static bool IsIgnored(ReadOnlySpan<char> filename)
+ => Regex.IsMatch(filename, @"\bsample\b", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static bool ContainsFile(IReadOnlyList<VideoInfo> result, FileSystemMetadata file)
{
diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json
index 8ad9e8c71..8bd3c5def 100644
--- a/Emby.Server.Implementations/Localization/Core/es-AR.json
+++ b/Emby.Server.Implementations/Localization/Core/es-AR.json
@@ -118,11 +118,11 @@
"TaskCleanActivityLog": "Borrar log de actividades",
"Undefined": "Indefinido",
"Forced": "Forzado",
- "Default": "Por Defecto",
+ "Default": "Predeterminado",
"TaskOptimizeDatabaseDescription": "Compacta la base de datos y restaura el espacio libre. Ejecutar esta tarea después de actualizar las librerías o realizar otros cambios que impliquen modificar las bases de datos puede mejorar la performance.",
"TaskOptimizeDatabase": "Optimización de base de datos",
"External": "Externo",
"TaskKeyframeExtractorDescription": "Extrae Fotogramas Clave de los archivos de vídeo para crear Listas de Reprodución HLS más precisas. Esta tarea puede durar mucho tiempo.",
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave",
- "HearingImpaired": "Personas con discapacidad auditiva"
+ "HearingImpaired": "Discapacidad Auditiva"
}
diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs
index 11933fd97..c9d2f67f9 100644
--- a/Jellyfin.Api/Controllers/ArtistsController.cs
+++ b/Jellyfin.Api/Controllers/ArtistsController.cs
@@ -118,6 +118,7 @@ public class ArtistsController : BaseJellyfinApiController
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@@ -125,7 +126,7 @@ public class ArtistsController : BaseJellyfinApiController
User? user = null;
BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
- if (userId.HasValue && !userId.Equals(default))
+ if (!userId.Value.Equals(default))
{
user = _userManager.GetUserById(userId.Value);
}
@@ -321,6 +322,7 @@ public class ArtistsController : BaseJellyfinApiController
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@@ -328,7 +330,7 @@ public class ArtistsController : BaseJellyfinApiController
User? user = null;
BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
- if (userId.HasValue && !userId.Equals(default))
+ if (!userId.Value.Equals(default))
{
user = _userManager.GetUserById(userId.Value);
}
@@ -462,11 +464,12 @@ public class ArtistsController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<BaseItemDto> GetArtistByName([FromRoute, Required] string name, [FromQuery] Guid? userId)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var dtoOptions = new DtoOptions().AddClientFields(User);
var item = _libraryManager.GetArtist(name, dtoOptions);
- if (userId.HasValue && !userId.Value.Equals(default))
+ if (!userId.Value.Equals(default))
{
var user = _userManager.GetUserById(userId.Value);
diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs
index 42f072f66..b5c4d8346 100644
--- a/Jellyfin.Api/Controllers/ChannelsController.cs
+++ b/Jellyfin.Api/Controllers/ChannelsController.cs
@@ -60,11 +60,12 @@ public class ChannelsController : BaseJellyfinApiController
[FromQuery] bool? supportsMediaDeletion,
[FromQuery] bool? isFavorite)
{
+ userId = RequestHelpers.GetUserId(User, userId);
return _channelManager.GetChannels(new ChannelQuery
{
Limit = limit,
StartIndex = startIndex,
- UserId = userId ?? Guid.Empty,
+ UserId = userId.Value,
SupportsLatestItems = supportsLatestItems,
SupportsMediaDeletion = supportsMediaDeletion,
IsFavorite = isFavorite
@@ -124,7 +125,8 @@ public class ChannelsController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
@@ -198,7 +200,8 @@ public class ChannelsController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs
index 497862236..aa0dff212 100644
--- a/Jellyfin.Api/Controllers/DevicesController.cs
+++ b/Jellyfin.Api/Controllers/DevicesController.cs
@@ -2,6 +2,7 @@ using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
+using Jellyfin.Api.Helpers;
using Jellyfin.Data.Dtos;
using Jellyfin.Data.Entities.Security;
using Jellyfin.Data.Queries;
@@ -48,6 +49,7 @@ public class DevicesController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<QueryResult<DeviceInfo>>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId)
{
+ userId = RequestHelpers.GetUserId(User, userId);
return await _deviceManager.GetDevicesForUser(userId, supportsSync).ConfigureAwait(false);
}
diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs
index dd64ff903..dac07429f 100644
--- a/Jellyfin.Api/Controllers/FilterController.cs
+++ b/Jellyfin.Api/Controllers/FilterController.cs
@@ -1,5 +1,7 @@
using System;
using System.Linq;
+using Jellyfin.Api.Constants;
+using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
@@ -51,7 +53,8 @@ public class FilterController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
@@ -143,7 +146,8 @@ public class FilterController : BaseJellyfinApiController
[FromQuery] bool? isSeries,
[FromQuery] bool? recursive)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs
index 711fb4aef..eb03b514c 100644
--- a/Jellyfin.Api/Controllers/GenresController.cs
+++ b/Jellyfin.Api/Controllers/GenresController.cs
@@ -90,11 +90,12 @@ public class GenresController : BaseJellyfinApiController
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
- User? user = userId is null || userId.Value.Equals(default)
+ User? user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
@@ -155,6 +156,7 @@ public class GenresController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<BaseItemDto> GetGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var dtoOptions = new DtoOptions()
.AddClientFields(User);
@@ -170,7 +172,7 @@ public class GenresController : BaseJellyfinApiController
item ??= new Genre();
- if (userId is null || userId.Value.Equals(default))
+ if (userId.Value.Equals(default))
{
return _dtoService.GetBaseItemDto(item, dtoOptions);
}
diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs
index 43f09b49a..4dc2a4253 100644
--- a/Jellyfin.Api/Controllers/InstantMixController.cs
+++ b/Jellyfin.Api/Controllers/InstantMixController.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Dto;
@@ -74,7 +75,8 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
var item = _libraryManager.GetItemById(id);
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
@@ -110,7 +112,8 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
var album = _libraryManager.GetItemById(id);
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
@@ -146,7 +149,8 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
var playlist = (Playlist)_libraryManager.GetItemById(id);
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
@@ -181,7 +185,8 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
@@ -217,7 +222,8 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
var item = _libraryManager.GetItemById(id);
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
@@ -253,7 +259,8 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
var item = _libraryManager.GetItemById(id);
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
@@ -326,7 +333,8 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{
var item = _libraryManager.GetItemById(id);
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 99366e80c..728e62810 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -240,7 +240,8 @@ public class ItemsController : BaseJellyfinApiController
{
var isApiKey = User.GetIsApiKey();
// if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method
- var user = !isApiKey && userId.HasValue && !userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = !isApiKey && !userId.Value.Equals(default)
? _userManager.GetUserById(userId.Value) ?? throw new ResourceNotFoundException()
: null;
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index e8b68c7c3..bf59febed 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.LibraryDtos;
using Jellyfin.Data.Entities;
@@ -142,12 +143,13 @@ public class LibraryController : BaseJellyfinApiController
[FromQuery] Guid? userId,
[FromQuery] bool inheritFromParent = false)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
var item = itemId.Equals(default)
- ? (userId is null || userId.Value.Equals(default)
+ ? (userId.Value.Equals(default)
? _libraryManager.RootFolder
: _libraryManager.GetUserRootFolder())
: _libraryManager.GetItemById(itemId);
@@ -208,12 +210,13 @@ public class LibraryController : BaseJellyfinApiController
[FromQuery] Guid? userId,
[FromQuery] bool inheritFromParent = false)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
var item = itemId.Equals(default)
- ? (userId is null || userId.Value.Equals(default)
+ ? (userId.Value.Equals(default)
? _libraryManager.RootFolder
: _libraryManager.GetUserRootFolder())
: _libraryManager.GetItemById(itemId);
@@ -403,7 +406,8 @@ public class LibraryController : BaseJellyfinApiController
[FromQuery] Guid? userId,
[FromQuery] bool? isFavorite)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
@@ -437,6 +441,7 @@ public class LibraryController : BaseJellyfinApiController
public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
{
var item = _libraryManager.GetItemById(itemId);
+ userId = RequestHelpers.GetUserId(User, userId);
if (item is null)
{
@@ -445,7 +450,7 @@ public class LibraryController : BaseJellyfinApiController
var baseItemDtos = new List<BaseItemDto>();
- var user = userId is null || userId.Value.Equals(default)
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
@@ -675,8 +680,9 @@ public class LibraryController : BaseJellyfinApiController
[FromQuery] int? limit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var item = itemId.Equals(default)
- ? (userId is null || userId.Value.Equals(default)
+ ? (userId.Value.Equals(default)
? _libraryManager.RootFolder
: _libraryManager.GetUserRootFolder())
: _libraryManager.GetItemById(itemId);
@@ -691,7 +697,7 @@ public class LibraryController : BaseJellyfinApiController
return new QueryResult<BaseItemDto>();
}
- var user = userId is null || userId.Value.Equals(default)
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 318ed5c67..96fc91f93 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -153,6 +153,7 @@ public class LiveTvController : BaseJellyfinApiController
[FromQuery] bool enableFavoriteSorting = false,
[FromQuery] bool addCurrentProgram = true)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@@ -161,7 +162,7 @@ public class LiveTvController : BaseJellyfinApiController
new LiveTvChannelQuery
{
ChannelType = type,
- UserId = userId ?? Guid.Empty,
+ UserId = userId.Value,
StartIndex = startIndex,
Limit = limit,
IsFavorite = isFavorite,
@@ -180,7 +181,7 @@ public class LiveTvController : BaseJellyfinApiController
dtoOptions,
CancellationToken.None);
- var user = userId is null || userId.Value.Equals(default)
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
@@ -211,7 +212,8 @@ public class LiveTvController : BaseJellyfinApiController
[Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
var item = channelId.Equals(default)
@@ -271,6 +273,7 @@ public class LiveTvController : BaseJellyfinApiController
[FromQuery] bool? isLibraryItem,
[FromQuery] bool enableTotalRecordCount = true)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@@ -279,7 +282,7 @@ public class LiveTvController : BaseJellyfinApiController
new RecordingQuery
{
ChannelId = channelId,
- UserId = userId ?? Guid.Empty,
+ UserId = userId.Value,
StartIndex = startIndex,
Limit = limit,
Status = status,
@@ -382,7 +385,8 @@ public class LiveTvController : BaseJellyfinApiController
[Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<QueryResult<BaseItemDto>> GetRecordingFolders([FromQuery] Guid? userId)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
var folders = _liveTvManager.GetRecordingFolders(user);
@@ -404,7 +408,8 @@ public class LiveTvController : BaseJellyfinApiController
[Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
var item = recordingId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId);
@@ -560,7 +565,8 @@ public class LiveTvController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery] bool enableTotalRecordCount = true)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
@@ -699,7 +705,8 @@ public class LiveTvController : BaseJellyfinApiController
[FromQuery] bool? enableUserData,
[FromQuery] bool enableTotalRecordCount = true)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
@@ -737,7 +744,8 @@ public class LiveTvController : BaseJellyfinApiController
[FromRoute, Required] string programId,
[FromQuery] Guid? userId)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs
index ea10dd771..da24616ff 100644
--- a/Jellyfin.Api/Controllers/MediaInfoController.cs
+++ b/Jellyfin.Api/Controllers/MediaInfoController.cs
@@ -132,6 +132,7 @@ public class MediaInfoController : BaseJellyfinApiController
// Copy params from posted body
// TODO clean up when breaking API compatibility.
userId ??= playbackInfoDto?.UserId;
+ userId = RequestHelpers.GetUserId(User, userId);
maxStreamingBitrate ??= playbackInfoDto?.MaxStreamingBitrate;
startTimeTicks ??= playbackInfoDto?.StartTimeTicks;
audioStreamIndex ??= playbackInfoDto?.AudioStreamIndex;
@@ -253,10 +254,12 @@ public class MediaInfoController : BaseJellyfinApiController
[FromQuery] bool? enableDirectPlay,
[FromQuery] bool? enableDirectStream)
{
+ userId ??= openLiveStreamDto?.UserId;
+ userId = RequestHelpers.GetUserId(User, userId);
var request = new LiveStreamRequest
{
OpenToken = openToken ?? openLiveStreamDto?.OpenToken,
- UserId = userId ?? openLiveStreamDto?.UserId ?? Guid.Empty,
+ UserId = userId.Value,
PlaySessionId = playSessionId ?? openLiveStreamDto?.PlaySessionId,
MaxStreamingBitrate = maxStreamingBitrate ?? openLiveStreamDto?.MaxStreamingBitrate,
StartTimeTicks = startTimeTicks ?? openLiveStreamDto?.StartTimeTicks,
diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs
index a9336f6d2..e1145481f 100644
--- a/Jellyfin.Api/Controllers/MoviesController.cs
+++ b/Jellyfin.Api/Controllers/MoviesController.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
@@ -67,7 +68,8 @@ public class MoviesController : BaseJellyfinApiController
[FromQuery] int categoryLimit = 5,
[FromQuery] int itemLimit = 8)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields }
diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs
index 3db1d89c1..435457af6 100644
--- a/Jellyfin.Api/Controllers/MusicGenresController.cs
+++ b/Jellyfin.Api/Controllers/MusicGenresController.cs
@@ -90,11 +90,12 @@ public class MusicGenresController : BaseJellyfinApiController
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
- User? user = userId is null || userId.Value.Equals(default)
+ User? user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
@@ -144,6 +145,7 @@ public class MusicGenresController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<BaseItemDto> GetMusicGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var dtoOptions = new DtoOptions().AddClientFields(User);
MusicGenre? item;
@@ -162,7 +164,7 @@ public class MusicGenresController : BaseJellyfinApiController
return NotFound();
}
- if (userId.HasValue && !userId.Value.Equals(default))
+ if (!userId.Value.Equals(default))
{
var user = _userManager.GetUserById(userId.Value);
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
index 5310f50b1..b4c6f490a 100644
--- a/Jellyfin.Api/Controllers/PersonsController.cs
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -2,6 +2,7 @@ using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Dto;
@@ -77,11 +78,12 @@ public class PersonsController : BaseJellyfinApiController
[FromQuery] Guid? userId,
[FromQuery] bool? enableImages = true)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
- User? user = userId is null || userId.Value.Equals(default)
+ User? user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
@@ -117,6 +119,7 @@ public class PersonsController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<BaseItemDto> GetPerson([FromRoute, Required] string name, [FromQuery] Guid? userId)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var dtoOptions = new DtoOptions()
.AddClientFields(User);
@@ -126,7 +129,7 @@ public class PersonsController : BaseJellyfinApiController
return NotFound();
}
- if (userId.HasValue && !userId.Value.Equals(default))
+ if (!userId.Value.Equals(default))
{
var user = _userManager.GetUserById(userId.Value);
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs
index 79c0d3c7b..c6dbea5e2 100644
--- a/Jellyfin.Api/Controllers/PlaylistsController.cs
+++ b/Jellyfin.Api/Controllers/PlaylistsController.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.PlaylistDtos;
using MediaBrowser.Controller.Dto;
@@ -81,11 +82,13 @@ public class PlaylistsController : BaseJellyfinApiController
ids = createPlaylistRequest?.Ids ?? Array.Empty<Guid>();
}
+ userId ??= createPlaylistRequest?.UserId ?? default;
+ userId = RequestHelpers.GetUserId(User, userId);
var result = await _playlistManager.CreatePlaylist(new PlaylistCreationRequest
{
Name = name ?? createPlaylistRequest?.Name,
ItemIdList = ids,
- UserId = userId ?? createPlaylistRequest?.UserId ?? default,
+ UserId = userId.Value,
MediaType = mediaType ?? createPlaylistRequest?.MediaType
}).ConfigureAwait(false);
@@ -107,7 +110,8 @@ public class PlaylistsController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids,
[FromQuery] Guid? userId)
{
- await _playlistManager.AddToPlaylistAsync(playlistId, ids, userId ?? Guid.Empty).ConfigureAwait(false);
+ userId = RequestHelpers.GetUserId(User, userId);
+ await _playlistManager.AddToPlaylistAsync(playlistId, ids, userId.Value).ConfigureAwait(false);
return NoContent();
}
diff --git a/Jellyfin.Api/Controllers/QuickConnectController.cs b/Jellyfin.Api/Controllers/QuickConnectController.cs
index 503b9d372..d7e54b5b6 100644
--- a/Jellyfin.Api/Controllers/QuickConnectController.cs
+++ b/Jellyfin.Api/Controllers/QuickConnectController.cs
@@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Net;
@@ -116,17 +117,11 @@ public class QuickConnectController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<bool>> AuthorizeQuickConnect([FromQuery, Required] string code, [FromQuery] Guid? userId = null)
{
- var currentUserId = User.GetUserId();
- var actualUserId = userId ?? currentUserId;
-
- if (actualUserId.Equals(default) || (!userId.Equals(currentUserId) && !User.IsInRole(UserRoles.Administrator)))
- {
- return Forbid("Unknown user id");
- }
+ userId = RequestHelpers.GetUserId(User, userId);
try
{
- return await _quickConnect.AuthorizeRequest(actualUserId, code).ConfigureAwait(false);
+ return await _quickConnect.AuthorizeRequest(userId.Value, code).ConfigureAwait(false);
}
catch (AuthenticationException)
{
diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs
index a25b43345..f638c31c3 100644
--- a/Jellyfin.Api/Controllers/SearchController.cs
+++ b/Jellyfin.Api/Controllers/SearchController.cs
@@ -3,6 +3,8 @@ using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
+using Jellyfin.Api.Constants;
+using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
@@ -98,6 +100,7 @@ public class SearchController : BaseJellyfinApiController
[FromQuery] bool includeStudios = true,
[FromQuery] bool includeArtists = true)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var result = _searchEngine.GetSearchHints(new SearchQuery
{
Limit = limit,
@@ -108,7 +111,7 @@ public class SearchController : BaseJellyfinApiController
IncludePeople = includePeople,
IncludeStudios = includeStudios,
StartIndex = startIndex,
- UserId = userId ?? Guid.Empty,
+ UserId = userId.Value,
IncludeItemTypes = includeItemTypes,
ExcludeItemTypes = excludeItemTypes,
MediaTypes = mediaTypes,
diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs
index 21965e956..f434f60f5 100644
--- a/Jellyfin.Api/Controllers/StudiosController.cs
+++ b/Jellyfin.Api/Controllers/StudiosController.cs
@@ -86,11 +86,12 @@ public class StudiosController : BaseJellyfinApiController
[FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
- User? user = userId is null || userId.Value.Equals(default)
+ User? user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
@@ -139,10 +140,11 @@ public class StudiosController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<BaseItemDto> GetStudio([FromRoute, Required] string name, [FromQuery] Guid? userId)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var dtoOptions = new DtoOptions().AddClientFields(User);
var item = _libraryManager.GetStudio(name);
- if (userId.HasValue && !userId.Equals(default))
+ if (!userId.Equals(default))
{
var user = _userManager.GetUserById(userId.Value);
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index b0760f97c..7d23281f2 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
@@ -87,6 +88,7 @@ public class TvShowsController : BaseJellyfinApiController
[FromQuery] bool disableFirstEpisode = false,
[FromQuery] bool enableRewatching = false)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var options = new DtoOptions { Fields = fields }
.AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@@ -98,7 +100,7 @@ public class TvShowsController : BaseJellyfinApiController
ParentId = parentId,
SeriesId = seriesId,
StartIndex = startIndex,
- UserId = userId ?? Guid.Empty,
+ UserId = userId.Value,
EnableTotalRecordCount = enableTotalRecordCount,
DisableFirstEpisode = disableFirstEpisode,
NextUpDateCutoff = nextUpDateCutoff ?? DateTime.MinValue,
@@ -106,7 +108,7 @@ public class TvShowsController : BaseJellyfinApiController
},
options);
- var user = userId is null || userId.Value.Equals(default)
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
@@ -144,7 +146,8 @@ public class TvShowsController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
@@ -215,7 +218,8 @@ public class TvShowsController : BaseJellyfinApiController
[FromQuery] bool? enableUserData,
[FromQuery] string? sortBy)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
@@ -331,7 +335,8 @@ public class TvShowsController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery] bool? enableUserData)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index 345521597..12d033ae6 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -106,11 +106,7 @@ public class UniversalAudioController : BaseJellyfinApiController
[FromQuery] bool enableRedirection = true)
{
var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
-
- if (!userId.HasValue || userId.Value.Equals(default))
- {
- userId = User.GetUserId();
- }
+ userId = RequestHelpers.GetUserId(User, userId);
_logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", deviceProfile);
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index 3a61367f7..c0ec646ed 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -104,12 +104,13 @@ public class VideosController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
{
- var user = userId is null || userId.Value.Equals(default)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
var item = itemId.Equals(default)
- ? (userId is null || userId.Value.Equals(default)
+ ? (userId.Value.Equals(default)
? _libraryManager.RootFolder
: _libraryManager.GetUserRootFolder())
: _libraryManager.GetItemById(itemId);
diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs
index def37cb97..74370db50 100644
--- a/Jellyfin.Api/Controllers/YearsController.cs
+++ b/Jellyfin.Api/Controllers/YearsController.cs
@@ -85,11 +85,12 @@ public class YearsController : BaseJellyfinApiController
[FromQuery] bool recursive = true,
[FromQuery] bool? enableImages = true)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
- User? user = userId is null || userId.Value.Equals(default)
+ User? user = userId.Value.Equals(default)
? null
: _userManager.GetUserById(userId.Value);
BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
@@ -171,6 +172,7 @@ public class YearsController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<BaseItemDto> GetYear([FromRoute, Required] int year, [FromQuery] Guid? userId)
{
+ userId = RequestHelpers.GetUserId(User, userId);
var item = _libraryManager.GetYear(year);
if (item is null)
{
@@ -180,7 +182,7 @@ public class YearsController : BaseJellyfinApiController
var dtoOptions = new DtoOptions()
.AddClientFields(User);
- if (userId.HasValue && !userId.Value.Equals(default))
+ if (!userId.Value.Equals(default))
{
var user = _userManager.GetUserById(userId.Value);
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs
index 0b7a4fa1a..57098edba 100644
--- a/Jellyfin.Api/Helpers/RequestHelpers.cs
+++ b/Jellyfin.Api/Helpers/RequestHelpers.cs
@@ -11,6 +11,7 @@ using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
@@ -56,6 +57,32 @@ public static class RequestHelpers
}
/// <summary>
+ /// Checks if the user can access a user.
+ /// </summary>
+ /// <param name="claimsPrincipal">The <see cref="ClaimsPrincipal"/> for the current request.</param>
+ /// <param name="userId">The user id.</param>
+ /// <returns>A <see cref="bool"/> whether the user can access the user.</returns>
+ internal static Guid GetUserId(ClaimsPrincipal claimsPrincipal, Guid? userId)
+ {
+ var authenticatedUserId = claimsPrincipal.GetUserId();
+
+ // UserId not provided, fall back to authenticated user id.
+ if (userId is null || userId.Value.Equals(default))
+ {
+ return authenticatedUserId;
+ }
+
+ // User must be administrator to access another user.
+ var isAdministrator = claimsPrincipal.IsInRole(UserRoles.Administrator);
+ if (!userId.Value.Equals(authenticatedUserId) && !isAdministrator)
+ {
+ throw new SecurityException("Forbidden");
+ }
+
+ return userId.Value;
+ }
+
+ /// <summary>
/// Checks if the user can update an entry.
/// </summary>
/// <param name="userManager">An instance of the <see cref="IUserManager"/> interface.</param>
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index 92384986a..c4756433e 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -740,7 +740,7 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)", nameof(name));
}
- private static bool IsValidUsername(string name)
+ private static bool IsValidUsername(ReadOnlySpan<char> name)
{
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 9e6134b52..540d50bf1 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -277,7 +277,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (match.Success)
{
- if (Version.TryParse(match.Groups[1].Value, out var result))
+ if (Version.TryParse(match.Groups[1].ValueSpan, out var result))
{
return result;
}
@@ -327,8 +327,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
RegexOptions.Multiline))
{
var version = new Version(
- int.Parse(match.Groups["major"].Value, CultureInfo.InvariantCulture),
- int.Parse(match.Groups["minor"].Value, CultureInfo.InvariantCulture));
+ int.Parse(match.Groups["major"].ValueSpan, CultureInfo.InvariantCulture),
+ int.Parse(match.Groups["minor"].ValueSpan, CultureInfo.InvariantCulture));
map.Add(match.Groups["name"].Value, version);
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
index 22229e377..a97b56743 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
@@ -8,7 +8,7 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz.Configuration;
/// </summary>
public class PluginConfiguration : BasePluginConfiguration
{
- private const string DefaultServer = "musicbrainz.org";
+ private const string DefaultServer = "https://musicbrainz.org";
private const double DefaultRateLimit = 1.0;
diff --git a/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs b/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs
index c4640bd22..2d7741d81 100644
--- a/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs
+++ b/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs
@@ -1,7 +1,11 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
+using System.Security.Claims;
+using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
using Jellyfin.Data.Enums;
+using MediaBrowser.Controller.Net;
using Xunit;
namespace Jellyfin.Api.Tests.Helpers
@@ -15,6 +19,82 @@ namespace Jellyfin.Api.Tests.Helpers
Assert.Equal(expected, RequestHelpers.GetOrderBy(sortBy, requestedSortOrder));
}
+ [Fact]
+ public static void GetUserId_IsAdmin()
+ {
+ Guid? requestUserId = Guid.NewGuid();
+ Guid? authUserId = Guid.NewGuid();
+
+ var claims = new[]
+ {
+ new Claim(InternalClaimTypes.UserId, authUserId.Value.ToString("N", CultureInfo.InvariantCulture)),
+ new Claim(InternalClaimTypes.IsApiKey, bool.FalseString),
+ new Claim(ClaimTypes.Role, UserRoles.Administrator)
+ };
+
+ var identity = new ClaimsIdentity(claims, string.Empty);
+ var principal = new ClaimsPrincipal(identity);
+
+ var userId = RequestHelpers.GetUserId(principal, requestUserId);
+
+ Assert.Equal(requestUserId, userId);
+ }
+
+ [Fact]
+ public static void GetUserId_IsApiKey_EmptyGuid()
+ {
+ Guid? requestUserId = Guid.Empty;
+
+ var claims = new[]
+ {
+ new Claim(InternalClaimTypes.IsApiKey, bool.TrueString)
+ };
+
+ var identity = new ClaimsIdentity(claims, string.Empty);
+ var principal = new ClaimsPrincipal(identity);
+
+ var userId = RequestHelpers.GetUserId(principal, requestUserId);
+
+ Assert.Equal(Guid.Empty, userId);
+ }
+
+ [Fact]
+ public static void GetUserId_IsApiKey_Null()
+ {
+ Guid? requestUserId = null;
+
+ var claims = new[]
+ {
+ new Claim(InternalClaimTypes.IsApiKey, bool.TrueString)
+ };
+
+ var identity = new ClaimsIdentity(claims, string.Empty);
+ var principal = new ClaimsPrincipal(identity);
+
+ var userId = RequestHelpers.GetUserId(principal, requestUserId);
+
+ Assert.Equal(Guid.Empty, userId);
+ }
+
+ [Fact]
+ public static void GetUserId_IsUser()
+ {
+ Guid? requestUserId = Guid.NewGuid();
+ Guid? authUserId = Guid.NewGuid();
+
+ var claims = new[]
+ {
+ new Claim(InternalClaimTypes.UserId, authUserId.Value.ToString("N", CultureInfo.InvariantCulture)),
+ new Claim(InternalClaimTypes.IsApiKey, bool.FalseString),
+ new Claim(ClaimTypes.Role, UserRoles.User)
+ };
+
+ var identity = new ClaimsIdentity(claims, string.Empty);
+ var principal = new ClaimsPrincipal(identity);
+
+ Assert.Throws<SecurityException>(() => RequestHelpers.GetUserId(principal, requestUserId));
+ }
+
public static TheoryData<IReadOnlyList<string>, IReadOnlyList<SortOrder>, (string, SortOrder)[]> GetOrderBy_Success_TestData()
{
var data = new TheoryData<IReadOnlyList<string>, IReadOnlyList<SortOrder>, (string, SortOrder)[]>();
diff --git a/tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs b/tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs
index 58aaed023..c49663248 100644
--- a/tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs
+++ b/tests/Jellyfin.Naming.Tests/Common/NamingOptionsTest.cs
@@ -12,8 +12,6 @@ namespace Jellyfin.Naming.Tests.Common
Assert.NotEmpty(options.CleanDateTimeRegexes);
Assert.NotEmpty(options.CleanStringRegexes);
- Assert.NotEmpty(options.EpisodeWithoutSeasonRegexes);
- Assert.NotEmpty(options.EpisodeMultiPartRegexes);
}
[Fact]
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs
index 62b32b92e..078002994 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs
@@ -22,13 +22,13 @@ public sealed class ItemsControllerTests : IClassFixture<JellyfinApplicationFact
}
[Fact]
- public async Task GetItems_NoApiKeyOrUserId_BadRequest()
+ public async Task GetItems_NoApiKeyOrUserId_Success()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
var response = await client.GetAsync("Items").ConfigureAwait(false);
- Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[Theory]