Fix Collabora: Fetch versioned editor URL from discovery endpoint
- Added getCollaboraEditorURL() to fetch correct /browser/{version}/cool.html path
- Cache discovery response for 5 minutes to avoid repeated requests
- Fixed 'Invalid URI' error caused by version mismatch
This commit is contained in:
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -21,6 +22,89 @@ import (
|
||||
"go.b0esche.cloud/backend/pkg/jwt"
|
||||
)
|
||||
|
||||
// Collabora discovery cache
|
||||
var (
|
||||
collaboraEditorURL string
|
||||
collaboraDiscoveryCache time.Time
|
||||
collaboraDiscoveryMu sync.RWMutex
|
||||
)
|
||||
|
||||
// getCollaboraEditorURL fetches the editor URL from Collabora's discovery endpoint
|
||||
func getCollaboraEditorURL(collaboraBaseURL string) string {
|
||||
collaboraDiscoveryMu.RLock()
|
||||
// Cache for 5 minutes
|
||||
if collaboraEditorURL != "" && time.Since(collaboraDiscoveryCache) < 5*time.Minute {
|
||||
url := collaboraEditorURL
|
||||
collaboraDiscoveryMu.RUnlock()
|
||||
return url
|
||||
}
|
||||
collaboraDiscoveryMu.RUnlock()
|
||||
|
||||
// Fetch discovery
|
||||
collaboraDiscoveryMu.Lock()
|
||||
defer collaboraDiscoveryMu.Unlock()
|
||||
|
||||
// Double-check after acquiring write lock
|
||||
if collaboraEditorURL != "" && time.Since(collaboraDiscoveryCache) < 5*time.Minute {
|
||||
return collaboraEditorURL
|
||||
}
|
||||
|
||||
discoveryURL := collaboraBaseURL + "/hosting/discovery"
|
||||
resp, err := http.Get(discoveryURL)
|
||||
if err != nil {
|
||||
fmt.Printf("[COLLABORA] Failed to fetch discovery: %v\n", err)
|
||||
// Fallback to guessed URL
|
||||
return collaboraBaseURL + "/browser/dist/cool.html"
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Printf("[COLLABORA] Failed to read discovery: %v\n", err)
|
||||
return collaboraBaseURL + "/browser/dist/cool.html"
|
||||
}
|
||||
|
||||
// Parse XML to extract urlsrc
|
||||
type Action struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Ext string `xml:"ext,attr"`
|
||||
URLSrc string `xml:"urlsrc,attr"`
|
||||
}
|
||||
type App struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Actions []Action `xml:"action"`
|
||||
}
|
||||
type NetZone struct {
|
||||
Apps []App `xml:"app"`
|
||||
}
|
||||
type WopiDiscovery struct {
|
||||
NetZone NetZone `xml:"net-zone"`
|
||||
}
|
||||
|
||||
var discovery WopiDiscovery
|
||||
if err := xml.Unmarshal(body, &discovery); err != nil {
|
||||
fmt.Printf("[COLLABORA] Failed to parse discovery XML: %v\n", err)
|
||||
return collaboraBaseURL + "/browser/dist/cool.html"
|
||||
}
|
||||
|
||||
// Find the first edit action URL (they all have the same base)
|
||||
for _, app := range discovery.NetZone.Apps {
|
||||
for _, action := range app.Actions {
|
||||
if action.URLSrc != "" {
|
||||
// Extract base URL (remove query string marker)
|
||||
url := strings.TrimSuffix(action.URLSrc, "?")
|
||||
collaboraEditorURL = url
|
||||
collaboraDiscoveryCache = time.Now()
|
||||
fmt.Printf("[COLLABORA] Discovered editor URL: %s\n", url)
|
||||
return url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("[COLLABORA] No editor URL found in discovery\n")
|
||||
return collaboraBaseURL + "/browser/dist/cool.html"
|
||||
}
|
||||
|
||||
// WOPILockManager manages file locks to prevent concurrent editing conflicts
|
||||
type WOPILockManager struct {
|
||||
locks map[string]*models.WOPILockInfo
|
||||
@@ -665,6 +749,9 @@ func collaboraProxyHandler(w http.ResponseWriter, r *http.Request, db *database.
|
||||
// Build WOPISrc URL (without access_token - that goes in a separate form field)
|
||||
wopiSrc := fmt.Sprintf("https://go.b0esche.cloud/wopi/files/%s", fileID)
|
||||
|
||||
// Get the correct Collabora editor URL from discovery (includes version hash)
|
||||
editorURL := getCollaboraEditorURL(collaboraURL)
|
||||
|
||||
// Return HTML page with auto-submitting form
|
||||
// The form POSTs to Collabora from within an iframe to work around CSP frame-ancestors restrictions
|
||||
// The iframe is hosted on the same domain as the embedded page, allowing the POST to complete
|
||||
@@ -684,7 +771,7 @@ func collaboraProxyHandler(w http.ResponseWriter, r *http.Request, db *database.
|
||||
<div class="loading">
|
||||
<p>Loading Collabora Online...</p>
|
||||
</div>
|
||||
<form method="POST" action="%s/browser/dist/cool.html" target="_self" id="collaboraForm" style="display: none;">
|
||||
<form method="POST" action="%s" target="_self" id="collaboraForm" style="display: none;">
|
||||
<input type="hidden" name="WOPISrc" value="%s">
|
||||
<input type="hidden" name="access_token" value="%s">
|
||||
</form>
|
||||
@@ -692,19 +779,19 @@ func collaboraProxyHandler(w http.ResponseWriter, r *http.Request, db *database.
|
||||
// Auto-submit the form to Collabora
|
||||
var form = document.getElementById('collaboraForm');
|
||||
if (form) {
|
||||
console.log('[COLLABORA] Submitting form to /browser/dist/cool.html');
|
||||
console.log('[COLLABORA] Submitting form to %s');
|
||||
form.submit();
|
||||
} else {
|
||||
console.error('[COLLABORA] Form not found');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>`, collaboraURL, wopiSrc, accessToken)
|
||||
</html>`, editorURL, wopiSrc, accessToken, editorURL)
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
// Don't set X-Frame-Options - this endpoint is meant to be loaded in an iframe
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(htmlContent))
|
||||
|
||||
fmt.Printf("[COLLABORA-PROXY] Served HTML form: file=%s user=%s wopi_src=%s\n", fileID, userID.String(), wopiSrc)
|
||||
fmt.Printf("[COLLABORA-PROXY] Served HTML form: file=%s user=%s wopi_src=%s editor_url=%s\n", fileID, userID.String(), wopiSrc, editorURL)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user