| 51 | } |
| 52 | |
| 53 | func corsMiddleware(boundHost string) gin.HandlerFunc { |
| 54 | return func(c *gin.Context) { |
| 55 | origin := strings.TrimSpace(c.GetHeader("Origin")) |
| 56 | headers := c.Writer.Header() |
| 57 | headers.Set("Access-Control-Allow-Headers", "Content-Type, Last-Event-ID, Accept") |
| 58 | headers.Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") |
| 59 | headers.Set("Access-Control-Expose-Headers", "Content-Type, Last-Event-ID, x-vercel-ai-ui-message-stream") |
| 60 | headers.Set("Vary", "Origin") |
| 61 | if origin != "" { |
| 62 | allowedOrigin, ok := resolveAllowedOrigin(origin, requestScheme(c.Request), c.Request.Host, boundHost) |
| 63 | if !ok { |
| 64 | if isOpenAICompatiblePath(c) { |
| 65 | core.RespondOpenAIError(c, http.StatusForbidden, errors.New("origin not allowed"), false) |
| 66 | c.Abort() |
| 67 | } else { |
| 68 | c.AbortWithStatusJSON(http.StatusForbidden, contract.ErrorPayload{Error: "origin not allowed"}) |
| 69 | } |
| 70 | return |
| 71 | } |
| 72 | headers.Set("Access-Control-Allow-Origin", allowedOrigin) |
| 73 | } |
| 74 | |
| 75 | if c.Request.Method == http.MethodOptions { |
| 76 | c.AbortWithStatus(http.StatusNoContent) |
| 77 | return |
| 78 | } |
| 79 | c.Next() |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | func resolveAllowedOrigin(origin string, requestScheme string, requestHost string, boundHost string) (string, bool) { |
| 84 | parsed, err := url.Parse(strings.TrimSpace(origin)) |