From f7fafa852b07737556a9985ab92fff56c4418f48 Mon Sep 17 00:00:00 2001 From: Vicente Penades <5433822+vpenades@users.noreply.github.com> Date: Tue, 14 Apr 2020 23:36:13 +0200 Subject: [PATCH] Temporary fix for unescaped URI characters. --- src/Shared/Guard.cs | 12 ++++--- src/Shared/_Extensions.cs | 7 ++++ src/SharpGLTF.Core/IO/ReadContext.cs | 14 ++++++-- src/SharpGLTF.Core/IO/WriteContext.cs | 16 ++++++--- src/SharpGLTF.Core/Schema2/gltf.Buffer.cs | 15 ++++---- src/SharpGLTF.Core/Schema2/gltf.Images.cs | 36 ++++++++----------- src/SharpGLTF.Toolkit/IO/Zip.cs | 4 ++- src/SharpGLTF.Toolkit/Scenes/readme.md | 4 +-- tests/Assets/white space.gltf | 3 ++ tests/SharpGLTF.NUnit/gltf_validator.cs | 2 +- .../Authoring/ExtensionsCreationTests.cs | 6 ++-- .../LoadAndSave/LoadSpecialModelsTest.cs | 2 +- .../Validation/InvalidFilesTests.cs | 5 ++- .../Scenes/SceneBuilderTests.cs | 2 +- 14 files changed, 79 insertions(+), 49 deletions(-) diff --git a/src/Shared/Guard.cs b/src/Shared/Guard.cs index 9281e322..a3a66afd 100644 --- a/src/Shared/Guard.cs +++ b/src/Shared/Guard.cs @@ -190,6 +190,11 @@ public static void IsFalse(bool target, string parameterName, string message = " #region specialised + private static readonly IReadOnlyList _InvalidRelativePathChars = + System.IO.Path.GetInvalidFileNameChars() + .Where(c => c != '/' && c != '\\') + .ToArray(); + public static void IsValidURI(string parameterName, string gltfURI, params string[] validHeaders) { if (string.IsNullOrEmpty(gltfURI)) return; @@ -206,12 +211,11 @@ public static void IsValidURI(string parameterName, string gltfURI, params strin } } - if (gltfURI.StartsWith("data:")) throw new ArgumentException($"Invalid URI '{gltfURI}'."); + if (gltfURI.Any(c => _InvalidRelativePathChars.Contains(c))) throw new ArgumentException($"Invalid URI '{gltfURI}'."); - gltfURI = Uri.EscapeUriString(gltfURI); + if (gltfURI.Any(chr => char.IsWhiteSpace(chr))) gltfURI = Uri.EscapeUriString(gltfURI); - if (!Uri.IsWellFormedUriString(gltfURI, UriKind.RelativeOrAbsolute)) throw new ArgumentException($"Invalid URI '{gltfURI}'."); - if (!Uri.TryCreate(gltfURI, UriKind.RelativeOrAbsolute, out Uri xuri)) throw new ArgumentException($"Invalid URI '{gltfURI}'."); + if (!Uri.TryCreate(gltfURI, UriKind.Relative, out Uri xuri)) throw new ArgumentException($"Invalid URI '{gltfURI}'."); return; } diff --git a/src/Shared/_Extensions.cs b/src/Shared/_Extensions.cs index f1a49a67..d81cb231 100644 --- a/src/Shared/_Extensions.cs +++ b/src/Shared/_Extensions.cs @@ -638,6 +638,13 @@ private static bool _IsDegenerated(uint a, uint b, uint c) #region serialization + public static Byte[] ToUnderlayingArray(this ArraySegment segment) + { + if (segment.Offset == 0 && segment.Count == segment.Array.Length) return segment.Array; + + return segment.ToArray(); + } + public static ArraySegment ToArraySegment(this System.IO.MemoryStream m) { if (m.TryGetBuffer(out ArraySegment data)) return data; diff --git a/src/SharpGLTF.Core/IO/ReadContext.cs b/src/SharpGLTF.Core/IO/ReadContext.cs index c3246561..63d5f259 100644 --- a/src/SharpGLTF.Core/IO/ReadContext.cs +++ b/src/SharpGLTF.Core/IO/ReadContext.cs @@ -46,12 +46,22 @@ public static ReadContext CreateFromFile(string filePath) public static ReadContext CreateFromDirectory(string directoryPath) { - return new ReadContext(assetFileName => new BYTES(File.ReadAllBytes(Path.Combine(directoryPath, assetFileName)))); + BYTES _loadFile(string rawUri) + { + var path = Uri.UnescapeDataString(rawUri); + path = Path.Combine(directoryPath, path); + + var content = File.ReadAllBytes(path); + + return new BYTES(content); + } + + return new ReadContext(_loadFile); } public static ReadContext CreateFromDictionary(IReadOnlyDictionary dictionary) { - return new ReadContext(fn => dictionary[fn]); + return new ReadContext(rawUri => dictionary[rawUri]); } private ReadContext(FileReaderCallback reader) diff --git a/src/SharpGLTF.Core/IO/WriteContext.cs b/src/SharpGLTF.Core/IO/WriteContext.cs index 91c3dd06..654582f6 100644 --- a/src/SharpGLTF.Core/IO/WriteContext.cs +++ b/src/SharpGLTF.Core/IO/WriteContext.cs @@ -46,11 +46,19 @@ public static WriteContext CreateFromFile(string filePath) return CreateFromDirectory(dir); } - public static WriteContext CreateFromDirectory(string dirPath) + public static WriteContext CreateFromDirectory(string directoryPath) { - Guard.DirectoryPathMustExist(dirPath, nameof(dirPath)); + Guard.DirectoryPathMustExist(directoryPath, nameof(directoryPath)); - var context = Create((fn, d) => File.WriteAllBytes(Path.Combine(dirPath, fn), d.ToArray())); + void _saveFile(string rawUri, BYTES data) + { + var path = Uri.UnescapeDataString(rawUri); + path = Path.Combine(directoryPath, path); + + File.WriteAllBytes(path, data.ToUnderlayingArray()); + } + + var context = Create(_saveFile); context.ImageWriting = ResourceWriteMode.SatelliteFile; context.JsonIndented = true; return context; @@ -60,7 +68,7 @@ public static WriteContext CreateFromDictionary(IDictionary dict) { Guard.NotNull(dict, nameof(dict)); - var context = Create((fn, buff) => dict[fn] = buff); + var context = Create((rawUri, data) => dict[rawUri] = data); context.ImageWriting = ResourceWriteMode.SatelliteFile; context.MergeBuffers = false; context.JsonIndented = false; diff --git a/src/SharpGLTF.Core/Schema2/gltf.Buffer.cs b/src/SharpGLTF.Core/Schema2/gltf.Buffer.cs index 2eb1173d..3eccbb93 100644 --- a/src/SharpGLTF.Core/Schema2/gltf.Buffer.cs +++ b/src/SharpGLTF.Core/Schema2/gltf.Buffer.cs @@ -48,14 +48,17 @@ internal void _ResolveUri(IO.ReadContext context) { _Content = _LoadBinaryBufferUnchecked(_uri, context); - // if (_uri == null) _byteLength = _Content.Length; // fixes "valid_placeholder.glb" case - _uri = null; // When _Data is not empty, clear URI } private static Byte[] _LoadBinaryBufferUnchecked(string uri, IO.ReadContext context) { - return uri.TryParseBase64Unchecked(EMBEDDEDGLTFBUFFER, EMBEDDEDOCTETSTREAM) ?? context.ReadAllBytesToEnd(uri).ToArray(); + var data = uri.TryParseBase64Unchecked(EMBEDDEDGLTFBUFFER, EMBEDDEDOCTETSTREAM); + if (data != null) return data; + + return context + .ReadAllBytesToEnd(uri) + .ToUnderlayingArray(); } #endregion @@ -69,10 +72,10 @@ private static Byte[] _LoadBinaryBufferUnchecked(string uri, IO.ReadContext cont /// A local satellite URI internal void _WriteToSatellite(IO.WriteContext writer, string satelliteUri) { - this._uri = satelliteUri; - this._byteLength = _Content.Length; + writer.WriteAllBytesToEnd(satelliteUri, new ArraySegment(_Content.GetPaddedContent())); - writer.WriteAllBytesToEnd(satelliteUri, new ArraySegment(_Content.GetPaddedContent()) ); + this._uri = Uri.EscapeDataString(satelliteUri); + this._byteLength = _Content.Length; } /// diff --git a/src/SharpGLTF.Core/Schema2/gltf.Images.cs b/src/SharpGLTF.Core/Schema2/gltf.Images.cs index 88dfd43e..3de40250 100644 --- a/src/SharpGLTF.Core/Schema2/gltf.Images.cs +++ b/src/SharpGLTF.Core/Schema2/gltf.Images.cs @@ -181,27 +181,17 @@ internal void _ResolveUri(IO.ReadContext context) { if (String.IsNullOrWhiteSpace(_uri)) return; - byte[] data = null; - - try - { - data = Memory.MemoryImage.TryParseBytes(_uri); - } - catch (ArgumentException argex) - { - throw new Validation.DataException(this, argex.Message); - } + var data = Memory.MemoryImage.TryParseBytes(_uri); if (data == null) { - var bytes = context.ReadAllBytesToEnd(_uri); - - // let's try to avoid making a copy if it's not neccesary. - if (bytes.Offset == 0 && bytes.Array.Length == bytes.Count) data = bytes.Array; - else data = bytes.ToArray(); + data = context + .ReadAllBytesToEnd(_uri) + .ToUnderlayingArray(); } _SatelliteImageContent = data; + _uri = null; _mimeType = null; } @@ -226,8 +216,9 @@ internal void _WriteToInternal() if (_SatelliteImageContent == null) { _WriteAsBufferView(); return; } var imimg = new Memory.MemoryImage(_SatelliteImageContent); - _mimeType = imimg.MimeType; + _uri = imimg.ToMime64(); + _mimeType = imimg.MimeType; } /// @@ -243,13 +234,15 @@ internal void _WriteToSatellite(IO.WriteContext writer, string satelliteUri) return; } - _mimeType = null; - var imimg = new Memory.MemoryImage(_SatelliteImageContent); if (!imimg.IsValid) throw new InvalidOperationException(); - _uri = System.IO.Path.ChangeExtension(satelliteUri, imimg.FileExtension); - writer.WriteAllBytesToEnd(_uri, imimg.GetBuffer()); + satelliteUri = System.IO.Path.ChangeExtension(satelliteUri, imimg.FileExtension); + + writer.WriteAllBytesToEnd(satelliteUri, imimg.GetBuffer()); + + _uri = Uri.EscapeDataString(satelliteUri); + _mimeType = null; } private void _WriteAsBufferView() @@ -259,6 +252,7 @@ private void _WriteAsBufferView() var imimg = this.MemoryImage; if (!imimg.IsValid) throw new InvalidOperationException(); + _uri = null; _mimeType = imimg.MimeType; } @@ -269,8 +263,8 @@ private void _WriteAsBufferView() /// internal void _ClearAfterWrite() { - _mimeType = null; _uri = null; + _mimeType = null; } #endregion diff --git a/src/SharpGLTF.Toolkit/IO/Zip.cs b/src/SharpGLTF.Toolkit/IO/Zip.cs index d3b49083..c5425719 100644 --- a/src/SharpGLTF.Toolkit/IO/Zip.cs +++ b/src/SharpGLTF.Toolkit/IO/Zip.cs @@ -79,8 +79,10 @@ private ModelRoot _LoadSchema2(string gltfFile, ReadSettings settings = null) } } - private ArraySegment _ReadAsset(string filePath) + private ArraySegment _ReadAsset(string rawUri) { + var filePath = Uri.UnescapeDataString(rawUri); + System.IO.Compression.ZipArchiveEntry entry = _FindEntry(filePath); using (var s = entry.Open()) diff --git a/src/SharpGLTF.Toolkit/Scenes/readme.md b/src/SharpGLTF.Toolkit/Scenes/readme.md index 3c24d4d2..a02b201a 100644 --- a/src/SharpGLTF.Toolkit/Scenes/readme.md +++ b/src/SharpGLTF.Toolkit/Scenes/readme.md @@ -26,8 +26,8 @@ and how you want to render it, so every AddMesh method adds an mesh instance to ```c# scene = new SceneBuilder(); -scene.AddMesh(...); -scene.AddMesh(...); +scene.AddRigidMesh(...); +scene.AddRigidMesh(...); scene.AddSkinnedMesh(...); scene.SaveGLB("scene.glb"); ``` diff --git a/tests/Assets/white space.gltf b/tests/Assets/white space.gltf index 4d873d17..87239cf2 100644 --- a/tests/Assets/white space.gltf +++ b/tests/Assets/white space.gltf @@ -5,6 +5,9 @@ "images": [ { "uri": "white space.jpg" + }, +{ + "uri": "white%20space.jpg" } ] } \ No newline at end of file diff --git a/tests/SharpGLTF.NUnit/gltf_validator.cs b/tests/SharpGLTF.NUnit/gltf_validator.cs index 9235fc36..edddc8e0 100644 --- a/tests/SharpGLTF.NUnit/gltf_validator.cs +++ b/tests/SharpGLTF.NUnit/gltf_validator.cs @@ -97,7 +97,7 @@ internal ValidationReport(string srcPath, string rawReport) public bool HasWarnings => Warnings.Count > 0; public bool HasErrors => Errors.Count > 0; - public string ToString() + public override string ToString() { return RawReport; diff --git a/tests/SharpGLTF.Tests/Schema2/Authoring/ExtensionsCreationTests.cs b/tests/SharpGLTF.Tests/Schema2/Authoring/ExtensionsCreationTests.cs index 82fa301e..30d3fbca 100644 --- a/tests/SharpGLTF.Tests/Schema2/Authoring/ExtensionsCreationTests.cs +++ b/tests/SharpGLTF.Tests/Schema2/Authoring/ExtensionsCreationTests.cs @@ -34,7 +34,7 @@ public void CreateSceneWithWithLightsExtension() .PunctualLight = root.CreatePunctualLight(PunctualLightType.Directional) .WithColor(Vector3.UnitX, 2); - var node2 = scene.CreateNode() + scene.CreateNode() .PunctualLight = root.CreatePunctualLight(PunctualLightType.Spot) .WithColor(Vector3.UnitY, 3, 10) .WithSpotCone(0.2f, 0.3f); @@ -79,7 +79,7 @@ public void CreateSceneWithSpecularGlossinessExtension() ); var scene = new Scenes.SceneBuilder(); - scene.AddMesh(mesh, Matrix4x4.Identity); + scene.AddRigidMesh(mesh, Matrix4x4.Identity); scene.AttachToCurrentTest("result.glb"); scene.AttachToCurrentTest("result.gltf"); @@ -115,7 +115,7 @@ public void CreateSceneWithClearCoatExtension() ); var scene = new Scenes.SceneBuilder(); - scene.AddMesh(mesh, Matrix4x4.Identity); + scene.AddRigidMesh(mesh, Matrix4x4.Identity); scene.AttachToCurrentTest("result.glb"); scene.AttachToCurrentTest("result.gltf"); diff --git a/tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadSpecialModelsTest.cs b/tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadSpecialModelsTest.cs index ec5757b3..8aa5349a 100644 --- a/tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadSpecialModelsTest.cs +++ b/tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadSpecialModelsTest.cs @@ -25,7 +25,7 @@ public void Setup() #endregion [Test] - public void LoadWithWhiteSpaceTexture() + public void LoadEscapedUriModel() { TestContext.CurrentContext.AttachShowDirLink(); diff --git a/tests/SharpGLTF.Tests/Validation/InvalidFilesTests.cs b/tests/SharpGLTF.Tests/Validation/InvalidFilesTests.cs index 9dd4af2c..a4f78e48 100644 --- a/tests/SharpGLTF.Tests/Validation/InvalidFilesTests.cs +++ b/tests/SharpGLTF.Tests/Validation/InvalidFilesTests.cs @@ -60,7 +60,7 @@ public void CheckInvalidFiles() foreach (var f in files) { - // System.Diagnostics.Debug.Assert(!f.EndsWith("unresolved_source.gltf")); + // System.Diagnostics.Debug.Assert(!f.EndsWith("invalid_uri_scheme.gltf")); var gltfJson = f.EndsWith(".gltf") ? System.IO.File.ReadAllText(f) : string.Empty; @@ -76,7 +76,6 @@ public void CheckInvalidFiles() Assert.AreEqual(report.Issues.NumErrors > 0, result.HasErrors); } - } - + } } } diff --git a/tests/SharpGLTF.Toolkit.Tests/Scenes/SceneBuilderTests.cs b/tests/SharpGLTF.Toolkit.Tests/Scenes/SceneBuilderTests.cs index 8ecd9d5d..5de9a7b9 100644 --- a/tests/SharpGLTF.Toolkit.Tests/Scenes/SceneBuilderTests.cs +++ b/tests/SharpGLTF.Toolkit.Tests/Scenes/SceneBuilderTests.cs @@ -542,7 +542,7 @@ public void TestCreateEmptyMesh() // create a scenebuilder with an empty mesh var sb = new SceneBuilder(); - sb.AddMesh(VPOSNRM.CreateCompatibleMesh("Empty"), Matrix4x4.Identity); + sb.AddRigidMesh(VPOSNRM.CreateCompatibleMesh("Empty"), Matrix4x4.Identity); var schema = sb.ToGltf2();