-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expand file tree
/
Copy pathhttp.go
More file actions
152 lines (129 loc) · 3.93 KB
/
http.go
File metadata and controls
152 lines (129 loc) · 3.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package x
import (
"cmp"
"context"
"net/http"
"net/url"
"strings"
"github.com/golang/gddo/httputil"
"github.com/ory/herodot"
)
type ctxKey struct{}
var baseURLKey ctxKey
func WithBaseURL(ctx context.Context, baseURL *url.URL) context.Context {
if baseURL == nil {
return ctx
}
baseURL.Scheme = "https" // Force https
return context.WithValue(ctx, baseURLKey, baseURL)
}
func BaseURLFromContext(ctx context.Context) *url.URL {
if ctx == nil {
return nil
}
if v := ctx.Value(baseURLKey); v != nil {
if u, ok := v.(*url.URL); ok {
return u
}
}
return nil
}
// FlowBaseURL returns the base URL to be used for a self-service flow. It will
// either take the request URL, or an explicit base URL set in the context.
func FlowBaseURL(ctx context.Context, flow interface{ GetRequestURL() string }) (*url.URL, error) {
if u := BaseURLFromContext(ctx); u != nil {
return u, nil
}
u, err := url.Parse(flow.GetRequestURL())
if err != nil {
return nil, err
}
u.Path = "/"
return u, nil
}
func RequestURL(r *http.Request) *url.URL {
source := *r.URL
source.Host = cmp.Or(source.Host, r.Header.Get("X-Forwarded-Host"), r.Host)
if proto := r.Header.Get("X-Forwarded-Proto"); len(proto) > 0 {
source.Scheme = proto
}
if prefix := r.Header.Get("X-Forwarded-Prefix"); len(prefix) > 0 {
if !hasPathPrefix(source.Path, prefix) {
source.Path = joinPathPrefix(prefix, source.Path)
}
if source.RawPath != "" && !hasPathPrefix(source.RawPath, prefix) {
source.RawPath = joinPathPrefix(prefix, source.RawPath)
}
}
if source.Scheme == "" {
source.Scheme = "https"
if r.TLS == nil {
source.Scheme = "http"
}
}
return &source
}
func hasPathPrefix(path, prefix string) bool {
prefix = strings.TrimSuffix(prefix, "/")
if prefix == "" {
return true
}
return path == prefix || strings.HasPrefix(path, prefix+"/")
}
func joinPathPrefix(prefix, path string) string {
switch {
case prefix == "" || prefix == "/":
return path
case path == "":
return prefix
case strings.HasSuffix(prefix, "/") && strings.HasPrefix(path, "/"):
return prefix + strings.TrimPrefix(path, "/")
case !strings.HasSuffix(prefix, "/") && !strings.HasPrefix(path, "/"):
return prefix + "/" + path
default:
return prefix + path
}
}
// SendFlowCompletedAsRedirectOrJSON should be used when a login, registration, ... flow has been completed successfully.
// It will redirect the user to the provided URL if the request accepts HTML, or return a JSON response if the request is
// an SPA request
func SendFlowCompletedAsRedirectOrJSON(
w http.ResponseWriter, r *http.Request, writer herodot.Writer, out interface{}, redirectTo string,
) {
sendFlowAsRedirectOrJSON(w, r, writer, out, redirectTo, http.StatusOK)
}
// SendFlowErrorAsRedirectOrJSON should be used when a login, registration, ... flow has errors (e.g. validation errors
// or missing data) and should be redirected to the provided URL if the request accepts HTML, or return a JSON response
// if the request is an SPA request.
func SendFlowErrorAsRedirectOrJSON(
w http.ResponseWriter, r *http.Request, writer herodot.Writer, out interface{}, redirectTo string,
) {
sendFlowAsRedirectOrJSON(w, r, writer, out, redirectTo, http.StatusBadRequest)
}
func sendFlowAsRedirectOrJSON(
w http.ResponseWriter, r *http.Request, writer herodot.Writer, out interface{}, redirectTo string, jsonResponseCode int,
) {
switch httputil.NegotiateContentType(r, []string{
"text/html",
"application/json",
}, "text/html") {
case "application/json":
if err, ok := out.(error); ok {
writer.WriteError(w, r, err)
return
}
writer.WriteCode(w, r, jsonResponseCode, out)
case "text/html":
fallthrough
default:
http.Redirect(w, r, redirectTo, http.StatusSeeOther)
}
}
func AcceptsJSON(r *http.Request) bool {
return httputil.NegotiateContentType(r, []string{
"text/html",
"application/json",
}, "text/html") == "application/json"
}