Przeglądaj źródła

Merge pull request #4799 from tommasodotNET/bug/authorization-header-issue

Authorization header parsing
Claus Vium 3 lat temu
rodzic
commit
8c463b9b81

+ 42 - 15
Jellyfin.Server.Implementations/Security/AuthorizationContext.cs

@@ -5,7 +5,6 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Net;
 using System.Threading.Tasks;
-using Jellyfin.Extensions;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using Microsoft.AspNetCore.Http;
@@ -253,29 +252,57 @@ namespace Jellyfin.Server.Implementations.Security
                 return null;
             }
 
+            // Remove up until the first space
             authorizationHeader = authorizationHeader[(firstSpace + 1)..];
+            return GetParts(authorizationHeader);
+        }
 
-            var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+        /// <summary>
+        /// Get the authorization header components.
+        /// </summary>
+        /// <param name="authorizationHeader">The authorization header.</param>
+        /// <returns>Dictionary{System.StringSystem.String}.</returns>
+        public static Dictionary<string, string> GetParts(ReadOnlySpan<char> authorizationHeader)
+        {
+            var result = new Dictionary<string, string>();
+            var escaped = false;
+            int start = 0;
+            string key = string.Empty;
 
-            foreach (var item in authorizationHeader.Split(','))
+            int i;
+            for (i = 0; i < authorizationHeader.Length; i++)
             {
-                var trimmedItem = item.Trim();
-                var firstEqualsSign = trimmedItem.IndexOf('=');
-
-                if (firstEqualsSign > 0)
+                var token = authorizationHeader[i];
+                if (token == '"' || token == ',')
                 {
-                    var key = trimmedItem[..firstEqualsSign].ToString();
-                    var value = NormalizeValue(trimmedItem[(firstEqualsSign + 1)..].Trim('"').ToString());
-                    result[key] = value;
+                    // Applying a XOR logic to evaluate whether it is opening or closing a value
+                    escaped = (!escaped) == (token == '"');
+                    if (token == ',' && !escaped)
+                    {
+                        // Meeting a comma after a closing escape char means the value is complete
+                        if (start < i)
+                        {
+                            result[key] = WebUtility.UrlDecode(authorizationHeader[start..i].Trim('"').ToString());
+                            key = string.Empty;
+                        }
+
+                        start = i + 1;
+                    }
+                }
+                else if (!escaped && token == '=')
+                {
+                    key = authorizationHeader[start.. i].Trim().ToString();
+                    start = i + 1;
                 }
             }
 
-            return result;
-        }
+            // Add last value
+            if (start < i)
+            {
+                result[key] = WebUtility.UrlDecode(authorizationHeader[start..i].Trim('"').ToString());
+            }
 
-        private static string NormalizeValue(string value)
-        {
-            return string.IsNullOrEmpty(value) ? value : WebUtility.HtmlEncode(value);
+            return result;
         }
     }
 }

+ 57 - 0
tests/Jellyfin.Api.Tests/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandlerTests.cs

@@ -4,6 +4,7 @@ using AutoFixture;
 using AutoFixture.AutoMoq;
 using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
 using Jellyfin.Api.Constants;
+using Jellyfin.Server.Implementations.Security;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.Library;
 using Microsoft.AspNetCore.Authorization;
@@ -49,5 +50,61 @@ namespace Jellyfin.Api.Tests.Auth.DefaultAuthorizationPolicy
             await _sut.HandleAsync(context);
             Assert.True(context.HasSucceeded);
         }
+
+        [Theory]
+        [MemberData(nameof(GetParts_ValidAuthHeader_Success_Data))]
+        public void GetParts_ValidAuthHeader_Success(string input, Dictionary<string, string> parts)
+        {
+            var dict = AuthorizationContext.GetParts(input);
+            foreach (var (key, value) in parts)
+            {
+                Assert.Equal(dict[key], value);
+            }
+        }
+
+        private static TheoryData<string, Dictionary<string, string>> GetParts_ValidAuthHeader_Success_Data()
+        {
+            var data = new TheoryData<string, Dictionary<string, string>>();
+
+            data.Add(
+                "x=\"123,123\",y=\"123\"",
+                new Dictionary<string, string>
+                {
+                    { "x", "123,123" },
+                    { "y", "123" }
+                });
+
+            data.Add(
+                "x=\"123,123\",         y=\"123\",z=\"'hi'\"",
+                new Dictionary<string, string>
+                {
+                    { "x", "123,123" },
+                    { "y", "123" },
+                    { "z", "'hi'" }
+                });
+
+            data.Add(
+                "x=\"ab\"",
+                new Dictionary<string, string>
+                {
+                    { "x", "ab" }
+                });
+
+            data.Add(
+                "param=Hörbücher",
+                new Dictionary<string, string>
+                {
+                    { "param", "Hörbücher" }
+                });
+
+            data.Add(
+                "param=%22%Hörbücher",
+                new Dictionary<string, string>
+                {
+                    { "param", "\"%Hörbücher" }
+                });
+
+            return data;
+        }
     }
 }