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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -21,6 +22,89 @@ import (
|
|||||||
"go.b0esche.cloud/backend/pkg/jwt"
|
"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
|
// WOPILockManager manages file locks to prevent concurrent editing conflicts
|
||||||
type WOPILockManager struct {
|
type WOPILockManager struct {
|
||||||
locks map[string]*models.WOPILockInfo
|
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)
|
// Build WOPISrc URL (without access_token - that goes in a separate form field)
|
||||||
wopiSrc := fmt.Sprintf("https://go.b0esche.cloud/wopi/files/%s", fileID)
|
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
|
// Return HTML page with auto-submitting form
|
||||||
// The form POSTs to Collabora from within an iframe to work around CSP frame-ancestors restrictions
|
// 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
|
// 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">
|
<div class="loading">
|
||||||
<p>Loading Collabora Online...</p>
|
<p>Loading Collabora Online...</p>
|
||||||
</div>
|
</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="WOPISrc" value="%s">
|
||||||
<input type="hidden" name="access_token" value="%s">
|
<input type="hidden" name="access_token" value="%s">
|
||||||
</form>
|
</form>
|
||||||
@@ -692,19 +779,19 @@ func collaboraProxyHandler(w http.ResponseWriter, r *http.Request, db *database.
|
|||||||
// Auto-submit the form to Collabora
|
// Auto-submit the form to Collabora
|
||||||
var form = document.getElementById('collaboraForm');
|
var form = document.getElementById('collaboraForm');
|
||||||
if (form) {
|
if (form) {
|
||||||
console.log('[COLLABORA] Submitting form to /browser/dist/cool.html');
|
console.log('[COLLABORA] Submitting form to %s');
|
||||||
form.submit();
|
form.submit();
|
||||||
} else {
|
} else {
|
||||||
console.error('[COLLABORA] Form not found');
|
console.error('[COLLABORA] Form not found');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>`, collaboraURL, wopiSrc, accessToken)
|
</html>`, editorURL, wopiSrc, accessToken, editorURL)
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
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
|
// Don't set X-Frame-Options - this endpoint is meant to be loaded in an iframe
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte(htmlContent))
|
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