From bbebdf95ecdaf3f7c5389fc16625d1a906307ff8 Mon Sep 17 00:00:00 2001 From: Nick Walters Date: Mon, 21 Sep 2026 00:55:31 +0800 Subject: [PATCH] fix(collaboration): match WOPI app extensions without case The WOPI discovery response lists its extensions in lower case, and OpenInApp looks them up with the extension of the file as it is on disk. So a "report.DOCX" found no app url while a "report.docx" opened, and the web UI reported that it could not contact the application, which reads like the editor is down. The extensions now go into the map in lower case and the lookup folds the case as well, so both ends match whatever the app publishes. The log line and the error also name the extension, because neither said which file type had no app. Fixes opencloud-eu/opencloud#3449 --- services/collaboration/pkg/helpers/discovery.go | 10 +++++++--- .../collaboration/pkg/helpers/discovery_test.go | 14 ++++++++++++++ .../collaboration/pkg/service/grpc/v0/service.go | 4 ++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/services/collaboration/pkg/helpers/discovery.go b/services/collaboration/pkg/helpers/discovery.go index 2db3987739..a2c1834a49 100644 --- a/services/collaboration/pkg/helpers/discovery.go +++ b/services/collaboration/pkg/helpers/discovery.go @@ -59,14 +59,17 @@ func (a *AppURLs) GetMimeTypes() []string { } // GetAppURLFor gets the appURL from the list of appURLs based on the -// action and file extension provided. If there is no match, an empty -// string will be returned. +// action and file extension provided. The extension is matched without +// case, so a "report.DOCX" opens in the same app as a "report.docx". +// If there is no match, an empty string will be returned. func (a *AppURLs) GetAppURLFor(action, fileExt string) string { currentURLs := a.urls.Load() if currentURLs == nil { return "" } + fileExt = strings.ToLower(fileExt) + if actionURL, ok := (*currentURLs)[action]; ok { if actionExtensionURL, ok := actionURL[fileExt]; ok { return actionExtensionURL @@ -162,7 +165,8 @@ func parseWopiDiscovery(body io.Reader) (map[string]map[string]string, error) { if _, ok := appURLs[access]; !ok { appURLs[access] = make(map[string]string) } - appURLs[access]["."+ext] = u.String() + // the extensions are stored in lower case, GetAppURLFor looks them up that way + appURLs[access]["."+strings.ToLower(ext)] = u.String() } } } diff --git a/services/collaboration/pkg/helpers/discovery_test.go b/services/collaboration/pkg/helpers/discovery_test.go index 6891323f35..8416e032e1 100644 --- a/services/collaboration/pkg/helpers/discovery_test.go +++ b/services/collaboration/pkg/helpers/discovery_test.go @@ -49,6 +49,18 @@ var _ = Describe("AppURLs", func() { Expect(appURLs.GetAppURLFor("edit", ".docx")).To(Equal("https://example.com/edit/docx")) }) + It("should find the app URL whatever case the extension has", func() { + testURLs := map[string]map[string]string{ + "view": {".docx": "https://example.com/view/docx"}, + "edit": {".docx": "https://example.com/edit/docx"}, + } + + appURLs.Store(testURLs) + + Expect(appURLs.GetAppURLFor("view", ".DOCX")).To(Equal("https://example.com/view/docx")) + Expect(appURLs.GetAppURLFor("edit", ".DocX")).To(Equal("https://example.com/edit/docx")) + }) + It("should return empty string for non-existent action", func() { testURLs := map[string]map[string]string{ "view": {".pdf": "https://example.com/view/pdf"}, @@ -285,6 +297,7 @@ var _ = Describe("Discovery", func() { + @@ -339,6 +352,7 @@ var _ = Describe("Discovery", func() { "view": map[string]string{ ".pdf": "https://cloud.opencloud.test/hosting/wopi/word/view", ".djvu": "https://cloud.opencloud.test/hosting/wopi/word/view", + ".odt": "https://cloud.opencloud.test/hosting/wopi/word/view", ".docx": "https://cloud.opencloud.test/hosting/wopi/word/view", ".xls": "https://cloud.opencloud.test/hosting/wopi/cell/view", ".xlsb": "https://cloud.opencloud.test/hosting/wopi/cell/view", diff --git a/services/collaboration/pkg/service/grpc/v0/service.go b/services/collaboration/pkg/service/grpc/v0/service.go index 4e9a6db73a..b68be18d9d 100644 --- a/services/collaboration/pkg/service/grpc/v0/service.go +++ b/services/collaboration/pkg/service/grpc/v0/service.go @@ -108,8 +108,8 @@ func (s *Service) OpenInApp( // get the appURL we need to use appURL := s.getAppUrl(fileExt, req.GetViewMode()) if appURL == "" { - logger.Error().Msg("OpenInApp: neither edit nor view app URL found") - return nil, errors.New("neither edit nor view app URL found") + logger.Error().Str("FileExtension", fileExt).Msg("OpenInApp: neither edit nor view app URL found") + return nil, errors.New("neither edit nor view app URL found for " + fileExt) } // append the parameters we need