mirror of https://github.com/miniflux/v2.git
Add support for base URLs with subfolders
This commit is contained in:
parent
78385a351e
commit
9c42997209
|
@ -5,6 +5,7 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
@ -26,7 +27,10 @@ const (
|
||||||
|
|
||||||
// Config manages configuration parameters.
|
// Config manages configuration parameters.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
IsHTTPS bool
|
IsHTTPS bool
|
||||||
|
baseURL string
|
||||||
|
rootURL string
|
||||||
|
basePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) get(key, fallback string) string {
|
func (c *Config) get(key, fallback string) string {
|
||||||
|
@ -53,13 +57,34 @@ func (c *Config) HasDebugMode() bool {
|
||||||
return c.get("DEBUG", "") != ""
|
return c.get("DEBUG", "") != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseURL returns the application base URL.
|
// BaseURL returns the application base URL with path.
|
||||||
func (c *Config) BaseURL() string {
|
func (c *Config) BaseURL() string {
|
||||||
baseURL := c.get("BASE_URL", defaultBaseURL)
|
if c.baseURL == "" {
|
||||||
if baseURL[len(baseURL)-1:] == "/" {
|
c.baseURL = c.get("BASE_URL", defaultBaseURL)
|
||||||
baseURL = baseURL[:len(baseURL)-1]
|
if c.baseURL[len(c.baseURL)-1:] == "/" {
|
||||||
|
c.baseURL = c.baseURL[:len(c.baseURL)-1]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return baseURL
|
return c.baseURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootURL returns the base URL without path.
|
||||||
|
func (c *Config) RootURL() string {
|
||||||
|
if c.rootURL == "" {
|
||||||
|
u, _ := url.Parse(c.BaseURL())
|
||||||
|
u.Path = ""
|
||||||
|
c.rootURL = u.String()
|
||||||
|
}
|
||||||
|
return c.rootURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasePath returns the application base path according to the base URL.
|
||||||
|
func (c *Config) BasePath() string {
|
||||||
|
if c.basePath == "" {
|
||||||
|
u, _ := url.Parse(c.BaseURL())
|
||||||
|
c.basePath = u.Path
|
||||||
|
}
|
||||||
|
return c.basePath
|
||||||
}
|
}
|
||||||
|
|
||||||
// DatabaseURL returns the database URL.
|
// DatabaseURL returns the database URL.
|
||||||
|
|
|
@ -9,7 +9,26 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetCustomBaseURL(t *testing.T) {
|
func TestDebugModeOn(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("DEBUG", "1")
|
||||||
|
cfg := NewConfig()
|
||||||
|
|
||||||
|
if !cfg.HasDebugMode() {
|
||||||
|
t.Fatalf(`Unexpected debug mode value, got "%v"`, cfg.HasDebugMode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDebugModeOff(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
cfg := NewConfig()
|
||||||
|
|
||||||
|
if cfg.HasDebugMode() {
|
||||||
|
t.Fatalf(`Unexpected debug mode value, got "%v"`, cfg.HasDebugMode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomBaseURL(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("BASE_URL", "http://example.org")
|
os.Setenv("BASE_URL", "http://example.org")
|
||||||
cfg := NewConfig()
|
cfg := NewConfig()
|
||||||
|
@ -17,9 +36,17 @@ func TestGetCustomBaseURL(t *testing.T) {
|
||||||
if cfg.BaseURL() != "http://example.org" {
|
if cfg.BaseURL() != "http://example.org" {
|
||||||
t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL())
|
t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.RootURL() != "http://example.org" {
|
||||||
|
t.Fatalf(`Unexpected root URL, got "%s"`, cfg.RootURL())
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.BasePath() != "" {
|
||||||
|
t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetCustomBaseURLWithTrailingSlash(t *testing.T) {
|
func TestCustomBaseURLWithTrailingSlash(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("BASE_URL", "http://example.org/folder/")
|
os.Setenv("BASE_URL", "http://example.org/folder/")
|
||||||
cfg := NewConfig()
|
cfg := NewConfig()
|
||||||
|
@ -27,13 +54,29 @@ func TestGetCustomBaseURLWithTrailingSlash(t *testing.T) {
|
||||||
if cfg.BaseURL() != "http://example.org/folder" {
|
if cfg.BaseURL() != "http://example.org/folder" {
|
||||||
t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL())
|
t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.RootURL() != "http://example.org" {
|
||||||
|
t.Fatalf(`Unexpected root URL, got "%s"`, cfg.BaseURL())
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.BasePath() != "/folder" {
|
||||||
|
t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetDefaultBaseURL(t *testing.T) {
|
func TestDefaultBaseURL(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
cfg := NewConfig()
|
cfg := NewConfig()
|
||||||
|
|
||||||
if cfg.BaseURL() != "http://localhost" {
|
if cfg.BaseURL() != "http://localhost" {
|
||||||
t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL())
|
t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.RootURL() != "http://localhost" {
|
||||||
|
t.Fatalf(`Unexpected root URL, got "%s"`, cfg.RootURL())
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.BasePath() != "" {
|
||||||
|
t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,10 @@ func routes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handle
|
||||||
middleware.NewSessionMiddleware(cfg, store).Handler,
|
middleware.NewSessionMiddleware(cfg, store).Handler,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
if cfg.BasePath() != "" {
|
||||||
|
router = router.PathPrefix(cfg.BasePath()).Subrouter()
|
||||||
|
}
|
||||||
|
|
||||||
router.Handle("/fever/", feverHandler.Use(feverController.Handler)).Name("feverEndpoint")
|
router.Handle("/fever/", feverHandler.Use(feverController.Handler)).Name("feverEndpoint")
|
||||||
|
|
||||||
router.Handle("/v1/users", apiHandler.Use(apiController.CreateUser)).Methods("POST")
|
router.Handle("/v1/users", apiHandler.Use(apiController.CreateUser)).Methods("POST")
|
||||||
|
|
|
@ -19,11 +19,11 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// New creates a new cookie.
|
// New creates a new cookie.
|
||||||
func New(name, value string, isHTTPS bool) *http.Cookie {
|
func New(name, value string, isHTTPS bool, path string) *http.Cookie {
|
||||||
return &http.Cookie{
|
return &http.Cookie{
|
||||||
Name: name,
|
Name: name,
|
||||||
Value: value,
|
Value: value,
|
||||||
Path: "/",
|
Path: basePath(path),
|
||||||
Secure: isHTTPS,
|
Secure: isHTTPS,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Expires: time.Now().Add(cookieDuration * 24 * time.Hour),
|
Expires: time.Now().Add(cookieDuration * 24 * time.Hour),
|
||||||
|
@ -31,14 +31,21 @@ func New(name, value string, isHTTPS bool) *http.Cookie {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expired returns an expired cookie.
|
// Expired returns an expired cookie.
|
||||||
func Expired(name string, isHTTPS bool) *http.Cookie {
|
func Expired(name string, isHTTPS bool, path string) *http.Cookie {
|
||||||
return &http.Cookie{
|
return &http.Cookie{
|
||||||
Name: name,
|
Name: name,
|
||||||
Value: "",
|
Value: "",
|
||||||
Path: "/",
|
Path: basePath(path),
|
||||||
Secure: isHTTPS,
|
Secure: isHTTPS,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
MaxAge: -1,
|
MaxAge: -1,
|
||||||
Expires: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
|
Expires: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func basePath(path string) string {
|
||||||
|
if path == "" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ func (s *SessionMiddleware) Handler(next http.Handler) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.SetCookie(w, cookie.New(cookie.CookieSessionID, session.ID, s.cfg.IsHTTPS))
|
http.SetCookie(w, cookie.New(cookie.CookieSessionID, session.ID, s.cfg.IsHTTPS, s.cfg.BasePath()))
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("[Middleware:Session] %s", session)
|
logger.Debug("[Middleware:Session] %s", session)
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
<label for="form-fever-password">{{ t "Fever Password" }}</label>
|
<label for="form-fever-password">{{ t "Fever Password" }}</label>
|
||||||
<input type="password" name="fever_password" id="form-fever-password" value="{{ .form.FeverPassword }}">
|
<input type="password" name="fever_password" id="form-fever-password" value="{{ .form.FeverPassword }}">
|
||||||
|
|
||||||
<p>{{ t "Fever API endpoint:" }} <strong>{{ baseURL }}{{ route "feverEndpoint" }}</strong></p>
|
<p>{{ t "Fever API endpoint:" }} <strong>{{ rootURL }}{{ route "feverEndpoint" }}</strong></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Pinboard</h3>
|
<h3>Pinboard</h3>
|
||||||
|
@ -120,7 +120,7 @@
|
||||||
<p>{{ t "This special link allows you to subscribe to a website directly by using a bookmark in your web browser." }}</p>
|
<p>{{ t "This special link allows you to subscribe to a website directly by using a bookmark in your web browser." }}</p>
|
||||||
|
|
||||||
<div class="bookmarklet">
|
<div class="bookmarklet">
|
||||||
<a href="javascript:location.href='{{ baseURL }}{{ route "bookmarklet" }}?uri='+encodeURIComponent(window.location.href)">{{ t "Add to Miniflux" }}</a>
|
<a href="javascript:location.href='{{ rootURL }}{{ route "bookmarklet" }}?uri='+encodeURIComponent(window.location.href)">{{ t "Add to Miniflux" }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>{{ t "Drag and drop this link to your bookmarks." }}</p>
|
<p>{{ t "Drag and drop this link to your bookmarks." }}</p>
|
||||||
|
|
|
@ -38,6 +38,9 @@ func (e *Engine) parseAll() {
|
||||||
"baseURL": func() string {
|
"baseURL": func() string {
|
||||||
return e.cfg.BaseURL()
|
return e.cfg.BaseURL()
|
||||||
},
|
},
|
||||||
|
"rootURL": func() string {
|
||||||
|
return e.cfg.RootURL()
|
||||||
|
},
|
||||||
"hasOAuth2Provider": func(provider string) bool {
|
"hasOAuth2Provider": func(provider string) bool {
|
||||||
return e.cfg.OAuth2Provider() == provider
|
return e.cfg.OAuth2Provider() == provider
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Code generated by go generate; DO NOT EDIT.
|
// Code generated by go generate; DO NOT EDIT.
|
||||||
// 2018-01-31 22:01:40.010173412 -0800 PST m=+0.035694895
|
// 2018-02-03 15:28:45.540437885 -0800 PST m=+0.032377624
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
||||||
|
@ -815,7 +815,7 @@ var templateViewsMap = map[string]string{
|
||||||
<label for="form-fever-password">{{ t "Fever Password" }}</label>
|
<label for="form-fever-password">{{ t "Fever Password" }}</label>
|
||||||
<input type="password" name="fever_password" id="form-fever-password" value="{{ .form.FeverPassword }}">
|
<input type="password" name="fever_password" id="form-fever-password" value="{{ .form.FeverPassword }}">
|
||||||
|
|
||||||
<p>{{ t "Fever API endpoint:" }} <strong>{{ baseURL }}{{ route "feverEndpoint" }}</strong></p>
|
<p>{{ t "Fever API endpoint:" }} <strong>{{ rootURL }}{{ route "feverEndpoint" }}</strong></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Pinboard</h3>
|
<h3>Pinboard</h3>
|
||||||
|
@ -895,7 +895,7 @@ var templateViewsMap = map[string]string{
|
||||||
<p>{{ t "This special link allows you to subscribe to a website directly by using a bookmark in your web browser." }}</p>
|
<p>{{ t "This special link allows you to subscribe to a website directly by using a bookmark in your web browser." }}</p>
|
||||||
|
|
||||||
<div class="bookmarklet">
|
<div class="bookmarklet">
|
||||||
<a href="javascript:location.href='{{ baseURL }}{{ route "bookmarklet" }}?uri='+encodeURIComponent(window.location.href)">{{ t "Add to Miniflux" }}</a>
|
<a href="javascript:location.href='{{ rootURL }}{{ route "bookmarklet" }}?uri='+encodeURIComponent(window.location.href)">{{ t "Add to Miniflux" }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>{{ t "Drag and drop this link to your bookmarks." }}</p>
|
<p>{{ t "Drag and drop this link to your bookmarks." }}</p>
|
||||||
|
@ -1225,7 +1225,7 @@ var templateViewsMapChecksums = map[string]string{
|
||||||
"feeds": "65b0a47c4438810b9d51c60f3f3b2519690e56ff74029e6296c68626b83a470b",
|
"feeds": "65b0a47c4438810b9d51c60f3f3b2519690e56ff74029e6296c68626b83a470b",
|
||||||
"history": "d2476fd727e4f53428b5ed1f3f9423063583337ec8cfe1dd9c931fcb03852a20",
|
"history": "d2476fd727e4f53428b5ed1f3f9423063583337ec8cfe1dd9c931fcb03852a20",
|
||||||
"import": "73b5112e20bfd232bf73334544186ea419505936bc237d481517a8622901878f",
|
"import": "73b5112e20bfd232bf73334544186ea419505936bc237d481517a8622901878f",
|
||||||
"integrations": "a677434e9a8be1f80cfbc1d04828dacc7abcec2a22b45c80594d49cc2ba7c0e5",
|
"integrations": "958b73d632a3e2a79368bb1582efb8aabc438cef4fa6e8dc1aa4932494916aca",
|
||||||
"login": "7d83c3067c02f1f6aafdd8816c7f97a4eb5a5a4bdaaaa4cc1e2fbb9c17ea65e8",
|
"login": "7d83c3067c02f1f6aafdd8816c7f97a4eb5a5a4bdaaaa4cc1e2fbb9c17ea65e8",
|
||||||
"sessions": "d8ef5900d8ea8395804b320002e5f45ed0ab8b790e43f674f61f8b9787041cbd",
|
"sessions": "d8ef5900d8ea8395804b320002e5f45ed0ab8b790e43f674f61f8b9787041cbd",
|
||||||
"settings": "ea2505b9d0a6d6bb594dba87a92079de19baa6d494f0651693a7685489fb7de9",
|
"settings": "ea2505b9d0a6d6bb594dba87a92079de19baa6d494f0651693a7685489fb7de9",
|
||||||
|
|
|
@ -59,7 +59,7 @@ func (c *Controller) CheckLogin(ctx *handler.Context, request *handler.Request,
|
||||||
|
|
||||||
logger.Info("[Controller:CheckLogin] username=%s just logged in", authForm.Username)
|
logger.Info("[Controller:CheckLogin] username=%s just logged in", authForm.Username)
|
||||||
|
|
||||||
response.SetCookie(cookie.New(cookie.CookieUserSessionID, sessionToken, c.cfg.IsHTTPS))
|
response.SetCookie(cookie.New(cookie.CookieUserSessionID, sessionToken, c.cfg.IsHTTPS, c.cfg.BasePath()))
|
||||||
response.Redirect(ctx.Route("unread"))
|
response.Redirect(ctx.Route("unread"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +75,6 @@ func (c *Controller) Logout(ctx *handler.Context, request *handler.Request, resp
|
||||||
logger.Error("[Controller:Logout] %v", err)
|
logger.Error("[Controller:Logout] %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
response.SetCookie(cookie.Expired(cookie.CookieUserSessionID, c.cfg.IsHTTPS))
|
response.SetCookie(cookie.Expired(cookie.CookieUserSessionID, c.cfg.IsHTTPS, c.cfg.BasePath()))
|
||||||
response.Redirect(ctx.Route("login"))
|
response.Redirect(ctx.Route("login"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,7 @@ func (c *Controller) OAuth2Callback(ctx *handler.Context, request *handler.Reque
|
||||||
|
|
||||||
logger.Info("[Controller:OAuth2Callback] username=%s just logged in", user.Username)
|
logger.Info("[Controller:OAuth2Callback] username=%s just logged in", user.Username)
|
||||||
|
|
||||||
response.SetCookie(cookie.New(cookie.CookieUserSessionID, sessionToken, c.cfg.IsHTTPS))
|
response.SetCookie(cookie.New(cookie.CookieUserSessionID, sessionToken, c.cfg.IsHTTPS, c.cfg.BasePath()))
|
||||||
response.Redirect(ctx.Route("unread"))
|
response.Redirect(ctx.Route("unread"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue