From 83bc99256ab56db40ec2d2a56ad8d2db75bd3e6d Mon Sep 17 00:00:00 2001 From: Haacked Date: Mon, 29 Dec 2014 22:51:16 -0800 Subject: [PATCH] Add attribute to serialize strings as base64 encoded --- Octokit.Tests/SimpleJsonSerializerTests.cs | 40 ++++++++++- Octokit/Helpers/SerializeAsBase64Attribute.cs | 12 ++++ Octokit/Http/SimpleJsonSerializer.cs | 71 ++++++++++++++++--- Octokit/Octokit-Mono.csproj | 3 +- Octokit/Octokit-MonoAndroid.csproj | 1 + Octokit/Octokit-Monotouch.csproj | 1 + Octokit/Octokit-Portable.csproj | 1 + Octokit/Octokit-netcore45.csproj | 1 + Octokit/Octokit.csproj | 1 + 9 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 Octokit/Helpers/SerializeAsBase64Attribute.cs diff --git a/Octokit.Tests/SimpleJsonSerializerTests.cs b/Octokit.Tests/SimpleJsonSerializerTests.cs index 499d26c2..2e6ae9b1 100644 --- a/Octokit.Tests/SimpleJsonSerializerTests.cs +++ b/Octokit.Tests/SimpleJsonSerializerTests.cs @@ -1,4 +1,4 @@ -using System; +using Octokit.Helpers; using Octokit.Internal; using Xunit; @@ -63,6 +63,21 @@ namespace Octokit.Tests Assert.Equal("{\"int\":42,\"bool\":true}", json); } + + [Fact] + public void HandlesBase64EncodedStrings() + { + var item = new SomeObject + { + Name = "Ferris Bueller", + Content = "Day off", + Description = "stuff" + }; + + var json = new SimpleJsonSerializer().Serialize(item); + + Assert.Equal("{\"name\":\"RmVycmlzIEJ1ZWxsZXI=\",\"description\":\"stuff\",\"content\":\"RGF5IG9mZg==\"}", json); + } } public class TheDeserializeMethod @@ -80,6 +95,18 @@ namespace Octokit.Tests Assert.True(sample.Private); } + [Fact] + public void UnencodesBase64Strings() + { + const string json = "{\"name\":\"RmVycmlzIEJ1ZWxsZXI=\",\"description\":\"stuff\",\"content\":\"RGF5IG9mZg==\"}"; + + var someObject = new SimpleJsonSerializer().Deserialize(json); + + Assert.Equal("Ferris Bueller", someObject.Name); + Assert.Equal("Day off", someObject.Content); + Assert.Equal("stuff", someObject.Description); + } + [Fact] public void CanDeserializeOrganization() { @@ -154,5 +181,16 @@ namespace Octokit.Tests [Parameter(Key = "_links")] public string Links { get; set; } } + + public class SomeObject + { + [SerializeAsBase64] + public string Name { get; set; } + + [SerializeAsBase64] + public string Content; + + public string Description { get; set; } + } } } diff --git a/Octokit/Helpers/SerializeAsBase64Attribute.cs b/Octokit/Helpers/SerializeAsBase64Attribute.cs new file mode 100644 index 00000000..68a240eb --- /dev/null +++ b/Octokit/Helpers/SerializeAsBase64Attribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Octokit.Helpers +{ + /// + /// Attribute used to denote that a string property should be serialized as a base64 encoded string. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class SerializeAsBase64Attribute : Attribute + { + } +} diff --git a/Octokit/Http/SimpleJsonSerializer.cs b/Octokit/Http/SimpleJsonSerializer.cs index 3c879970..94b11a66 100644 --- a/Octokit/Http/SimpleJsonSerializer.cs +++ b/Octokit/Http/SimpleJsonSerializer.cs @@ -1,8 +1,9 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Text; +using Octokit.Helpers; using Octokit.Reflection; namespace Octokit.Internal @@ -23,8 +24,8 @@ namespace Octokit.Internal class GitHubSerializerStrategy : PocoJsonSerializerStrategy { - readonly List _membersWhichShouldPublishNull - = new List(); + readonly List membersWhichShouldPublishNull = new List(); + readonly List membersWhichShouldBeBase64Encoded = new List(); protected override string MapClrMemberToJsonFieldName(MemberInfo member) { @@ -56,19 +57,30 @@ namespace Octokit.Internal var getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); if (getMethod.IsStatic || !getMethod.IsPublic) continue; - var attribute = propertyInfo.GetCustomAttribute(); - if (attribute == null) + var base64Attribute = propertyInfo.GetCustomAttribute(); + if (base64Attribute != null) + { + membersWhichShouldBeBase64Encoded.Add(fullName + MapClrMemberToJsonFieldName(propertyInfo)); + } + var serializeNullAttribute = propertyInfo.GetCustomAttribute(); + if (serializeNullAttribute == null) continue; - _membersWhichShouldPublishNull.Add(fullName + MapClrMemberToJsonFieldName(propertyInfo)); + membersWhichShouldPublishNull.Add(fullName + MapClrMemberToJsonFieldName(propertyInfo)); } + foreach (var fieldInfo in ReflectionUtils.GetFields(type)) { if (fieldInfo.IsStatic || !fieldInfo.IsPublic) continue; + var base64Attribute = fieldInfo.GetCustomAttribute(); + if (base64Attribute != null) + { + membersWhichShouldBeBase64Encoded.Add(fullName + MapClrMemberToJsonFieldName(fieldInfo)); + } var attribute = fieldInfo.GetCustomAttribute(); if (attribute == null) continue; - _membersWhichShouldPublishNull.Add(fullName + MapClrMemberToJsonFieldName(fieldInfo)); + membersWhichShouldPublishNull.Add(fullName + MapClrMemberToJsonFieldName(fieldInfo)); } return base.GetterValueFactory(type); @@ -91,10 +103,18 @@ namespace Octokit.Internal if (value == null) { var key = type.FullName + "-" + getter.Key; - if (!_membersWhichShouldPublishNull.Contains(key)) + if (!membersWhichShouldPublishNull.Contains(key)) continue; } - + else + { + var key = type.FullName + "-" + getter.Key; + if (membersWhichShouldBeBase64Encoded.Contains(key)) + { + var stringValue = value as string ?? ""; + value = Convert.ToBase64String(Encoding.UTF8.GetBytes(stringValue)); + } + } jsonObject.Add(getter.Key, value); } } @@ -143,7 +163,38 @@ namespace Octokit.Internal } } - return base.DeserializeObject(value, type); + var deserialized = base.DeserializeObject(value, type); + + // Handle base64 encoding + foreach (var propertyInfo in ReflectionUtils.GetProperties(type)) + { + if (!propertyInfo.CanRead) continue; + if (!propertyInfo.CanWrite) continue; + if (propertyInfo.GetCustomAttribute() != null) + { + var propertyValue = propertyInfo.GetValue(deserialized) as string; + if (propertyValue != null) + { + var unencoded = Encoding.UTF8.GetString(Convert.FromBase64String(propertyValue)); + propertyInfo.SetValue(deserialized, unencoded); + } + } + } + + foreach (var fieldInfo in ReflectionUtils.GetFields(type)) + { + if (fieldInfo.GetCustomAttribute() != null) + { + var propertyValue = fieldInfo.GetValue(deserialized) as string; + if (propertyValue != null) + { + var unencoded = Encoding.UTF8.GetString(Convert.FromBase64String(propertyValue)); + fieldInfo.SetValue(deserialized, unencoded); + } + } + } + + return deserialized; } } } diff --git a/Octokit/Octokit-Mono.csproj b/Octokit/Octokit-Mono.csproj index 5716bbd8..b862edf6 100644 --- a/Octokit/Octokit-Mono.csproj +++ b/Octokit/Octokit-Mono.csproj @@ -352,6 +352,7 @@ + - + \ No newline at end of file diff --git a/Octokit/Octokit-MonoAndroid.csproj b/Octokit/Octokit-MonoAndroid.csproj index 34a17439..06a52c62 100644 --- a/Octokit/Octokit-MonoAndroid.csproj +++ b/Octokit/Octokit-MonoAndroid.csproj @@ -362,6 +362,7 @@ + \ No newline at end of file diff --git a/Octokit/Octokit-Monotouch.csproj b/Octokit/Octokit-Monotouch.csproj index 11993032..af746d31 100644 --- a/Octokit/Octokit-Monotouch.csproj +++ b/Octokit/Octokit-Monotouch.csproj @@ -357,6 +357,7 @@ + diff --git a/Octokit/Octokit-Portable.csproj b/Octokit/Octokit-Portable.csproj index 33a7b775..54e82861 100644 --- a/Octokit/Octokit-Portable.csproj +++ b/Octokit/Octokit-Portable.csproj @@ -350,6 +350,7 @@ + diff --git a/Octokit/Octokit-netcore45.csproj b/Octokit/Octokit-netcore45.csproj index a1cc1326..244f2ae5 100644 --- a/Octokit/Octokit-netcore45.csproj +++ b/Octokit/Octokit-netcore45.csproj @@ -354,6 +354,7 @@ + diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index 27cd556b..0f8e3ad1 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -365,6 +365,7 @@ +