Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support whitespaces in local URIs #61

Merged
merged 1 commit into from
Sep 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@ func (d *Decoder) Decode(doc *Document) error {
return err
}

for _, b := range doc.Buffers {
if !b.IsEmbeddedResource() {
if uri, ok := sanitizeURI(b.URI); ok {
b.URI = uri
}
}
}
for _, im := range doc.Images {
if !im.IsEmbeddedResource() {
if uri, ok := sanitizeURI(im.URI); ok {
im.URI = uri
}
}
}

var externalBufferIndex = 0
if isBinary && len(doc.Buffers) > 0 {
externalBufferIndex = 1
Expand Down Expand Up @@ -141,12 +156,9 @@ func (d *Decoder) decodeBuffer(buffer *Buffer) error {
} else {
err = validateBufferURI(buffer.URI)
if err == nil && d.Fsys != nil {
uri, ok := sanitizeURI(buffer.URI)
if ok {
buffer.Data, err = fs.ReadFile(d.Fsys, uri)
if len(buffer.Data) > int(buffer.ByteLength) {
buffer.Data = buffer.Data[:buffer.ByteLength:buffer.ByteLength]
}
buffer.Data, err = fs.ReadFile(d.Fsys, buffer.URI)
if len(buffer.Data) > int(buffer.ByteLength) {
buffer.Data = buffer.Data[:buffer.ByteLength:buffer.ByteLength]
}
}
}
Expand Down Expand Up @@ -191,9 +203,17 @@ func sanitizeURI(uri string) (string, bool) {
uri = strings.Replace(uri, "/./", "/", -1)
uri = strings.TrimPrefix(uri, "./")
u, err := url.Parse(uri)
if err != nil || u.IsAbs() {
// Only relative paths supported.
if err != nil {
return "", false
}
return strings.TrimPrefix(u.RequestURI(), "/"), true
if u.Scheme == "" {
// URI should always be decoded before using it in a file path.
uri, err = url.PathUnescape(uri)
if err != nil {
return "", false
}
} else {
uri = u.String()
}
return uri, true
}
38 changes: 34 additions & 4 deletions decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,36 @@ func TestOpen(t *testing.T) {
Scene: Index(0),
Scenes: []*Scene{{Name: "Root Scene", Nodes: []uint32{0}}},
}, false},
{args{"testdata/Box With Spaces/glTF/Box With Spaces.gltf", ""}, &Document{
Accessors: []*Accessor{
{BufferView: Index(0), ComponentType: ComponentFloat, Count: 24, Max: []float32{1, 1, 1}, Min: []float32{-1, -1, -1}, Type: AccessorVec3},
{BufferView: Index(1), ComponentType: ComponentFloat, Count: 24, Type: AccessorVec3},
{BufferView: Index(2), ComponentType: ComponentFloat, Count: 24, Type: AccessorVec2},
{BufferView: Index(3), ComponentType: ComponentUshort, Count: 36, Type: AccessorScalar},
},
Asset: Asset{Generator: "Khronos glTF Blender I/O v1.3.48", Version: "2.0", Copyright: "CC0 by Ed Mackey, AGI"},
BufferViews: []*BufferView{
{ByteLength: 288, ByteOffset: 0},
{ByteLength: 288, ByteOffset: 288},
{ByteLength: 192, ByteOffset: 576},
{ByteLength: 72, ByteOffset: 768},
},
Buffers: []*Buffer{{ByteLength: 840, URI: "Box With Spaces.bin", Data: readFile("testdata/Box With Spaces/glTF/Box With Spaces.bin")}},
Images: []*Image{
{Name: "Normal Map", MimeType: "image/png", URI: "Normal Map.png"},
{Name: "glTF Logo With Spaces", MimeType: "image/png", URI: "glTF Logo With Spaces.png"},
{Name: "Roughness Metallic", MimeType: "image/png", URI: "Roughness Metallic.png"},
},
Materials: []*Material{{
Name: "Material", AlphaMode: AlphaOpaque, AlphaCutoff: Float(0.5), NormalTexture: &NormalTexture{Index: Index(0), Scale: Float(1)}, PBRMetallicRoughness: &PBRMetallicRoughness{
BaseColorFactor: &[4]float32{1, 1, 1, 1}, MetallicFactor: Float(1), RoughnessFactor: Float(1), BaseColorTexture: &TextureInfo{Index: 1}, MetallicRoughnessTexture: &TextureInfo{Index: 2},
}}},
Meshes: []*Mesh{{Name: "Cube", Primitives: []*Primitive{{Indices: Index(3), Material: Index(0), Mode: PrimitiveTriangles, Attributes: map[string]uint32{NORMAL: 1, POSITION: 0, TEXCOORD_0: 2}}}}},
Nodes: []*Node{{Mesh: Index(0), Name: "Cube", Matrix: [16]float32{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, Rotation: [4]float32{0, 0, 0, 1}, Scale: [3]float32{1, 1, 1}}},
Scene: Index(0),
Scenes: []*Scene{{Name: "Scene", Nodes: []uint32{0}}},
Textures: []*Texture{{Source: Index(0)}, {Source: Index(1)}, {Source: Index(2)}},
}, false},
}
for _, tt := range tests {
t.Run(tt.args.name, func(t *testing.T) {
Expand All @@ -122,15 +152,15 @@ func TestOpen(t *testing.T) {
}
if tt.args.embedded != "" {
got, err = Open(tt.args.embedded)
if (err != nil) != tt.wantErr {
t.Errorf("Open() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i, b := range got.Buffers {
if b.IsEmbeddedResource() {
tt.want.Buffers[i].EmbeddedResource()
}
}
if (err != nil) != tt.wantErr {
t.Errorf("Open() error = %v, wantErr %v", err, tt.wantErr)
return
}
if diff := deep.Equal(got, tt.want); diff != nil {
t.Errorf("Open() = %v", diff)
return
Expand Down
102 changes: 102 additions & 0 deletions simple.gltf
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{
"scenes": [
{
"nodes": [0, 1]
}
],
"nodes": [
{
"mesh": 0
},
{
"mesh": 0,
"name": "guid",
"matrix": [
2.0,
0.0,
0.0,
0.0,
0.0,
0.866,
0.5,
0.0,
0.0,
-0.25,
0.433,
0.0,
10.0,
20.0,
30.0,
1.0
]
}
],

"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 1,
"NORMAL": 2
},
"indices": 0
}
]
}
],

"buffers": [
{
"uri": "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8=",
"byteLength": 80
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 6,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 8,
"byteLength": 72,
"target": 34962
}
],
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5123,
"count": 3,
"type": "SCALAR",
"max": [2],
"min": [0]
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 3,
"type": "VEC3",
"max": [1.0, 1.0, 0.0],
"min": [0.0, 0.0, 0.0]
},
{
"bufferView": 1,
"byteOffset": 36,
"componentType": 5126,
"count": 3,
"type": "VEC3",
"max": [0.0, 0.0, 1.0],
"min": [0.0, 0.0, 1.0]
}
],

"asset": {
"version": "2.0"
}
}
21 changes: 21 additions & 0 deletions testdata/Box With Spaces/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Box With Spaces

## Screenshot

![screenshot](screenshot/screenshot_large.png)

## Description

The binary file is called `Box With Spaces.bin`, testing runtime support for the presence of spaces in a URI. Three textures
also have spaces in their URIs, but each space character is URI-encoded as `%20`.

Client implementations are expected to URI-decode all URIs present in a glTF model, even when they represent files on a
local disk. See [#1449](https://github.com/KhronosGroup/glTF/issues/1449) for additional comments.

## License Information

glTF™ is a trademark of the Khronos Group Inc. Follow the [Khronos Logo Usage Guidelines](https://www.khronos.org/legal/trademarks/)
when reproducing the glTF™ logo.

The cube model is [![CC0](http://i.creativecommons.org/p/zero/1.0/88x31.png)](http://creativecommons.org/publicdomain/zero/1.0/)
To the extent possible under law, Analytical Graphics has waived all copyright and related or neighboring rights to this asset.
Binary file added testdata/Box With Spaces/glTF/Box With Spaces.bin
Binary file not shown.
154 changes: 154 additions & 0 deletions testdata/Box With Spaces/glTF/Box With Spaces.gltf
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
{
"asset" : {
"copyright" : "CC0 by Ed Mackey, AGI",
"generator" : "Khronos glTF Blender I/O v1.3.48",
"version" : "2.0"
},
"scene" : 0,
"scenes" : [
{
"name" : "Scene",
"nodes" : [
0
]
}
],
"nodes" : [
{
"mesh" : 0,
"name" : "Cube"
}
],
"materials" : [
{
"emissiveFactor" : [
0,
0,
0
],
"name" : "Material",
"normalTexture" : {
"index" : 0,
"texCoord" : 0
},
"pbrMetallicRoughness" : {
"baseColorTexture" : {
"index" : 1,
"texCoord" : 0
},
"metallicRoughnessTexture" : {
"index" : 2,
"texCoord" : 0
}
}
}
],
"meshes" : [
{
"name" : "Cube",
"primitives" : [
{
"attributes" : {
"POSITION" : 0,
"NORMAL" : 1,
"TEXCOORD_0" : 2
},
"indices" : 3,
"material" : 0
}
]
}
],
"textures" : [
{
"source" : 0
},
{
"source" : 1
},
{
"source" : 2
}
],
"images" : [
{
"mimeType" : "image/png",
"name" : "Normal Map",
"uri" : "Normal%20Map.png"
},
{
"mimeType" : "image/png",
"name" : "glTF Logo With Spaces",
"uri" : "glTF%20Logo%20With%20Spaces.png"
},
{
"mimeType" : "image/png",
"name" : "Roughness Metallic",
"uri" : "Roughness%20Metallic.png"
}
],
"accessors" : [
{
"bufferView" : 0,
"componentType" : 5126,
"count" : 24,
"max" : [
1,
1,
1
],
"min" : [
-1,
-1,
-1
],
"type" : "VEC3"
},
{
"bufferView" : 1,
"componentType" : 5126,
"count" : 24,
"type" : "VEC3"
},
{
"bufferView" : 2,
"componentType" : 5126,
"count" : 24,
"type" : "VEC2"
},
{
"bufferView" : 3,
"componentType" : 5123,
"count" : 36,
"type" : "SCALAR"
}
],
"bufferViews" : [
{
"buffer" : 0,
"byteLength" : 288,
"byteOffset" : 0
},
{
"buffer" : 0,
"byteLength" : 288,
"byteOffset" : 288
},
{
"buffer" : 0,
"byteLength" : 192,
"byteOffset" : 576
},
{
"buffer" : 0,
"byteLength" : 72,
"byteOffset" : 768
}
],
"buffers" : [
{
"byteLength" : 840,
"uri" : "Box With Spaces.bin"
}
]
}
Binary file added testdata/Box With Spaces/glTF/Normal Map.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.