Infer site URL from host and headers

We can use the Host property of the request and the X-Forwarded-Proto to
infer the site URL. To reduce complexity, the path is not inferred, and
it is assumed that linx-server is running at /. If this is not the case,
the site URL must be manually configured; this is no different than it
was before.
This commit is contained in:
mutantmonkey 2016-06-04 01:22:01 -07:00
parent afa65349cc
commit 47670af185
8 changed files with 152 additions and 28 deletions

View File

@ -26,7 +26,7 @@ func fileServeHandler(c web.C, w http.ResponseWriter, r *http.Request) {
if !Config.allowHotlink {
referer := r.Header.Get("Referer")
u, _ := url.Parse(referer)
p, _ := url.Parse(Config.siteURL)
p, _ := url.Parse(getSiteURL(r))
if referer != "" && !sameOrigin(u, p) {
http.Redirect(w, r, Config.sitePath+fileName, 303)
return

View File

@ -2,6 +2,7 @@ package main
import (
"net/http"
"net/url"
"strings"
)
@ -25,3 +26,26 @@ func AddHeaders(headers []string) func(http.Handler) http.Handler {
}
return fn
}
func getSiteURL(r *http.Request) string {
if Config.siteURL != "" {
return Config.siteURL
} else {
u := &url.URL{}
u.Host = r.Host
if Config.sitePath != "" {
u.Path = Config.sitePath
}
if scheme := r.Header.Get("X-Forwarded-Proto"); scheme != "" {
u.Scheme = scheme
} else if Config.certFile != "" || (r.TLS != nil && r.TLS.HandshakeComplete == true) {
u.Scheme = "https"
} else {
u.Scheme = "http"
}
return u.String()
}
}

View File

@ -36,7 +36,7 @@ func pasteHandler(c web.C, w http.ResponseWriter, r *http.Request) {
}
func apiDocHandler(c web.C, w http.ResponseWriter, r *http.Request) {
err := Templates["API.html"].ExecuteWriter(pongo2.Context{}, w)
err := Templates["API.html"].ExecuteWriter(pongo2.Context{"siteurl": getSiteURL(r)}, w)
if err != nil {
oopsHandler(c, w, r, RespHTML, "")
}

View File

@ -102,17 +102,21 @@ func setup() *web.Mux {
log.Fatal("Could not create metadata directory:", err)
}
// ensure siteURL ends wth '/'
if lastChar := Config.siteURL[len(Config.siteURL)-1:]; lastChar != "/" {
Config.siteURL = Config.siteURL + "/"
}
if Config.siteURL != "" {
// ensure siteURL ends wth '/'
if lastChar := Config.siteURL[len(Config.siteURL)-1:]; lastChar != "/" {
Config.siteURL = Config.siteURL + "/"
}
parsedUrl, err := url.Parse(Config.siteURL)
if err != nil {
log.Fatal("Could not parse siteurl:", err)
}
parsedUrl, err := url.Parse(Config.siteURL)
if err != nil {
log.Fatal("Could not parse siteurl:", err)
}
Config.sitePath = parsedUrl.Path
Config.sitePath = parsedUrl.Path
} else {
Config.sitePath = "/"
}
// Template setup
p2l, err := NewPongo2TemplatesLoader()
@ -121,7 +125,6 @@ func setup() *web.Mux {
}
TemplateSet := pongo2.NewSet("templates", p2l)
TemplateSet.Globals["sitename"] = Config.siteName
TemplateSet.Globals["siteurl"] = Config.siteURL
TemplateSet.Globals["sitepath"] = Config.sitePath
TemplateSet.Globals["using_auth"] = Config.authFile != ""
err = populateTemplatesMap(TemplateSet, Templates)
@ -193,7 +196,7 @@ func main() {
"Allow hotlinking of files")
flag.StringVar(&Config.siteName, "sitename", "linx",
"name of the site")
flag.StringVar(&Config.siteURL, "siteurl", "http://"+Config.bind+"/",
flag.StringVar(&Config.siteURL, "siteurl", "",
"site base url (including trailing slash)")
flag.Int64Var(&Config.maxSize, "maxsize", 4*1024*1024*1024,
"maximum upload file size in bytes (default 4GB)")
@ -232,16 +235,16 @@ func main() {
log.Fatal("Could not bind: ", err)
}
log.Printf("Serving over fastcgi, bound on %s, using siteurl %s", Config.bind, Config.siteURL)
log.Printf("Serving over fastcgi, bound on %s", Config.bind)
fcgi.Serve(listener, mux)
} else if Config.certFile != "" {
log.Printf("Serving over https, bound on %s, using siteurl %s", Config.bind, Config.siteURL)
log.Printf("Serving over https, bound on %s", Config.bind)
err := graceful.ListenAndServeTLS(Config.bind, Config.certFile, Config.keyFile, mux)
if err != nil {
log.Fatal(err)
}
} else {
log.Printf("Serving over http, bound on %s, using siteurl %s", Config.bind, Config.siteURL)
log.Printf("Serving over http, bound on %s", Config.bind)
err := graceful.ListenAndServe(Config.bind, mux)
if err != nil {
log.Fatal(err)

View File

@ -2,6 +2,7 @@ package main
import (
"bytes"
"crypto/tls"
"encoding/json"
"mime/multipart"
"net/http"
@ -923,6 +924,102 @@ func TestExtension(t *testing.T) {
}
}
func TestInferSiteURL(t *testing.T) {
oldSiteURL := Config.siteURL
oldSitePath := Config.sitePath
Config.siteURL = ""
Config.sitePath = "/linxtest/"
mux := setup()
w := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/API/", nil)
req.Host = "example.com:8080"
if err != nil {
t.Fatal(err)
}
mux.ServeHTTP(w, req)
if !strings.Contains(w.Body.String(), "http://example.com:8080/upload/") {
t.Fatal("Site URL not found properly embedded in response")
}
Config.siteURL = oldSiteURL
Config.sitePath = oldSitePath
}
func TestInferSiteURLProxied(t *testing.T) {
oldSiteURL := Config.siteURL
Config.siteURL = ""
mux := setup()
w := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/API/", nil)
req.Header.Add("X-Forwarded-Proto", "https")
req.Host = "example.com:8080"
if err != nil {
t.Fatal(err)
}
mux.ServeHTTP(w, req)
if !strings.Contains(w.Body.String(), "https://example.com:8080/upload/") {
t.Fatal("Site URL not found properly embedded in response")
}
Config.siteURL = oldSiteURL
}
func TestInferSiteURLHTTPS(t *testing.T) {
oldSiteURL := Config.siteURL
oldCertFile := Config.certFile
Config.siteURL = ""
Config.certFile = "/dev/null"
mux := setup()
w := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/API/", nil)
req.Host = "example.com"
if err != nil {
t.Fatal(err)
}
mux.ServeHTTP(w, req)
if !strings.Contains(w.Body.String(), "https://example.com/upload/") {
t.Fatal("Site URL not found properly embedded in response")
}
Config.siteURL = oldSiteURL
Config.certFile = oldCertFile
}
func TestInferSiteURLHTTPSFastCGI(t *testing.T) {
oldSiteURL := Config.siteURL
Config.siteURL = ""
mux := setup()
w := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/API/", nil)
req.Host = "example.com"
req.TLS = &tls.ConnectionState{HandshakeComplete: true}
if err != nil {
t.Fatal(err)
}
mux.ServeHTTP(w, req)
if !strings.Contains(w.Body.String(), "https://example.com/upload/") {
t.Fatal("Site URL not found properly embedded in response")
}
Config.siteURL = oldSiteURL
}
func TestShutdown(t *testing.T) {
os.RemoveAll(Config.filesDir)
os.RemoveAll(Config.metaDir)

View File

@ -37,7 +37,7 @@ func hashPiece(piece []byte) []byte {
return h.Sum(nil)
}
func createTorrent(fileName string, filePath string) ([]byte, error) {
func createTorrent(fileName string, filePath string, r *http.Request) ([]byte, error) {
chunk := make([]byte, TORRENT_PIECE_LENGTH)
torrent := Torrent{
@ -46,7 +46,7 @@ func createTorrent(fileName string, filePath string) ([]byte, error) {
PieceLength: TORRENT_PIECE_LENGTH,
Name: fileName,
},
UrlList: []string{fmt.Sprintf("%sselif/%s", Config.siteURL, fileName)},
UrlList: []string{fmt.Sprintf("%sselif/%s", getSiteURL(r), fileName)},
}
f, err := os.Open(filePath)
@ -89,7 +89,7 @@ func fileTorrentHandler(c web.C, w http.ResponseWriter, r *http.Request) {
return
}
encoded, err := createTorrent(fileName, filePath)
encoded, err := createTorrent(fileName, filePath, r)
if err != nil {
oopsHandler(c, w, r, RespHTML, "Could not create torrent.")
return

View File

@ -11,7 +11,7 @@ func TestCreateTorrent(t *testing.T) {
fileName := "server.go"
var decoded Torrent
encoded, err := createTorrent(fileName, fileName)
encoded, err := createTorrent(fileName, fileName, nil)
if err != nil {
t.Fatal(err)
}
@ -47,7 +47,7 @@ func TestCreateTorrent(t *testing.T) {
func TestCreateTorrentWithImage(t *testing.T) {
var decoded Torrent
encoded, err := createTorrent("test.jpg", "static/images/404.jpg")
encoded, err := createTorrent("test.jpg", "static/images/404.jpg", nil)
if err != nil {
t.Fatal(err)
}

View File

@ -46,7 +46,7 @@ type Upload struct {
}
func uploadPostHandler(c web.C, w http.ResponseWriter, r *http.Request) {
if !strictReferrerCheck(r, Config.siteURL, []string{"Linx-Delete-Key", "Linx-Expiry", "Linx-Randomize", "X-Requested-With"}) {
if !strictReferrerCheck(r, getSiteURL(r), []string{"Linx-Delete-Key", "Linx-Expiry", "Linx-Randomize", "X-Requested-With"}) {
badRequestHandler(c, w, r)
return
}
@ -94,7 +94,7 @@ func uploadPostHandler(c web.C, w http.ResponseWriter, r *http.Request) {
return
}
js := generateJSONresponse(upload)
js := generateJSONresponse(upload, r)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.Write(js)
} else {
@ -124,7 +124,7 @@ func uploadPutHandler(c web.C, w http.ResponseWriter, r *http.Request) {
return
}
js := generateJSONresponse(upload)
js := generateJSONresponse(upload, r)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.Write(js)
} else {
@ -133,7 +133,7 @@ func uploadPutHandler(c web.C, w http.ResponseWriter, r *http.Request) {
return
}
fmt.Fprintf(w, Config.siteURL+upload.Filename)
fmt.Fprintf(w, getSiteURL(r)+upload.Filename)
}
}
@ -174,7 +174,7 @@ func uploadRemote(c web.C, w http.ResponseWriter, r *http.Request) {
return
}
js := generateJSONresponse(upload)
js := generateJSONresponse(upload, r)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.Write(js)
} else {
@ -308,9 +308,9 @@ func generateBarename() string {
return uniuri.NewLenChars(8, []byte("abcdefghijklmnopqrstuvwxyz0123456789"))
}
func generateJSONresponse(upload Upload) []byte {
func generateJSONresponse(upload Upload, r *http.Request) []byte {
js, _ := json.Marshal(map[string]string{
"url": Config.siteURL + upload.Filename,
"url": getSiteURL(r) + upload.Filename,
"filename": upload.Filename,
"delete_key": upload.Metadata.DeleteKey,
"expiry": strconv.FormatInt(upload.Metadata.Expiry.Unix(), 10),