Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion api/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func buildWithPaging(ctx *gin.Context, paging *pagingParams, messages []*model.M
useMessages = messages[:len(messages)-1]
since = useMessages[len(useMessages)-1].ID
url := location.Get(ctx)
url.Path = ctx.Request.URL.Path
url.Path = withForwardedPrefix(ctx, ctx.Request.URL.Path)
query := url.Query()
query.Add("limit", strconv.Itoa(paging.Limit))
query.Add("since", strconv.FormatUint(uint64(since), 10))
Expand All @@ -118,6 +118,20 @@ func buildWithPaging(ctx *gin.Context, paging *pagingParams, messages []*model.M
}
}

// withForwardedPrefix prepends the reverse-proxy path prefix to the given
// request path. Reverse proxies that strip a subpath before forwarding the
// request (e.g. Caddy's `uri strip_prefix /gotify`) can announce the stripped
// prefix via the X-Forwarded-Prefix header, so that URLs emitted by gotify —
// like the paging `next` link — keep pointing at the externally reachable path
// instead of dropping the prefix. See #664.
func withForwardedPrefix(ctx *gin.Context, path string) string {
prefix := strings.Trim(ctx.GetHeader("X-Forwarded-Prefix"), "/")
if prefix == "" {
return path
}
return "/" + prefix + "/" + strings.TrimPrefix(path, "/")
}

func withPaging(ctx *gin.Context, f func(pagingParams *pagingParams)) {
params := &pagingParams{Limit: 100}
if err := ctx.MustBindWith(params, binding.Query); err == nil {
Expand Down
27 changes: 27 additions & 0 deletions api/message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,33 @@ func (s *MessageSuite) Test_GetMessages_WithLimit_ReturnsNext() {
test.BodyEquals(s.T(), expected, s.recorder)
}

func (s *MessageSuite) Test_GetMessages_WithLimit_BehindProxyPrefix_ReturnsNextWithPrefix() {
user := s.db.User(5)
app1 := user.App(1)
app2 := user.App(2)
var messages []*model.Message
for i := 100; i >= 1; i -= 2 {
one := app2.NewMessage(uint(i))
two := app1.NewMessage(uint(i - 1))
messages = append(messages, &one, &two)
}

// The reverse proxy strips the /gotify prefix, so gotify receives /message,
// but announces the stripped prefix via X-Forwarded-Prefix. The emitted
// next url must keep the prefix. See #664.
s.withURL("http", "example.com", "/message", "limit=5")
s.ctx.Request.Header.Set("X-Forwarded-Prefix", "/gotify")
test.WithUser(s.ctx, 5)
s.a.GetMessages(s.ctx)

expected := &model.PagedMessages{
Paging: model.Paging{Limit: 5, Size: 5, Since: 96, Next: "http://example.com/gotify/message?limit=5&since=96"},
Messages: toExternalMessages(messages[:5]),
}

test.BodyEquals(s.T(), expected, s.recorder)
}

func (s *MessageSuite) Test_GetMessages_WithLimit_WithSince_ReturnsNext() {
user := s.db.User(5)
app1 := user.App(1)
Expand Down