diff --git a/go_cloud/internal/http/wopi_handlers.go b/go_cloud/internal/http/wopi_handlers.go index 279bdb2..92e6468 100644 --- a/go_cloud/internal/http/wopi_handlers.go +++ b/go_cloud/internal/http/wopi_handlers.go @@ -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.

Loading Collabora Online...

- @@ -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'); } -`, collaboraURL, wopiSrc, accessToken) +`, 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) }