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
This commit is contained in:
Nick Walters committed 2026-09-21 00:55:31 +08:00
1 parent f293031e40
commit bbebdf95ec
3 files changed
+23 -5

No files matched your search

@@ -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()
}
}
}
@@ -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() {
<app name="Word" favIconUrl="https://cloud.opencloud.test/web-apps/apps/documenteditor/main/resources/img/favicon.ico">
<action name="view" ext="pdf" urlsrc="https://cloud.opencloud.test/hosting/wopi/word/view?&amp;&lt;rs=DC_LLCC&amp;&gt;&lt;dchat=DISABLE_CHAT&amp;&gt;&lt;embed=EMBEDDED&amp;&gt;&lt;fs=FULLSCREEN&amp;&gt;&lt;hid=HOST_SESSION_ID&amp;&gt;&lt;rec=RECORDING&amp;&gt;&lt;sc=SESSION_CONTEXT&amp;&gt;&lt;thm=THEME_ID&amp;&gt;&lt;ui=UI_LLCC&amp;&gt;&lt;wopisrc=WOPI_SOURCE&amp;&gt;&amp;"/>
<action name="embedview" ext="pdf" urlsrc="https://cloud.opencloud.test/hosting/wopi/word/view?embed=1&amp;&lt;rs=DC_LLCC&amp;&gt;&lt;dchat=DISABLE_CHAT&amp;&gt;&lt;embed=EMBEDDED&amp;&gt;&lt;fs=FULLSCREEN&amp;&gt;&lt;hid=HOST_SESSION_ID&amp;&gt;&lt;rec=RECORDING&amp;&gt;&lt;sc=SESSION_CONTEXT&amp;&gt;&lt;thm=THEME_ID&amp;&gt;&lt;ui=UI_LLCC&amp;&gt;&lt;wopisrc=WOPI_SOURCE&amp;&gt;&amp;"/>
<action name="view" ext="ODT" urlsrc="https://cloud.opencloud.test/hosting/wopi/word/view"/>
<action name="view" ext="djvu" urlsrc="https://cloud.opencloud.test/hosting/wopi/word/view?&amp;&lt;rs=DC_LLCC&amp;&gt;&lt;dchat=DISABLE_CHAT&amp;&gt;&lt;embed=EMBEDDED&amp;&gt;&lt;fs=FULLSCREEN&amp;&gt;&lt;hid=HOST_SESSION_ID&amp;&gt;&lt;rec=RECORDING&amp;&gt;&lt;sc=SESSION_CONTEXT&amp;&gt;&lt;thm=THEME_ID&amp;&gt;&lt;ui=UI_LLCC&amp;&gt;&lt;wopisrc=WOPI_SOURCE&amp;&gt;&amp;"/>
<action name="embedview" ext="djvu" urlsrc="https://cloud.opencloud.test/hosting/wopi/word/view?embed=1&amp;&lt;rs=DC_LLCC&amp;&gt;&lt;dchat=DISABLE_CHAT&amp;&gt;&lt;embed=EMBEDDED&amp;&gt;&lt;fs=FULLSCREEN&amp;&gt;&lt;hid=HOST_SESSION_ID&amp;&gt;&lt;rec=RECORDING&amp;&gt;&lt;sc=SESSION_CONTEXT&amp;&gt;&lt;thm=THEME_ID&amp;&gt;&lt;ui=UI_LLCC&amp;&gt;&lt;wopisrc=WOPI_SOURCE&amp;&gt;&amp;"/>
<action name="view" ext="docx" urlsrc="https://cloud.opencloud.test/hosting/wopi/word/view?&amp;&lt;rs=DC_LLCC&amp;&gt;&lt;dchat=DISABLE_CHAT&amp;&gt;&lt;embed=EMBEDDED&amp;&gt;&lt;fs=FULLSCREEN&amp;&gt;&lt;hid=HOST_SESSION_ID&amp;&gt;&lt;rec=RECORDING&amp;&gt;&lt;sc=SESSION_CONTEXT&amp;&gt;&lt;thm=THEME_ID&amp;&gt;&lt;ui=UI_LLCC&amp;&gt;&lt;wopisrc=WOPI_SOURCE&amp;&gt;&amp;"/>
@@ -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",
@@ -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