Use RegexGenerator where possible

pull/9799/head
Bond_009 1 year ago
parent f954dc5c96
commit b5f0760db8

@ -31,6 +31,9 @@ namespace Emby.Dlna.PlayTo
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
} }
[GeneratedRegex("(&(?![a-z]*;))")]
private static partial Regex EscapeAmpersandRegex();
private static string NormalizeServiceUrl(string baseUrl, string serviceUrl) private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
{ {
// If it's already a complete url, don't stick anything onto the front of it // If it's already a complete url, don't stick anything onto the front of it
@ -128,12 +131,5 @@ namespace Emby.Dlna.PlayTo
// Have to await here instead of returning the Task directly, otherwise request would be disposed too soon // Have to await here instead of returning the Task directly, otherwise request would be disposed too soon
return await SendRequestAsync(request, cancellationToken).ConfigureAwait(false); return await SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
} }
/// <summary>
/// Compile-time generated regular expression for escaping ampersands.
/// </summary>
/// <returns>Compiled regular expression.</returns>
[GeneratedRegex("(&(?![a-z]*;))")]
private static partial Regex EscapeAmpersandRegex();
} }
} }

@ -10,7 +10,7 @@ namespace Emby.Naming.Audio
/// <summary> /// <summary>
/// Helper class to determine if Album is multipart. /// Helper class to determine if Album is multipart.
/// </summary> /// </summary>
public class AlbumParser public partial class AlbumParser
{ {
private readonly NamingOptions _options; private readonly NamingOptions _options;
@ -23,6 +23,9 @@ namespace Emby.Naming.Audio
_options = options; _options = options;
} }
[GeneratedRegex(@"([-\.\(\)]|\s+)")]
private static partial Regex CleanRegex();
/// <summary> /// <summary>
/// Function that determines if album is multipart. /// Function that determines if album is multipart.
/// </summary> /// </summary>
@ -42,13 +45,9 @@ namespace Emby.Naming.Audio
// Normalize // Normalize
// Remove whitespace // Remove whitespace
filename = filename.Replace('-', ' '); filename = CleanRegex().Replace(filename, " ");
filename = filename.Replace('.', ' ');
filename = filename.Replace('(', ' ');
filename = filename.Replace(')', ' ');
filename = Regex.Replace(filename, @"\s+", " ");
ReadOnlySpan<char> trimmedFilename = filename.TrimStart(); ReadOnlySpan<char> trimmedFilename = filename.AsSpan().TrimStart();
foreach (var prefix in _options.AlbumStackingPrefixes) foreach (var prefix in _options.AlbumStackingPrefixes)
{ {

@ -7,14 +7,15 @@ namespace Emby.Naming.TV
/// <summary> /// <summary>
/// Used to resolve information about series from path. /// Used to resolve information about series from path.
/// </summary> /// </summary>
public static class SeriesResolver public static partial class SeriesResolver
{ {
/// <summary> /// <summary>
/// Regex that matches strings of at least 2 characters separated by a dot or underscore. /// Regex that matches strings of at least 2 characters separated by a dot or underscore.
/// Used for removing separators between words, i.e turns "The_show" into "The show" while /// Used for removing separators between words, i.e turns "The_show" into "The show" while
/// preserving namings like "S.H.O.W". /// preserving namings like "S.H.O.W".
/// </summary> /// </summary>
private static readonly Regex _seriesNameRegex = new Regex(@"((?<a>[^\._]{2,})[\._]*)|([\._](?<b>[^\._]{2,}))", RegexOptions.Compiled); [GeneratedRegex(@"((?<a>[^\._]{2,})[\._]*)|([\._](?<b>[^\._]{2,}))")]
private static partial Regex SeriesNameRegex();
/// <summary> /// <summary>
/// Resolve information about series from path. /// Resolve information about series from path.
@ -37,7 +38,7 @@ namespace Emby.Naming.TV
if (!string.IsNullOrEmpty(seriesName)) if (!string.IsNullOrEmpty(seriesName))
{ {
seriesName = _seriesNameRegex.Replace(seriesName, "${a} ${b}").Trim(); seriesName = SeriesNameRegex().Replace(seriesName, "${a} ${b}").Trim();
} }
return new SeriesInfo(path) return new SeriesInfo(path)

@ -12,9 +12,13 @@ namespace Emby.Naming.Video
/// <summary> /// <summary>
/// Resolves alternative versions and extras from list of video files. /// Resolves alternative versions and extras from list of video files.
/// </summary> /// </summary>
public static class VideoListResolver public static partial class VideoListResolver
{ {
private static readonly Regex _resolutionRegex = new Regex("[0-9]{2}[0-9]+[ip]", RegexOptions.IgnoreCase | RegexOptions.Compiled); [GeneratedRegex("[0-9]{2}[0-9]+[ip]", RegexOptions.IgnoreCase)]
private static partial Regex ResolutionRegex();
[GeneratedRegex(@"^\[([^]]*)\]")]
private static partial Regex CheckMultiVersionRegex();
/// <summary> /// <summary>
/// Resolves alternative versions and extras from list of video files. /// Resolves alternative versions and extras from list of video files.
@ -131,7 +135,7 @@ namespace Emby.Naming.Video
if (videos.Count > 1) if (videos.Count > 1)
{ {
var groups = videos.GroupBy(x => _resolutionRegex.IsMatch(x.Files[0].FileNameWithoutExtension)).ToList(); var groups = videos.GroupBy(x => ResolutionRegex().IsMatch(x.Files[0].FileNameWithoutExtension)).ToList();
videos.Clear(); videos.Clear();
foreach (var group in groups) foreach (var group in groups)
{ {
@ -201,7 +205,7 @@ namespace Emby.Naming.Video
// The CleanStringParser should have removed common keywords etc. // The CleanStringParser should have removed common keywords etc.
return testFilename.IsEmpty return testFilename.IsEmpty
|| testFilename[0] == '-' || testFilename[0] == '-'
|| Regex.IsMatch(testFilename, @"^\[([^]]*)\]", RegexOptions.Compiled); || CheckMultiVersionRegex().IsMatch(testFilename);
} }
} }
} }

@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// <summary> /// <summary>
/// Class MovieResolver. /// Class MovieResolver.
/// </summary> /// </summary>
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver public partial class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
{ {
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
@ -56,6 +56,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// <value>The priority.</value> /// <value>The priority.</value>
public override ResolverPriority Priority => ResolverPriority.Fourth; public override ResolverPriority Priority => ResolverPriority.Fourth;
[GeneratedRegex(@"\bsample\b", RegexOptions.IgnoreCase)]
private static partial Regex IsIgnoredRegex();
/// <inheritdoc /> /// <inheritdoc />
public MultiItemResolverResult ResolveMultiple( public MultiItemResolverResult ResolveMultiple(
Folder parent, Folder parent,
@ -261,7 +264,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
{ {
leftOver.Add(child); leftOver.Add(child);
} }
else if (!IsIgnored(child.Name)) else if (!IsIgnoredRegex().IsMatch(child.Name))
{ {
files.Add(child); files.Add(child);
} }
@ -314,9 +317,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return result; return result;
} }
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) private static bool ContainsFile(IReadOnlyList<VideoInfo> result, FileSystemMetadata file)
{ {
for (var i = 0; i < result.Count; i++) for (var i = 0; i < result.Count; i++)

@ -5,7 +5,7 @@ using System.Text.RegularExpressions;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
public class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands public partial class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands
{ {
private string? _channel; private string? _channel;
private string? _program; private string? _program;
@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public LegacyHdHomerunChannelCommands(string url) public LegacyHdHomerunChannelCommands(string url)
{ {
// parse url for channel and program // parse url for channel and program
var match = Regex.Match(url, @"\/ch([0-9]+)-?([0-9]*)"); var match = ChannelAndProgramRegex().Match(url);
if (match.Success) if (match.Success)
{ {
_channel = match.Groups[1].Value; _channel = match.Groups[1].Value;
@ -21,6 +21,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
} }
} }
[GeneratedRegex(@"\/ch([0-9]+)-?([0-9]*)")]
private static partial Regex ChannelAndProgramRegex();
public IEnumerable<(string CommandName, string CommandValue)> GetCommands() public IEnumerable<(string CommandName, string CommandValue)> GetCommands()
{ {
if (!string.IsNullOrEmpty(_channel)) if (!string.IsNullOrEmpty(_channel))

@ -20,7 +20,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.TunerHosts namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
public class M3uParser public partial class M3uParser
{ {
private const string ExtInfPrefix = "#EXTINF:"; private const string ExtInfPrefix = "#EXTINF:";
@ -33,6 +33,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
} }
[GeneratedRegex(@"([a-z0-9\-_]+)=\""([^""]+)\""", RegexOptions.IgnoreCase, "en-US")]
private static partial Regex KeyValueRegex();
public async Task<List<ChannelInfo>> Parse(TunerHostInfo info, string channelIdPrefix, CancellationToken cancellationToken) public async Task<List<ChannelInfo>> Parse(TunerHostInfo info, string channelIdPrefix, CancellationToken cancellationToken)
{ {
// Read the file and display it line by line. // Read the file and display it line by line.
@ -311,7 +314,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var matches = Regex.Matches(line, @"([a-z0-9\-_]+)=\""([^""]+)\""", RegexOptions.IgnoreCase); var matches = KeyValueRegex().Matches(line);
remaining = line; remaining = line;
@ -320,7 +323,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var key = match.Groups[1].Value; var key = match.Groups[1].Value;
var value = match.Groups[2].Value; var value = match.Groups[2].Value;
dict[match.Groups[1].Value] = match.Groups[2].Value; dict[key] = value;
remaining = remaining.Replace(key + "=\"" + value + "\"", string.Empty, StringComparison.OrdinalIgnoreCase); remaining = remaining.Replace(key + "=\"" + value + "\"", string.Empty, StringComparison.OrdinalIgnoreCase);
} }

@ -31,7 +31,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <summary> /// <summary>
/// Manages the creation and retrieval of <see cref="User"/> instances. /// Manages the creation and retrieval of <see cref="User"/> instances.
/// </summary> /// </summary>
public class UserManager : IUserManager public partial class UserManager : IUserManager
{ {
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
private readonly IEventManager _eventManager; private readonly IEventManager _eventManager;
@ -105,6 +105,12 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<Guid> UsersIds => _users.Keys; public IEnumerable<Guid> UsersIds => _users.Keys;
// 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
// Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( )
[GeneratedRegex("^[\\w\\ \\-'._@]+$")]
private static partial Regex ValidUsernameRegex();
/// <inheritdoc/> /// <inheritdoc/>
public User? GetUserById(Guid id) public User? GetUserById(Guid id)
{ {
@ -527,7 +533,7 @@ namespace Jellyfin.Server.Implementations.Users
} }
var defaultName = Environment.UserName; var defaultName = Environment.UserName;
if (string.IsNullOrWhiteSpace(defaultName) || !IsValidUsername(defaultName)) if (string.IsNullOrWhiteSpace(defaultName) || !ValidUsernameRegex().IsMatch(defaultName))
{ {
defaultName = "MyJellyfinUser"; defaultName = "MyJellyfinUser";
} }
@ -710,7 +716,7 @@ namespace Jellyfin.Server.Implementations.Users
internal static void ThrowIfInvalidUsername(string name) internal static void ThrowIfInvalidUsername(string name)
{ {
if (!string.IsNullOrWhiteSpace(name) && IsValidUsername(name)) if (!string.IsNullOrWhiteSpace(name) && ValidUsernameRegex().IsMatch(name))
{ {
return; return;
} }
@ -718,14 +724,6 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)", nameof(name)); throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)", nameof(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
// Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( )
return Regex.IsMatch(name, @"^[\w\ \-'._@]+$");
}
private IAuthenticationProvider GetAuthenticationProvider(User user) private IAuthenticationProvider GetAuthenticationProvider(User user)
{ {
return GetAuthenticationProviders(user)[0]; return GetAuthenticationProviders(user)[0];

@ -8,20 +8,19 @@ namespace MediaBrowser.Common.Extensions
/// <summary> /// <summary>
/// Class BaseExtensions. /// Class BaseExtensions.
/// </summary> /// </summary>
public static class BaseExtensions public static partial class BaseExtensions
{ {
// http://stackoverflow.com/questions/1349023/how-can-i-strip-html-from-text-in-net
[GeneratedRegex(@"<(.|\n)*?>")]
private static partial Regex StripHtmlRegex();
/// <summary> /// <summary>
/// Strips the HTML. /// Strips the HTML.
/// </summary> /// </summary>
/// <param name="htmlString">The HTML string.</param> /// <param name="htmlString">The HTML string.</param>
/// <returns><see cref="string" />.</returns> /// <returns><see cref="string" />.</returns>
public static string StripHtml(this string htmlString) public static string StripHtml(this string htmlString)
{ => StripHtmlRegex().Replace(htmlString, string.Empty).Trim();
// http://stackoverflow.com/questions/1349023/how-can-i-strip-html-from-text-in-net
const string Pattern = @"<(.|\n)*?>";
return Regex.Replace(htmlString, Pattern, string.Empty).Trim();
}
/// <summary> /// <summary>
/// Gets the Md5. /// Gets the Md5.

@ -23,7 +23,7 @@ using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.MediaEncoding namespace MediaBrowser.Controller.MediaEncoding
{ {
public class EncodingHelper public partial class EncodingHelper
{ {
private const string QsvAlias = "qs"; private const string QsvAlias = "qs";
private const string VaapiAlias = "va"; private const string VaapiAlias = "va";
@ -112,6 +112,9 @@ namespace MediaBrowser.Controller.MediaEncoding
_config = config; _config = config;
} }
[GeneratedRegex(@"\s+")]
private static partial Regex WhiteSpaceRegex();
public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
=> GetH264OrH265Encoder("libx264", "h264", state, encodingOptions); => GetH264OrH265Encoder("libx264", "h264", state, encodingOptions);
@ -1733,7 +1736,7 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty; var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
profile = Regex.Replace(profile, @"\s+", string.Empty); profile = WhiteSpaceRegex().Replace(profile, string.Empty);
// We only transcode to HEVC 8-bit for now, force Main Profile. // We only transcode to HEVC 8-bit for now, force Main Profile.
if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase) if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)

@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.MediaEncoding.Encoder namespace MediaBrowser.MediaEncoding.Encoder
{ {
public class EncoderValidator public partial class EncoderValidator
{ {
private static readonly string[] _requiredDecoders = new[] private static readonly string[] _requiredDecoders = new[]
{ {
@ -160,6 +160,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
public static Version? MaxVersion { get; } = null; public static Version? MaxVersion { get; } = null;
[GeneratedRegex(@"^ffmpeg version n?((?:[0-9]+\.?)+)")]
private static partial Regex FfmpegVersionRegex();
[GeneratedRegex(@"((?<name>lib\w+)\s+(?<major>[0-9]+)\.\s*(?<minor>[0-9]+))", RegexOptions.Multiline)]
private static partial Regex LibraryRegex();
public bool ValidateVersion() public bool ValidateVersion()
{ {
string output; string output;
@ -278,7 +284,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
internal Version? GetFFmpegVersionInternal(string output) internal Version? GetFFmpegVersionInternal(string output)
{ {
// For pre-built binaries the FFmpeg version should be mentioned at the very start of the output // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)"); var match = FfmpegVersionRegex().Match(output);
if (match.Success) if (match.Success)
{ {
@ -326,10 +332,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
var map = new Dictionary<string, Version>(); var map = new Dictionary<string, Version>();
foreach (Match match in Regex.Matches( foreach (Match match in LibraryRegex().Matches(output))
output,
@"((?<name>lib\w+)\s+(?<major>[0-9]+)\.\s*(?<minor>[0-9]+))",
RegexOptions.Multiline))
{ {
var version = new Version( var version = new Version(
int.Parse(match.Groups["major"].ValueSpan, CultureInfo.InvariantCulture), int.Parse(match.Groups["major"].ValueSpan, CultureInfo.InvariantCulture),

@ -36,7 +36,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <summary> /// <summary>
/// Class MediaEncoder. /// Class MediaEncoder.
/// </summary> /// </summary>
public class MediaEncoder : IMediaEncoder, IDisposable public partial class MediaEncoder : IMediaEncoder, IDisposable
{ {
/// <summary> /// <summary>
/// The default SDR image extraction timeout in milliseconds. /// The default SDR image extraction timeout in milliseconds.
@ -142,6 +142,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <inheritdoc /> /// <inheritdoc />
public bool IsVaapiDeviceSupportVulkanFmtModifier => _isVaapiDeviceSupportVulkanFmtModifier; public bool IsVaapiDeviceSupportVulkanFmtModifier => _isVaapiDeviceSupportVulkanFmtModifier;
[GeneratedRegex(@"[^\/\\]+?(\.[^\/\\\n.]+)?$")]
private static partial Regex FfprobePathRegex();
/// <summary> /// <summary>
/// Run at startup or if the user removes a Custom path from transcode page. /// Run at startup or if the user removes a Custom path from transcode page.
/// Sets global variables FFmpegPath. /// Sets global variables FFmpegPath.
@ -176,7 +179,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (_ffmpegPath is not null) if (_ffmpegPath is not null)
{ {
// Determine a probe path from the mpeg path // Determine a probe path from the mpeg path
_ffprobePath = Regex.Replace(_ffmpegPath, @"[^\/\\]+?(\.[^\/\\\n.]+)?$", @"ffprobe$1"); _ffprobePath = FfprobePathRegex().Replace(_ffmpegPath, @"ffprobe$1");
// Interrogate to understand what coders are supported // Interrogate to understand what coders are supported
var validator = new EncoderValidator(_logger, _ffmpegPath); var validator = new EncoderValidator(_logger, _ffmpegPath);
@ -416,8 +419,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
public Task<MediaInfo> GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken) public Task<MediaInfo> GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken)
{ {
var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters; var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters;
string analyzeDuration = string.Empty; var analyzeDuration = string.Empty;
string ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty; var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
if (request.MediaSource.AnalyzeDurationMs > 0) if (request.MediaSource.AnalyzeDurationMs > 0)
{ {

@ -11,8 +11,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary> /// <summary>
/// ASS subtitle writer. /// ASS subtitle writer.
/// </summary> /// </summary>
public class AssWriter : ISubtitleWriter public partial class AssWriter : ISubtitleWriter
{ {
[GeneratedRegex(@"\n", RegexOptions.IgnoreCase)]
private static partial Regex NewLineRegex();
/// <inheritdoc /> /// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{ {
@ -40,7 +43,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var trackEvent = trackEvents[i]; var trackEvent = trackEvents[i];
var startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture); var startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
var endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture); var endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
var text = Regex.Replace(trackEvent.Text, @"\n", "\\n", RegexOptions.IgnoreCase); var text = NewLineRegex().Replace(trackEvent.Text, "\\n");
writer.WriteLine( writer.WriteLine(
"Dialogue: 0,{0},{1},Default,{2}", "Dialogue: 0,{0},{1},Default,{2}",

@ -11,8 +11,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary> /// <summary>
/// SRT subtitle writer. /// SRT subtitle writer.
/// </summary> /// </summary>
public class SrtWriter : ISubtitleWriter public partial class SrtWriter : ISubtitleWriter
{ {
[GeneratedRegex(@"\\n", RegexOptions.IgnoreCase)]
private static partial Regex NewLineEscapedRegex();
/// <inheritdoc /> /// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{ {
@ -35,7 +38,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var text = trackEvent.Text; var text = trackEvent.Text;
// TODO: Not sure how to handle these // TODO: Not sure how to handle these
text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase); text = NewLineEscapedRegex().Replace(text, " ");
writer.WriteLine(text); writer.WriteLine(text);
writer.WriteLine(); writer.WriteLine();

@ -11,8 +11,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary> /// <summary>
/// SSA subtitle writer. /// SSA subtitle writer.
/// </summary> /// </summary>
public class SsaWriter : ISubtitleWriter public partial class SsaWriter : ISubtitleWriter
{ {
[GeneratedRegex(@"\n", RegexOptions.IgnoreCase)]
private static partial Regex NewLineRegex();
/// <inheritdoc /> /// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{ {
@ -40,7 +43,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var trackEvent = trackEvents[i]; var trackEvent = trackEvents[i];
var startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture); var startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
var endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture); var endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
var text = Regex.Replace(trackEvent.Text, @"\n", "\\n", RegexOptions.IgnoreCase); var text = NewLineRegex().Replace(trackEvent.Text, "\\n");
writer.WriteLine( writer.WriteLine(
"Dialogue: 0,{0},{1},Default,{2}", "Dialogue: 0,{0},{1},Default,{2}",

@ -9,8 +9,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary> /// <summary>
/// TTML subtitle writer. /// TTML subtitle writer.
/// </summary> /// </summary>
public class TtmlWriter : ISubtitleWriter public partial class TtmlWriter : ISubtitleWriter
{ {
[GeneratedRegex(@"\\n", RegexOptions.IgnoreCase)]
private static partial Regex NewLineEscapeRegex();
/// <inheritdoc /> /// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{ {
@ -38,7 +41,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{ {
var text = trackEvent.Text; var text = trackEvent.Text;
text = Regex.Replace(text, @"\\n", "<br/>", RegexOptions.IgnoreCase); text = NewLineEscapeRegex().Replace(text, "<br/>");
writer.WriteLine( writer.WriteLine(
"<p begin=\"{0}\" dur=\"{1}\">{2}</p>", "<p begin=\"{0}\" dur=\"{1}\">{2}</p>",

@ -10,8 +10,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary> /// <summary>
/// Subtitle writer for the WebVTT format. /// Subtitle writer for the WebVTT format.
/// </summary> /// </summary>
public class VttWriter : ISubtitleWriter public partial class VttWriter : ISubtitleWriter
{ {
[GeneratedRegex(@"\\n", RegexOptions.IgnoreCase)]
private static partial Regex NewlineEscapeRegex();
/// <inheritdoc /> /// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{ {
@ -39,7 +42,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var text = trackEvent.Text; var text = trackEvent.Text;
// TODO: Not sure how to handle these // TODO: Not sure how to handle these
text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase); text = NewlineEscapeRegex().Replace(text, " ");
writer.WriteLine(text); writer.WriteLine(text);
writer.WriteLine(); writer.WriteLine();

@ -5,7 +5,7 @@ using System.Text.RegularExpressions;
namespace MediaBrowser.Model.Dlna namespace MediaBrowser.Model.Dlna
{ {
public class SearchCriteria public partial class SearchCriteria
{ {
public SearchCriteria(string search) public SearchCriteria(string search)
{ {
@ -13,10 +13,10 @@ namespace MediaBrowser.Model.Dlna
SearchType = SearchType.Unknown; SearchType = SearchType.Unknown;
string[] factors = RegexSplit(search, "(and|or)"); string[] factors = AndOrRegex().Split(search);
foreach (string factor in factors) foreach (string factor in factors)
{ {
string[] subFactors = RegexSplit(factor.Trim().Trim('(').Trim(')').Trim(), "\\s", 3); string[] subFactors = WhiteSpaceRegex().Split(factor.Trim().Trim('(').Trim(')').Trim(), 3);
if (subFactors.Length == 3) if (subFactors.Length == 3)
{ {
@ -46,27 +46,10 @@ namespace MediaBrowser.Model.Dlna
public SearchType SearchType { get; set; } public SearchType SearchType { get; set; }
/// <summary> [GeneratedRegex("\\s")]
/// Splits the specified string. private static partial Regex WhiteSpaceRegex();
/// </summary>
/// <param name="str">The string.</param>
/// <param name="term">The term.</param>
/// <param name="limit">The limit.</param>
/// <returns>System.String[].</returns>
private static string[] RegexSplit(string str, string term, int limit)
{
return new Regex(term).Split(str, limit);
}
/// <summary> [GeneratedRegex("(and|or)", RegexOptions.IgnoreCase)]
/// Splits the specified string. private static partial Regex AndOrRegex();
/// </summary>
/// <param name="str">The string.</param>
/// <param name="term">The term.</param>
/// <returns>System.String[].</returns>
private static string[] RegexSplit(string str, string term)
{
return Regex.Split(str, term, RegexOptions.IgnoreCase);
}
} }
} }

@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// <summary> /// <summary>
/// Probes audio files for metadata. /// Probes audio files for metadata.
/// </summary> /// </summary>
public class AudioFileProber public partial class AudioFileProber
{ {
// Default LUFS value for use with the web interface, at -18db gain will be 1(no db gain). // Default LUFS value for use with the web interface, at -18db gain will be 1(no db gain).
private const float DefaultLUFSValue = -18; private const float DefaultLUFSValue = -18;
@ -58,6 +58,9 @@ namespace MediaBrowser.Providers.MediaInfo
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
} }
[GeneratedRegex("I:\\s+(.*?)\\s+LUFS")]
private static partial Regex LUFSRegex();
/// <summary> /// <summary>
/// Probes the specified item for metadata. /// Probes the specified item for metadata.
/// </summary> /// </summary>
@ -129,7 +132,7 @@ namespace MediaBrowser.Providers.MediaInfo
output = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false); output = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
MatchCollection split = Regex.Matches(output, @"I:\s+(.*?)\s+LUFS"); MatchCollection split = LUFSRegex().Matches(output);
if (split.Count != 0) if (split.Count != 0)
{ {

@ -11,10 +11,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <summary> /// <summary>
/// Utilities for the TMDb provider. /// Utilities for the TMDb provider.
/// </summary> /// </summary>
public static class TmdbUtils public static partial class TmdbUtils
{ {
private static readonly Regex _nonWords = new(@"[\W_]+", RegexOptions.Compiled);
/// <summary> /// <summary>
/// URL of the TMDb instance to use. /// URL of the TMDb instance to use.
/// </summary> /// </summary>
@ -50,6 +48,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
PersonKind.Producer PersonKind.Producer
}; };
[GeneratedRegex(@"[\W_]+")]
private static partial Regex NonWordRegex();
/// <summary> /// <summary>
/// Cleans the name according to TMDb requirements. /// Cleans the name according to TMDb requirements.
/// </summary> /// </summary>
@ -58,7 +59,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
public static string CleanName(string name) public static string CleanName(string name)
{ {
// TMDb expects a space separated list of words make sure that is the case // TMDb expects a space separated list of words make sure that is the case
return _nonWords.Replace(name, " "); return NonWordRegex().Replace(name, " ");
} }
/// <summary> /// <summary>

@ -27,15 +27,12 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Savers namespace MediaBrowser.XbmcMetadata.Savers
{ {
public abstract class BaseNfoSaver : IMetadataFileSaver public abstract partial class BaseNfoSaver : IMetadataFileSaver
{ {
public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss"; public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
public const string YouTubeWatchUrl = "https://www.youtube.com/watch?v="; public const string YouTubeWatchUrl = "https://www.youtube.com/watch?v=";
// filters control characters but allows only properly-formed surrogate sequences
private const string _invalidXMLCharsRegex = @"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]";
private static readonly HashSet<string> _commonTags = new HashSet<string>(StringComparer.OrdinalIgnoreCase) private static readonly HashSet<string> _commonTags = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{ {
"plot", "plot",
@ -148,6 +145,12 @@ namespace MediaBrowser.XbmcMetadata.Savers
public static string SaverName => "Nfo"; public static string SaverName => "Nfo";
// filters control characters but allows only properly-formed surrogate sequences
// http://web.archive.org/web/20181230211547/https://emby.media/community/index.php?/topic/49071-nfo-not-generated-on-actualize-or-rescan-or-identify
// Web Archive version of link since it's not really explained in the thread.
[GeneratedRegex(@"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]")]
private static partial Regex InvalidXMLCharsRegexRegex();
/// <inheritdoc /> /// <inheritdoc />
public string GetSavePath(BaseItem item) public string GetSavePath(BaseItem item)
=> GetLocalSavePath(item); => GetLocalSavePath(item);
@ -354,9 +357,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (!string.IsNullOrEmpty(stream.Language)) if (!string.IsNullOrEmpty(stream.Language))
{ {
// http://web.archive.org/web/20181230211547/https://emby.media/community/index.php?/topic/49071-nfo-not-generated-on-actualize-or-rescan-or-identify writer.WriteElementString("language", InvalidXMLCharsRegexRegex().Replace(stream.Language, string.Empty));
// Web Archive version of link since it's not really explained in the thread.
writer.WriteElementString("language", Regex.Replace(stream.Language, _invalidXMLCharsRegex, string.Empty));
} }
var scanType = stream.IsInterlaced ? "interlaced" : "progressive"; var scanType = stream.IsInterlaced ? "interlaced" : "progressive";

@ -60,6 +60,8 @@
<Rule Id="SA1515" Action="None" /> <Rule Id="SA1515" Action="None" />
<!-- disable warning SA1600: Elements should be documented --> <!-- disable warning SA1600: Elements should be documented -->
<Rule Id="SA1600" Action="None" /> <Rule Id="SA1600" Action="None" />
<!-- disable warning SA1601: Partial elements should be documented -->
<Rule Id="SA1601" Action="None" />
<!-- disable warning SA1602: Enumeration items should be documented --> <!-- disable warning SA1602: Enumeration items should be documented -->
<Rule Id="SA1602" Action="None" /> <Rule Id="SA1602" Action="None" />
<!-- disable warning SA1633: The file header is missing or not located at the top of the file --> <!-- disable warning SA1633: The file header is missing or not located at the top of the file -->

@ -123,8 +123,7 @@ public partial class StripCollageBuilder
var typeFace = SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright); var typeFace = SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright);
// use the system fallback to find a typeface for the given CJK character // use the system fallback to find a typeface for the given CJK character
var nonCjkPattern = @"[^\p{IsCJKUnifiedIdeographs}\p{IsCJKUnifiedIdeographsExtensionA}\p{IsKatakana}\p{IsHiragana}\p{IsHangulSyllables}\p{IsHangulJamo}]"; var filteredName = NonCjkPatternRegex().Replace(libraryName ?? string.Empty, string.Empty);
var filteredName = Regex.Replace(libraryName ?? string.Empty, nonCjkPattern, string.Empty);
if (!string.IsNullOrEmpty(filteredName)) if (!string.IsNullOrEmpty(filteredName))
{ {
typeFace = SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, filteredName[0]); typeFace = SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, filteredName[0]);

@ -6,11 +6,13 @@ namespace Jellyfin.Extensions
/// <summary> /// <summary>
/// Provides extensions methods for <see cref="string" />. /// Provides extensions methods for <see cref="string" />.
/// </summary> /// </summary>
public static class StringExtensions public static partial class StringExtensions
{ {
// Matches non-conforming unicode chars // Matches non-conforming unicode chars
// https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/ // https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/
private static readonly Regex _nonConformingUnicode = new Regex("([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])|(\ufffd)", RegexOptions.Compiled);
[GeneratedRegex("([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])|(<EFBFBD>)")]
private static partial Regex NonConformingUnicodeRegex();
/// <summary> /// <summary>
/// Removes the diacritics character from the strings. /// Removes the diacritics character from the strings.
@ -19,7 +21,7 @@ namespace Jellyfin.Extensions
/// <returns>The string without diacritics character.</returns> /// <returns>The string without diacritics character.</returns>
public static string RemoveDiacritics(this string text) public static string RemoveDiacritics(this string text)
=> Diacritics.Extensions.StringExtensions.RemoveDiacritics( => Diacritics.Extensions.StringExtensions.RemoveDiacritics(
_nonConformingUnicode.Replace(text, string.Empty)); NonConformingUnicodeRegex().Replace(text, string.Empty));
/// <summary> /// <summary>
/// Checks whether or not the specified string has diacritics in it. /// Checks whether or not the specified string has diacritics in it.
@ -28,7 +30,7 @@ namespace Jellyfin.Extensions
/// <returns>True if the string has diacritics, false otherwise.</returns> /// <returns>True if the string has diacritics, false otherwise.</returns>
public static bool HasDiacritics(this string text) public static bool HasDiacritics(this string text)
=> Diacritics.Extensions.StringExtensions.HasDiacritics(text) => Diacritics.Extensions.StringExtensions.HasDiacritics(text)
|| _nonConformingUnicode.IsMatch(text); || NonConformingUnicodeRegex().IsMatch(text);
/// <summary> /// <summary>
/// Counts the number of occurrences of [needle] in the string. /// Counts the number of occurrences of [needle] in the string.

@ -25,10 +25,13 @@ using Xunit;
namespace Jellyfin.Providers.Tests.Manager namespace Jellyfin.Providers.Tests.Manager
{ {
public class ItemImageProviderTests public partial class ItemImageProviderTests
{ {
private const string TestDataImagePath = "Test Data/Images/blank{0}.jpg"; private const string TestDataImagePath = "Test Data/Images/blank{0}.jpg";
[GeneratedRegex("[0-9]+")]
private static partial Regex NumbersRegex();
[Fact] [Fact]
public void ValidateImages_PhotoEmptyProviders_NoChange() public void ValidateImages_PhotoEmptyProviders_NoChange()
{ {
@ -463,7 +466,7 @@ namespace Jellyfin.Providers.Tests.Manager
// images from the provider manager are sorted by preference (earlier images are higher priority) so we can verify that low url numbers are chosen // images from the provider manager are sorted by preference (earlier images are higher priority) so we can verify that low url numbers are chosen
foreach (var image in actualImages) foreach (var image in actualImages)
{ {
var index = int.Parse(Regex.Match(image.Path, @"[0-9]+").Value, NumberStyles.Integer, CultureInfo.InvariantCulture); var index = int.Parse(NumbersRegex().Match(image.Path).ValueSpan, NumberStyles.Integer, CultureInfo.InvariantCulture);
Assert.True(index < imageCount); Assert.True(index < imageCount);
} }
} }

Loading…
Cancel
Save