初始化蓝鸟文件快递柜源码与部署文档
This commit is contained in:
commit
5643d22242
70 files changed
+18669
No files matched your search
@@ -0,0 +1,172 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func normalizeAccessURL(raw string) (string, error) {
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil || u.Host == "" || u.Hostname() == "" || u.User != nil || u.RawQuery != "" || u.Fragment != "" || (u.Path != "" && u.Path != "/") || (u.Scheme != "http" && u.Scheme != "https") {
|
||||
return "", errors.New("访问地址须为无路径的http/https站点地址")
|
||||
}
|
||||
host := strings.ToLower(u.Hostname())
|
||||
port := u.Port()
|
||||
if port != "" {
|
||||
n, e := strconv.Atoi(port)
|
||||
if e != nil || n < 1 || n > 65535 {
|
||||
return "", errors.New("访问端口须为1至65535")
|
||||
}
|
||||
}
|
||||
if port == "80" && u.Scheme == "http" || port == "443" && u.Scheme == "https" {
|
||||
port = ""
|
||||
}
|
||||
if port != "" {
|
||||
host = net.JoinHostPort(host, port)
|
||||
} else if strings.Contains(host, ":") {
|
||||
host = "[" + host + "]"
|
||||
}
|
||||
return u.Scheme + "://" + host, nil
|
||||
}
|
||||
func (c Config) accessOrigins() []string {
|
||||
out := []string{}
|
||||
for _, raw := range append([]string{c.PublicURL}, c.AccessURLs...) {
|
||||
base, err := normalizeAccessURL(raw)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
found := false
|
||||
for _, v := range out {
|
||||
if v == base {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
out = append(out, base)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type requestAccessKey struct{}
|
||||
type requestAccess struct {
|
||||
base string
|
||||
httpCookie bool
|
||||
}
|
||||
|
||||
func (a *App) accessFor(r *http.Request) requestAccess {
|
||||
if state, ok := r.Context().Value(requestAccessKey{}).(requestAccess); ok {
|
||||
return state
|
||||
}
|
||||
return requestAccess{base: strings.TrimRight(a.cfg.PublicURL, "/")}
|
||||
}
|
||||
func (a *App) selectAccess(r *http.Request) (requestAccess, error) {
|
||||
origins := a.cfg.accessOrigins()
|
||||
allowed := func(raw string) string {
|
||||
normalized, err := normalizeAccessURL(raw)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, v := range origins {
|
||||
if v == normalized {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
origin, hint := r.Header.Get("Origin"), r.Header.Get("X-Bluebird-Origin")
|
||||
selected := ""
|
||||
if origin != "" {
|
||||
selected = allowed(origin)
|
||||
if selected == "" {
|
||||
return requestAccess{}, errors.New("请求来源不允许")
|
||||
}
|
||||
}
|
||||
if hint != "" {
|
||||
base := allowed(hint)
|
||||
if base == "" || selected != "" && base != selected {
|
||||
return requestAccess{}, errors.New("访问入口不允许")
|
||||
}
|
||||
selected = base
|
||||
}
|
||||
if selected == "" {
|
||||
host, scheme := r.Host, ""
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
peer, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
ip := net.ParseIP(peer)
|
||||
trusted := false
|
||||
if err == nil {
|
||||
for _, network := range a.proxies {
|
||||
if network.Contains(ip) {
|
||||
trusted = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if trusted {
|
||||
if forwarded := r.Header.Get("X-Forwarded-Host"); forwarded != "" && !strings.Contains(forwarded, ",") {
|
||||
host = forwarded
|
||||
}
|
||||
if proto := r.Header.Get("X-Forwarded-Proto"); proto == "http" || proto == "https" {
|
||||
scheme = proto
|
||||
}
|
||||
}
|
||||
for _, base := range origins {
|
||||
u, _ := url.Parse(base)
|
||||
if strings.EqualFold(u.Host, host) && (scheme == "" || scheme == u.Scheme) {
|
||||
selected = base
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if selected == "" {
|
||||
selected = origins[0]
|
||||
}
|
||||
if r.TLS != nil && strings.HasPrefix(selected, "http://") {
|
||||
return requestAccess{}, errors.New("访问入口协议与连接不一致")
|
||||
}
|
||||
hasHTTPS := false
|
||||
for _, base := range origins {
|
||||
if strings.HasPrefix(base, "https://") {
|
||||
hasHTTPS = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return requestAccess{base: selected, httpCookie: hasHTTPS && strings.HasPrefix(selected, "http://")}, nil
|
||||
}
|
||||
func (a *App) withAccess(r *http.Request) (*http.Request, error) {
|
||||
state, err := a.selectAccess(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.WithContext(context.WithValue(r.Context(), requestAccessKey{}, state)), nil
|
||||
}
|
||||
func cookieNames(r *http.Request, name string) []string {
|
||||
state, _ := r.Context().Value(requestAccessKey{}).(requestAccess)
|
||||
if state.httpCookie && (r.Header.Get("Origin") != "" || r.Header.Get("X-Bluebird-Origin") != "") {
|
||||
return []string{name + "_http", name}
|
||||
}
|
||||
return []string{name, name + "_http"}
|
||||
}
|
||||
|
||||
// LANListen accepts only a concrete private or loopback HTTP address.
|
||||
func LANListen(raw string) (string, string, error) {
|
||||
base, err := normalizeAccessURL(raw)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
u, _ := url.Parse(base)
|
||||
ip := net.ParseIP(u.Hostname())
|
||||
if u.Scheme != "http" || ip == nil || !ip.IsPrivate() && !ip.IsLoopback() || u.Port() == "" {
|
||||
return "", "", errors.New("内网入口须为http://具体私有IP:端口")
|
||||
}
|
||||
return u.Host, base, nil
|
||||
}
|
||||
Reference in new issue
Block a user