diff --git a/src/ReportingApi/IReportBody.cs b/src/ReportingApi/IReportBody.cs new file mode 100644 index 0000000..0d1fcf0 --- /dev/null +++ b/src/ReportingApi/IReportBody.cs @@ -0,0 +1,5 @@ +namespace ReportingApi; + +public interface IReportBody +{ +} \ No newline at end of file diff --git a/src/ReportingApi/JsonConverters/ReportRequestConverter.cs b/src/ReportingApi/JsonConverters/ReportRequestConverter.cs new file mode 100644 index 0000000..9eecc3b --- /dev/null +++ b/src/ReportingApi/JsonConverters/ReportRequestConverter.cs @@ -0,0 +1,85 @@ +using ReportingApi.Models; +using System; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace ReportingApi.JsonConverters; + +public class ReportRequestConverter : JsonConverter +{ + public const string TypeDiscriminator = "type"; + + public override ReportRequest? Read( + ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + var readerClone = reader; + + if (readerClone.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + + readerClone.Read(); + + var depth = readerClone.CurrentDepth; + while (readerClone.Read()) + { + if (depth < readerClone.CurrentDepth) + { + continue; + } + + switch (readerClone.TokenType) + { + case JsonTokenType.PropertyName: + if (readerClone.GetString() == TypeDiscriminator) + { + readerClone.Read(); + + if (readerClone.TokenType is JsonTokenType.String) + { + var bodyType = GetBodyType(readerClone.ValueSpan); + + var targetType = typeof(ReportRequest<>).MakeGenericType(bodyType); + + return (ReportRequest?) JsonSerializer.Deserialize( + ref reader, targetType, options + ); + } + } + + break; + + case JsonTokenType.StartArray: + case JsonTokenType.StartObject: + readerClone.Skip(); + + break; + } + } + + throw new JsonException($"Expected '{TypeDiscriminator}' property, nothing found"); + } + + private Type GetBodyType(ReadOnlySpan type) + { + if (type.SequenceEqual("csp-violation"u8)) + { + return typeof(CspReport); + } + + var typeString = Encoding.UTF8.GetString(type); + throw new JsonException($"Type '{typeString}' does not have defined body type"); + } + + public override void Write( + Utf8JsonWriter writer, ReportRequest value, JsonSerializerOptions options + ) => throw new NotImplementedException(); +} diff --git a/src/ReportingApi/Models/CspReport.cs b/src/ReportingApi/Models/CspReport.cs new file mode 100644 index 0000000..23713a9 --- /dev/null +++ b/src/ReportingApi/Models/CspReport.cs @@ -0,0 +1,36 @@ +using System.Text.Json.Serialization; + +namespace ReportingApi.Models; + +public class CspReport : IReportBody +{ + [JsonPropertyName("blockedURL")] + public string BlockedUri { get; set; } = null!; + + [JsonPropertyName("disposition")] + public string Disposition { get; set; } = null!; + + [JsonPropertyName("documentURL")] + public string DocumentUri { get; set; } = null!; + + [JsonPropertyName("effectiveDirective")] + public string EffectiveDirective { get; set; } = null!; + + [JsonPropertyName("lineNumber")] + public int LineNumber { get; set; } + + [JsonPropertyName("originalPolicy")] + public string OriginalPolicy { get; set; } = null!; + + [JsonPropertyName("referrer")] + public string Referrer { get; set; } = null!; + + [JsonPropertyName("sample")] + public string Sample { get; set; } = null!; + + [JsonPropertyName("sourceFile")] + public string SourceFile { get; set; } = null!; + + [JsonPropertyName("statusCode")] + public int StatusCode { get; set; } +} diff --git a/src/ReportingApi/Models/ReportRequest.cs b/src/ReportingApi/Models/ReportRequest.cs new file mode 100644 index 0000000..7480ced --- /dev/null +++ b/src/ReportingApi/Models/ReportRequest.cs @@ -0,0 +1,27 @@ +using ReportingApi.JsonConverters; +using System.Text.Json.Serialization; + +namespace ReportingApi.Models; + +[JsonConverter(typeof(ReportRequestConverter))] +public class ReportRequest +{ + [JsonPropertyName("age")] + public int Age { get; set; } + + [JsonPropertyName("url")] + public string Url { get; set; } = null!; + + [JsonPropertyName("user_agent")] + public string UserAgent { get; set; } = null!; +} + +public class ReportRequest : ReportRequest + where TBody : class, IReportBody +{ + [JsonPropertyName("body")] + public TBody Body { get; set; } = null!; + + [JsonPropertyName(ReportRequestConverter.TypeDiscriminator)] + public string Type { get; set; } = null!; +} diff --git a/src/ReportingApi/ReportingApi.csproj b/src/ReportingApi/ReportingApi.csproj index e9bebf9..c248531 100644 --- a/src/ReportingApi/ReportingApi.csproj +++ b/src/ReportingApi/ReportingApi.csproj @@ -19,4 +19,6 @@ + +