From 925acd5eb8d39575d774c454c7c6a432fef71351 Mon Sep 17 00:00:00 2001 From: Leonard Richardson Date: Thu, 20 Aug 2026 16:46:39 -0400 Subject: [PATCH 1/4] Put a nil guard on references to link.MediaType. --- pkg/manifest/link.go | 16 ++++++++-------- pkg/streamer/a11y_infer.go | 9 ++++++--- pkg/streamer/a11y_infer_test.go | 28 ++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/pkg/manifest/link.go b/pkg/manifest/link.go index fec9d395..aa874827 100644 --- a/pkg/manifest/link.go +++ b/pkg/manifest/link.go @@ -257,7 +257,7 @@ func (ll LinkList) FilterByRel(rel string) LinkList { // Finds the first link matching the given media type. func (ll LinkList) FirstWithMediaType(mt *mediatype.MediaType) *Link { for _, link := range ll { - if link.MediaType.Matches(mt) { + if link.MediaType != nil && link.MediaType.Matches(mt) { return &link } } @@ -268,7 +268,7 @@ func (ll LinkList) FirstWithMediaType(mt *mediatype.MediaType) *Link { func (ll LinkList) FilterByMediaType(mt ...*mediatype.MediaType) LinkList { flinks := make(LinkList, 0) for _, link := range ll { - if link.MediaType.Matches(mt...) { + if link.MediaType != nil && link.MediaType.Matches(mt...) { flinks = append(flinks, link) } } @@ -278,7 +278,7 @@ func (ll LinkList) FilterByMediaType(mt ...*mediatype.MediaType) LinkList { // Returns whether all the resources in the collection are bitmaps. func (ll LinkList) AllAreBitmap() bool { for _, link := range ll { - if !link.MediaType.IsBitmap() { + if link.MediaType == nil || !link.MediaType.IsBitmap() { return false } } @@ -288,7 +288,7 @@ func (ll LinkList) AllAreBitmap() bool { // Returns whether all the resources in the collection are audio clips. func (ll LinkList) AllAreAudio() bool { for _, link := range ll { - if !link.MediaType.IsAudio() { + if link.MediaType == nil || !link.MediaType.IsAudio() { return false } } @@ -298,7 +298,7 @@ func (ll LinkList) AllAreAudio() bool { // Returns whether all the resources in the collection are video clips. func (ll LinkList) AllAreVideo() bool { for _, link := range ll { - if !link.MediaType.IsVideo() { + if link.MediaType == nil || !link.MediaType.IsVideo() { return false } } @@ -308,7 +308,7 @@ func (ll LinkList) AllAreVideo() bool { // Returns whether all the resources in the collection are bitmaps or video clips. func (ll LinkList) AllAreVisual() bool { for _, link := range ll { - if !link.MediaType.IsBitmap() && !link.MediaType.IsVideo() { + if link.MediaType == nil || (!link.MediaType.IsBitmap() && !link.MediaType.IsVideo()) { return false } } @@ -318,7 +318,7 @@ func (ll LinkList) AllAreVisual() bool { // Returns whether all the resources in the collection are HTML documents. func (ll LinkList) AllAreHTML() bool { for _, link := range ll { - if !link.MediaType.IsHTML() { + if link.MediaType == nil || !link.MediaType.IsHTML() { return false } } @@ -328,7 +328,7 @@ func (ll LinkList) AllAreHTML() bool { // Returns whether all the resources in the collection are matching the given media type. func (ll LinkList) AllMatchMediaType(mt ...*mediatype.MediaType) bool { for _, link := range ll { - if !link.MediaType.Matches(mt...) { + if link.MediaType == nil || !link.MediaType.Matches(mt...) { return false } } diff --git a/pkg/streamer/a11y_infer.go b/pkg/streamer/a11y_infer.go index ce555cd6..a9114eb4 100644 --- a/pkg/streamer/a11y_infer.go +++ b/pkg/streamer/a11y_infer.go @@ -65,6 +65,9 @@ func inferA11yMetadataInPublicationManifest(ctx context.Context, pub *pub.Public for _, link := range allResources { mt := link.MediaType + if mt == nil { + continue + } if mt.IsAudio() || mt.IsVideo() || mt.Matches(&mediatype.PDF) { @@ -109,7 +112,7 @@ func inferA11yMetadataInPublicationManifest(ctx context.Context, pub *pub.Public // audio or video resource (inspect "resources" and "readingOrder" in // RWPM). for _, link := range allResources { - if link.MediaType.IsAudio() || link.MediaType.IsVideo() { + if link.MediaType != nil && (link.MediaType.IsAudio() || link.MediaType.IsVideo()) { inferredA11y.AccessModes = append(inferredA11y.AccessModes, manifest.A11yAccessModeAuditory) break } @@ -119,7 +122,7 @@ func inferA11yMetadataInPublicationManifest(ctx context.Context, pub *pub.Public // or a video resource (inspect "resources" and "readingOrder" in // RWPM). for _, link := range allResources { - if link.MediaType.IsBitmap() || link.MediaType.IsVideo() { + if link.MediaType != nil && (link.MediaType.IsBitmap() || link.MediaType.IsVideo()) { inferredA11y.AccessModes = append(inferredA11y.AccessModes, manifest.A11yAccessModeVisual) break } @@ -170,7 +173,7 @@ func inferA11yMetadataInPublicationManifest(ctx context.Context, pub *pub.Public } for _, link := range mf.Resources { - if link.MediaType.Matches(&mediatype.SMIL) { + if link.MediaType != nil && link.MediaType.Matches(&mediatype.SMIL) { addFeature(manifest.A11yFeatureSynchronizedAudioText) break } diff --git a/pkg/streamer/a11y_infer_test.go b/pkg/streamer/a11y_infer_test.go index 60e98ddd..8f35eed5 100644 --- a/pkg/streamer/a11y_infer_test.go +++ b/pkg/streamer/a11y_infer_test.go @@ -46,6 +46,34 @@ func newLink(mt mediatype.MediaType, extension string) manifest.Link { } } +// A resource with an unparseable media type is treated as unclassified. +func TestInferA11yMetadataTreatsNilMediaTypeAsUnclassified(t *testing.T) { + nilMediaTypeLink := manifest.Link{ + Href: manifest.MustNewHREFFromString("file.ttf", false), + } + require.Nil(t, nilMediaTypeLink.MediaType) + + m := manifest.Manifest{ + Metadata: manifest.Metadata{ + ConformsTo: manifest.Profiles{manifest.ProfileEPUB}, + Layout: manifest.LayoutReflowable, + }, + ReadingOrder: []manifest.Link{ + newLink(mediatype.HTML, "html"), + }, + Resources: []manifest.Link{ + nilMediaTypeLink, + }, + } + + require.NotPanics(t, func() { + res, err := inferA11yMetadataInPublicationManifest(context.TODO(), pub.New(m, nil, nil), nil) + require.NoError(t, err) + assert.NotNil(t, res) + assert.Contains(t, res.AccessModes, manifest.A11yAccessModeTextual) + }) +} + // If the publication contains a reference to an audio or video resource // (inspect "resources" and "readingOrder" in RWPM). func TestInferAuditoryAccessMode(t *testing.T) { From 7b5959669fe06dd440b0d8d1823e799cacd40ae0 Mon Sep 17 00:00:00 2001 From: Leonard Richardson Date: Fri, 21 Aug 2026 10:19:24 -0400 Subject: [PATCH 2/4] Added some more nil guards. --- pkg/content/iterator/html.go | 2 +- pkg/parser/audio/parser.go | 2 +- pkg/parser/epub/media_overlay_service.go | 2 +- pkg/parser/image/parser.go | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/content/iterator/html.go b/pkg/content/iterator/html.go index e38a1b1e..6ab6bba9 100644 --- a/pkg/content/iterator/html.go +++ b/pkg/content/iterator/html.go @@ -37,7 +37,7 @@ func NewHTML(resource fetcher.Resource, locator manifest.Locator) *HTMLContentIt func HTMLFactory() ResourceContentIteratorFactory { return func(resource fetcher.Resource, locator manifest.Locator) Iterator { - if resource.Link().MediaType.Matches(&mediatype.HTML, &mediatype.XHTML) { + if mt := resource.Link().MediaType; mt != nil && mt.Matches(&mediatype.HTML, &mediatype.XHTML) { return NewHTML(resource, locator) } return nil diff --git a/pkg/parser/audio/parser.go b/pkg/parser/audio/parser.go index 1b809dc8..1b20a465 100644 --- a/pkg/parser/audio/parser.go +++ b/pkg/parser/audio/parser.go @@ -214,7 +214,7 @@ func (p AudioParser) accepts(ctx context.Context, asset asset.PublicationAsset, if extensions.IsHiddenOrThumbs(path) { continue } - if link.MediaType.IsBitmap() { + if link.MediaType != nil && link.MediaType.IsBitmap() { continue } fext := filepath.Ext(strings.ToLower(path)) diff --git a/pkg/parser/epub/media_overlay_service.go b/pkg/parser/epub/media_overlay_service.go index 656ff735..10a5a100 100644 --- a/pkg/parser/epub/media_overlay_service.go +++ b/pkg/parser/epub/media_overlay_service.go @@ -26,7 +26,7 @@ func MediaOverlayFactory() pub.ServiceFactory { alts := context.Manifest.ReadingOrder[i].Alternates for j := range alts { alt := context.Manifest.ReadingOrder[i].Alternates[j] - if alt.MediaType.Equal(&mediatype.SMIL) { + if alt.MediaType != nil && alt.MediaType.Equal(&mediatype.SMIL) { // SMIL alternate for reading order item found // Create a guided navigation link for the SMIL alt diff --git a/pkg/parser/image/parser.go b/pkg/parser/image/parser.go index 3217b4a9..b958a45f 100644 --- a/pkg/parser/image/parser.go +++ b/pkg/parser/image/parser.go @@ -39,7 +39,7 @@ func (p ImageParser) Parse(ctx context.Context, asset asset.PublicationAsset, fe path := link.URL(nil, nil).Path() // Filter out all irrelevant files - if extensions.IsHiddenOrThumbs(path) || !link.MediaType.IsBitmap() { + if extensions.IsHiddenOrThumbs(path) || link.MediaType == nil || !link.MediaType.IsBitmap() { continue } readingOrder = append(readingOrder, link) @@ -94,7 +94,7 @@ func (p ImageParser) accepts(ctx context.Context, asset asset.PublicationAsset, if extensions.IsHiddenOrThumbs(path) { continue } - if link.MediaType.IsBitmap() { + if link.MediaType != nil && link.MediaType.IsBitmap() { continue } fext := filepath.Ext(strings.ToLower(path)) From 4c11afe996566776f517aa69917e2bcb407ba720 Mon Sep 17 00:00:00 2001 From: Leonard Richardson Date: Fri, 21 Aug 2026 11:15:57 -0400 Subject: [PATCH 3/4] Update media_overlay_service.go --- pkg/parser/epub/media_overlay_service.go | 30 +----------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/pkg/parser/epub/media_overlay_service.go b/pkg/parser/epub/media_overlay_service.go index 6e64c1f3..8584cbca 100644 --- a/pkg/parser/epub/media_overlay_service.go +++ b/pkg/parser/epub/media_overlay_service.go @@ -22,35 +22,7 @@ import ( func MediaOverlayFactory() pub.ServiceFactory { return func(context pub.Context, public bool) pub.Service { smilMap := make(map[string]manifest.Link) - htmlMap := make(map[string]manifest.Link) - var guideIndexes []string - for i := range context.Manifest.ReadingOrder { - href := context.Manifest.ReadingOrder[i].Href.String() - hasGuide := false - - alts := context.Manifest.ReadingOrder[i].Alternates - for j := range alts { - alt := context.Manifest.ReadingOrder[i].Alternates[j] - if alt.MediaType != nil && alt.MediaType.Equal(&mediatype.SMIL) { - // SMIL alternate for reading order item found - - // Create a guided navigation link for the SMIL alt - gnLink := pub.GuidedNavigationLink - gnLink.Href = manifest.NewHREF(gnLink.URL(nil, - map[string]string{ - "ref": href, - }, - )) - - // Store the original SMIL alt in an internal map - smilMap[href] = alt - hasGuide = true - - // Swap the original SMIL alt with the new guided navigation link - alts = append(append(alts[:j], gnLink), alts[j+1:]...) - } - } - + // Find and replace SMIL alternates with media overlay links process := func(link *manifest.Link) (hasOverlay bool) { href := link.Href.String() From 87d7cee15145fd5dc09e514440999c3ef12e0119 Mon Sep 17 00:00:00 2001 From: Leonard Richardson Date: Fri, 21 Aug 2026 11:16:19 -0400 Subject: [PATCH 4/4] Update media_overlay_service.go --- pkg/parser/epub/media_overlay_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/parser/epub/media_overlay_service.go b/pkg/parser/epub/media_overlay_service.go index 8584cbca..f3f29051 100644 --- a/pkg/parser/epub/media_overlay_service.go +++ b/pkg/parser/epub/media_overlay_service.go @@ -22,7 +22,7 @@ import ( func MediaOverlayFactory() pub.ServiceFactory { return func(context pub.Context, public bool) pub.Service { smilMap := make(map[string]manifest.Link) - + // Find and replace SMIL alternates with media overlay links process := func(link *manifest.Link) (hasOverlay bool) { href := link.Href.String()