feat(validator): cap entry limit at MaxEntryLimit
Reject API "limit" query parameter and "entries_per_page" preference values above 1000, and clamp the storage entry query builder so any caller (REST API, Google Reader, internal UI) is bounded. The HTML settings form now exposes the cap via the input "max" attribute.
This commit is contained in:
committed by
fguillot
parent
6543d652a6
commit
0909323ae3
@@ -15,6 +15,10 @@ const (
|
||||
DefaultSortingDirection = "asc"
|
||||
)
|
||||
|
||||
// MaxEntryLimit is the maximum allowed value for the "limit" query parameter
|
||||
// and for the user "entries_per_page" preference.
|
||||
const MaxEntryLimit = 1000
|
||||
|
||||
// Entry represents a feed item in the system.
|
||||
type Entry struct {
|
||||
ID int64 `json:"id"`
|
||||
|
||||
@@ -216,7 +216,7 @@ func (e *EntryQueryBuilder) WithSorting(column, direction string) *EntryQueryBui
|
||||
// WithLimit set the limit.
|
||||
func (e *EntryQueryBuilder) WithLimit(limit int) *EntryQueryBuilder {
|
||||
if limit > 0 {
|
||||
e.limit = limit
|
||||
e.limit = min(limit, model.MaxEntryLimit)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
</select>
|
||||
|
||||
<label for="form-entries-per-page">{{ t "form.prefs.label.entries_per_page" }}</label>
|
||||
<input type="number" name="entries_per_page" id="form-entries-per-page" value="{{ .form.EntriesPerPage }}" min="1">
|
||||
<input type="number" name="entries_per_page" id="form-entries-per-page" value="{{ .form.EntriesPerPage }}" min="1" max="{{ .maxEntriesPerPage }}">
|
||||
|
||||
<label><input type="checkbox" name="keyboard_shortcuts" value="1" {{ if .form.KeyboardShortcuts }}checked{{ end }}> {{ t "form.prefs.label.keyboard_shortcuts" }}</label>
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
|
||||
view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(user.ID))
|
||||
view.Set("default_home_pages", model.HomePages())
|
||||
view.Set("categories_sorting_options", model.CategoriesSortingOptions())
|
||||
view.Set("maxEntriesPerPage", model.MaxEntryLimit)
|
||||
view.Set("countWebAuthnCerts", h.store.CountWebAuthnCredentialsByUserID(user.ID))
|
||||
view.Set("webAuthnCerts", creds)
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ func validateTimezone(timezoneValue string) *locale.LocalizedError {
|
||||
}
|
||||
|
||||
func validateEntriesPerPage(entriesPerPage int) *locale.LocalizedError {
|
||||
if entriesPerPage < 1 {
|
||||
if entriesPerPage < 1 || entriesPerPage > model.MaxEntryLimit {
|
||||
return locale.NewLocalizedError("error.entries_per_page_invalid")
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -110,11 +110,13 @@ func TestValidateEntryDirection(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateEntriesPerPage(t *testing.T) {
|
||||
if err := validateEntriesPerPage(1); err != nil {
|
||||
t.Errorf("expected positive entries per page to pass, got %v", err)
|
||||
for _, value := range []int{1, model.MaxEntryLimit} {
|
||||
if err := validateEntriesPerPage(value); err != nil {
|
||||
t.Errorf("expected %d to pass, got %v", value, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, value := range []int{0, -1} {
|
||||
for _, value := range []int{0, -1, model.MaxEntryLimit + 1} {
|
||||
if err := validateEntriesPerPage(value); err == nil {
|
||||
t.Errorf("expected %d to fail", value)
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@ package validator // import "miniflux.app/v2/internal/validator"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"miniflux.app/v2/internal/model"
|
||||
)
|
||||
|
||||
var domainRegex = regexp.MustCompile(`^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$`)
|
||||
@@ -21,6 +24,10 @@ func ValidateRange(offset, limit int) error {
|
||||
return errors.New(`limit value should be >= 0`)
|
||||
}
|
||||
|
||||
if limit > model.MaxEntryLimit {
|
||||
return fmt.Errorf(`limit value should be <= %d`, model.MaxEntryLimit)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
|
||||
package validator // import "miniflux.app/v2/internal/validator"
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"miniflux.app/v2/internal/model"
|
||||
)
|
||||
|
||||
func TestValidateRange(t *testing.T) {
|
||||
if err := ValidateRange(-1, 0); err == nil {
|
||||
@@ -14,9 +18,17 @@ func TestValidateRange(t *testing.T) {
|
||||
t.Error(`An invalid limit should generate a error`)
|
||||
}
|
||||
|
||||
if err := ValidateRange(0, model.MaxEntryLimit+1); err == nil {
|
||||
t.Error(`A limit above MaxEntryLimit should generate an error`)
|
||||
}
|
||||
|
||||
if err := ValidateRange(42, 42); err != nil {
|
||||
t.Error(`A valid offset and limit should not generate any error`)
|
||||
}
|
||||
|
||||
if err := ValidateRange(0, model.MaxEntryLimit); err != nil {
|
||||
t.Error(`A limit equal to MaxEntryLimit should not generate an error`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDirection(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user