Skip to content

Commit f3c807d

Browse files
authored
Fix concurrent R/W access to link properties (#87)
1 parent 4c3038f commit f3c807d

File tree

11 files changed

+157
-78
lines changed

11 files changed

+157
-78
lines changed

pkg/fetcher/fetcher_archive.go

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,6 @@ func (f *ArchiveFetcher) Links() (manifest.LinkList, error) {
3636
link.Type = mt.String()
3737
}
3838
}
39-
cl := af.CompressedLength()
40-
if cl == 0 {
41-
cl = af.Length()
42-
}
43-
link.Properties.Add(manifest.Properties{
44-
"https://readium.org/webpub-manifest/properties#archive": manifest.Properties{
45-
"entryLength": cl,
46-
"isEntryCompressed": af.CompressedLength() > 0,
47-
},
48-
})
4939
links = append(links, link)
5040
}
5141
return links, nil
@@ -57,10 +47,25 @@ func (f *ArchiveFetcher) Get(link manifest.Link) Resource {
5747
if err != nil {
5848
return NewFailureResource(link, NotFound(err))
5949
}
60-
return &entryResource{
50+
51+
// Compute archive properties
52+
cl := entry.CompressedLength()
53+
if cl == 0 {
54+
cl = entry.Length()
55+
}
56+
57+
er := &entryResource{
6158
link: link,
6259
entry: entry,
60+
properties: manifest.Properties{
61+
"https://readium.org/webpub-manifest/properties#archive": map[string]interface{}{
62+
"entryLength": cl,
63+
"isEntryCompressed": entry.CompressedLength() > 0,
64+
},
65+
},
6366
}
67+
68+
return er
6469
}
6570

6671
// Close implements Fetcher
@@ -90,8 +95,9 @@ func NewArchiveFetcherFromPathWithFactory(path string, factory archive.ArchiveFa
9095

9196
// Resource from archive entry
9297
type entryResource struct {
93-
link manifest.Link
94-
entry archive.Entry
98+
link manifest.Link
99+
entry archive.Entry
100+
properties manifest.Properties
95101
}
96102

97103
// File implements Resource
@@ -104,21 +110,16 @@ func (r *entryResource) Close() {
104110
// Nothing needs to be done at the moment
105111
}
106112

113+
// Link implements Resource
107114
func (r *entryResource) Link() manifest.Link {
108-
cl := r.entry.CompressedLength()
109-
if cl == 0 {
110-
cl = r.entry.Length()
111-
}
112-
r.link.Properties.Add(manifest.Properties{
113-
"https://readium.org/webpub-manifest/properties#archive": manifest.Properties{
114-
"entryLength": cl,
115-
"isEntryCompressed": r.entry.CompressedLength() > 0,
116-
},
117-
})
118-
119115
return r.link
120116
}
121117

118+
// Properties implements Resource
119+
func (r *entryResource) Properties() manifest.Properties {
120+
return r.properties
121+
}
122+
122123
// Read implements Resource
123124
func (r *entryResource) Read(start int64, end int64) ([]byte, *ResourceError) {
124125
data, err := r.entry.Read(start, end)

pkg/fetcher/fetcher_archive_test.go

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,30 @@ func withArchiveFetcher(t *testing.T, callback func(a *ArchiveFetcher)) {
1515
}
1616

1717
func TestArchiveFetcherLinks(t *testing.T) {
18-
makeTestLink := func(href string, typ string, entryLength uint64, isCompressed bool) manifest.Link {
19-
return manifest.Link{
18+
makeTestLink := func(href string, typ string, entryLength uint64, isCompressed bool) struct {
19+
manifest.Link
20+
manifest.Properties
21+
} {
22+
l := manifest.Link{
2023
Href: href,
2124
Type: typ,
22-
Properties: manifest.Properties{
23-
"https://readium.org/webpub-manifest/properties#archive": manifest.Properties{
24-
"entryLength": entryLength,
25-
"isEntryCompressed": isCompressed,
26-
},
25+
}
26+
p := manifest.Properties{
27+
"https://readium.org/webpub-manifest/properties#archive": map[string]interface{}{
28+
"entryLength": entryLength,
29+
"isEntryCompressed": isCompressed,
2730
},
2831
}
32+
return struct {
33+
manifest.Link
34+
manifest.Properties
35+
}{l, p}
2936
}
3037

31-
mustContain := manifest.LinkList{
38+
mustContain := []struct {
39+
manifest.Link
40+
manifest.Properties
41+
}{
3242
makeTestLink("/mimetype", "", 20, false),
3343
makeTestLink("/EPUB/cover.xhtml", "application/xhtml+xml", 259, true),
3444
makeTestLink("/EPUB/css/epub.css", "text/css", 595, true),
@@ -45,7 +55,12 @@ func TestArchiveFetcherLinks(t *testing.T) {
4555
links, err := a.Links()
4656
assert.Nil(t, err)
4757

48-
assert.ElementsMatch(t, mustContain, links)
58+
mustLinks := make([]manifest.Link, len(mustContain))
59+
for i, l := range mustContain {
60+
assert.Equal(t, l.Properties, a.Get(l.Link).Properties())
61+
mustLinks[i] = l.Link
62+
}
63+
assert.ElementsMatch(t, mustLinks, links)
4964
})
5065
}
5166

@@ -128,25 +143,10 @@ func TestArchiveFetcherAddsProperties(t *testing.T) {
128143
withArchiveFetcher(t, func(a *ArchiveFetcher) {
129144
resource := a.Get(manifest.Link{Href: "/EPUB/css/epub.css"})
130145
assert.Equal(t, manifest.Properties{
131-
"https://readium.org/webpub-manifest/properties#archive": manifest.Properties{
132-
"entryLength": uint64(595),
133-
"isEntryCompressed": true,
134-
},
135-
}, resource.Link().Properties)
136-
})
137-
}
138-
139-
func TestArchiveFetcherOriginalPropertiesKept(t *testing.T) {
140-
withArchiveFetcher(t, func(a *ArchiveFetcher) {
141-
resource := a.Get(manifest.Link{Href: "/EPUB/css/epub.css", Properties: manifest.Properties{
142-
"other": "property",
143-
}})
144-
assert.Equal(t, manifest.Properties{
145-
"other": "property",
146-
"https://readium.org/webpub-manifest/properties#archive": manifest.Properties{
146+
"https://readium.org/webpub-manifest/properties#archive": map[string]interface{}{
147147
"entryLength": uint64(595),
148148
"isEntryCompressed": true,
149149
},
150-
}, resource.Link().Properties)
150+
}, resource.Properties())
151151
})
152152
}

pkg/fetcher/fetcher_file.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ func (r *FileResource) Link() manifest.Link {
128128
return r.link
129129
}
130130

131+
func (r *FileResource) Properties() manifest.Properties {
132+
return manifest.Properties{}
133+
}
134+
131135
// Close implements Resource
132136
func (r *FileResource) Close() {
133137
if r.file != nil {

pkg/fetcher/resource.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ type Resource interface {
3838
// It might be modified by the [Resource] to include additional metadata, e.g. the `Content-Type` HTTP header in [Link.Type].
3939
Link() manifest.Link
4040

41+
// Returns the properties associated with the resource.
42+
// This is opened for extensions.
43+
Properties() manifest.Properties
44+
4145
// Returns data length from metadata if available, or calculated from reading the bytes otherwise.
4246
// This value must be treated as a hint, as it might not reflect the actual bytes length. To get the real length, you need to read the whole resource.
4347
Length() (int64, *ResourceError)
@@ -265,6 +269,10 @@ func (r FailureResource) Link() manifest.Link {
265269
return r.link
266270
}
267271

272+
func (r FailureResource) Properties() manifest.Properties {
273+
return manifest.Properties{}
274+
}
275+
268276
// Length implements Resource
269277
func (r FailureResource) Length() (int64, *ResourceError) {
270278
return 0, r.ex
@@ -323,6 +331,10 @@ func (r ProxyResource) Link() manifest.Link {
323331
return r.Res.Link()
324332
}
325333

334+
func (r ProxyResource) Properties() manifest.Properties {
335+
return manifest.Properties{}
336+
}
337+
326338
// Length implements Resource
327339
func (r ProxyResource) Length() (int64, *ResourceError) {
328340
return r.Res.Length()

pkg/fetcher/resource_bytes.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ func (r *BytesResource) Link() manifest.Link {
2929
return r.link
3030
}
3131

32+
// Properties implements Resource
33+
func (r *BytesResource) Properties() manifest.Properties {
34+
return manifest.Properties{}
35+
}
36+
3237
// Length implements Resource
3338
func (r *BytesResource) Length() (int64, *ResourceError) {
3439
bin, err := r.Read(0, 0)

pkg/manifest/link.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,48 @@ func (l *Link) UnmarshalJSON(b []byte) error {
181181
return nil
182182
}
183183

184+
func (l Link) MarshalJSON() ([]byte, error) {
185+
res := make(map[string]interface{})
186+
res["href"] = l.Href
187+
if l.Type != "" {
188+
res["type"] = l.Type
189+
}
190+
if l.Templated {
191+
res["templated"] = l.Templated
192+
}
193+
if l.Title != "" {
194+
res["title"] = l.Title
195+
}
196+
if len(l.Rels) > 0 {
197+
res["rel"] = l.Rels
198+
}
199+
if len(l.Properties) > 0 {
200+
res["properties"] = l.Properties
201+
}
202+
if l.Height > 0 {
203+
res["height"] = l.Height
204+
}
205+
if l.Width > 0 {
206+
res["width"] = l.Width
207+
}
208+
if l.Bitrate > 0 {
209+
res["bitrate"] = l.Bitrate
210+
}
211+
if l.Duration > 0 {
212+
res["duration"] = l.Duration
213+
}
214+
if len(l.Languages) > 0 {
215+
res["language"] = l.Languages
216+
}
217+
if len(l.Alternates) > 0 {
218+
res["alternate"] = l.Alternates
219+
}
220+
if len(l.Children) > 0 {
221+
res["children"] = l.Children
222+
}
223+
return json.Marshal(res)
224+
}
225+
184226
// Slice of links
185227
type LinkList []Link
186228

pkg/manifest/link_test.go

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,19 @@ func TestLinkUnmarshalFullJSON(t *testing.T) {
6666
]
6767
}`), &l))
6868
assert.Equal(t, Link{
69-
Href: "http://href",
70-
Type: "application/pdf",
71-
Templated: true,
72-
Title: "Link Title",
73-
Rels: []string{"publication", "cover"},
74-
Properties: map[string]interface{}{"orientation": "landscape"},
75-
Height: 1024,
76-
Width: 768,
77-
Bitrate: 74.2,
78-
Duration: 45.6,
79-
Languages: []string{"fr"},
69+
Href: "http://href",
70+
Type: "application/pdf",
71+
Templated: true,
72+
Title: "Link Title",
73+
Rels: []string{"publication", "cover"},
74+
Properties: Properties{
75+
"orientation": "landscape",
76+
},
77+
Height: 1024,
78+
Width: 768,
79+
Bitrate: 74.2,
80+
Duration: 45.6,
81+
Languages: []string{"fr"},
8082
Alternates: []Link{
8183
{Href: "/alternate1"},
8284
{Href: "/alternate2"},
@@ -181,17 +183,19 @@ func TestLinkMinimalJSON(t *testing.T) {
181183

182184
func TestLinkFullJSON(t *testing.T) {
183185
b, err := json.Marshal(Link{
184-
Href: "http://href",
185-
Type: "application/pdf",
186-
Templated: true,
187-
Title: "Link Title",
188-
Rels: []string{"publication", "cover"},
189-
Properties: map[string]interface{}{"orientation": "landscape"},
190-
Height: 1024,
191-
Width: 768,
192-
Bitrate: 74.2,
193-
Duration: 45.6,
194-
Languages: []string{"fr"},
186+
Href: "http://href",
187+
Type: "application/pdf",
188+
Templated: true,
189+
Title: "Link Title",
190+
Rels: []string{"publication", "cover"},
191+
Properties: Properties{
192+
"orientation": "landscape",
193+
},
194+
Height: 1024,
195+
Width: 768,
196+
Bitrate: 74.2,
197+
Duration: 45.6,
198+
Languages: []string{"fr"},
195199
Alternates: []Link{
196200
{Href: "/alternate1"},
197201
{Href: "/alternate2"},

pkg/manifest/properties.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import (
88

99
type Properties map[string]interface{}
1010

11-
func (p *Properties) Add(newProperties Properties) Properties {
11+
// Properties should be immutable, therefore these functions have been removed.
12+
// The code is left here in case it's useful in a future implementation.
13+
14+
/*func (p *Properties) Add(newProperties Properties) Properties {
1215
if *p == nil {
1316
*p = make(Properties)
1417
}
@@ -18,6 +21,14 @@ func (p *Properties) Add(newProperties Properties) Properties {
1821
return *p
1922
}
2023
24+
func (p *Properties) Delete(key string) Properties {
25+
if p == nil {
26+
p = &Properties{}
27+
}
28+
delete(*p, key)
29+
return *p
30+
}*/
31+
2132
func (p *Properties) Get(key string) interface{} {
2233
if p != nil {
2334
return (*p)[key]

pkg/manifest/properties_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func TestPropertiesUnmarshalFullJSON(t *testing.T) {
3232
}, p)
3333
}
3434

35-
func TestPropertiesAddGiven(t *testing.T) {
35+
/*func TestPropertiesAddGiven(t *testing.T) {
3636
p2 := Properties{
3737
"other-property1": "value",
3838
"other-property2": []interface{}{float64(42)},
@@ -44,7 +44,7 @@ func TestPropertiesAddGiven(t *testing.T) {
4444
}, p2.Add(Properties{
4545
"additional": "property",
4646
}))
47-
}
47+
}*/
4848

4949
// Presentation-specific properties
5050

pkg/parser/epub/deobfuscator_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ func withDeobfuscator(t *testing.T, href string, algorithm string, start, end in
2828
Href: href,
2929
}
3030
if algorithm != "" {
31-
link.Properties.Add(manifest.Properties{
31+
link.Properties = manifest.Properties{
3232
"encrypted": map[string]interface{}{
3333
"algorithm": algorithm,
3434
},
35-
})
35+
}
3636
}
3737
obfu, err := NewDeobfuscator(identifier).Transform(ft.Get(link)).Read(start, end)
3838
if !assert.Nil(t, err) {

0 commit comments

Comments
 (0)