fix(api): bound mysql page sizes
This commit is contained in:
@@ -13,8 +13,10 @@ type SQLQuery struct {
|
||||
CountArgs []any
|
||||
}
|
||||
|
||||
const maxSQLPageSize = 10_000
|
||||
|
||||
func buildVehicleListSQL(query url.Values) SQLQuery {
|
||||
limit := parsePositive(query.Get("limit"), 20)
|
||||
limit := parseSQLPageSize(query.Get("limit"), 20)
|
||||
offset := parsePositive(query.Get("offset"), 0)
|
||||
canonicalSourceCount := strconv.Itoa(len(canonicalVehicleProtocols))
|
||||
args := []any{}
|
||||
@@ -65,7 +67,7 @@ func buildVehicleListSQL(query url.Values) SQLQuery {
|
||||
}
|
||||
|
||||
func buildVehicleCoverageSQL(query url.Values) SQLQuery {
|
||||
limit := parsePositive(query.Get("limit"), 20)
|
||||
limit := parseSQLPageSize(query.Get("limit"), 20)
|
||||
offset := parsePositive(query.Get("offset"), 0)
|
||||
canonicalSourceCount := strconv.Itoa(len(canonicalVehicleProtocols))
|
||||
args := []any{}
|
||||
@@ -288,7 +290,7 @@ func archiveMissingFieldPredicate(field string) string {
|
||||
}
|
||||
|
||||
func buildRealtimeLocationSQL(query url.Values) SQLQuery {
|
||||
limit := parsePositive(query.Get("limit"), 20)
|
||||
limit := parseSQLPageSize(query.Get("limit"), 20)
|
||||
offset := parsePositive(query.Get("offset"), 0)
|
||||
args := []any{}
|
||||
where := []string{"1 = 1"}
|
||||
@@ -316,7 +318,7 @@ func buildRealtimeLocationSQL(query url.Values) SQLQuery {
|
||||
}
|
||||
|
||||
func buildVehicleRealtimeSQL(query url.Values) SQLQuery {
|
||||
limit := parsePositive(query.Get("limit"), 20)
|
||||
limit := parseSQLPageSize(query.Get("limit"), 20)
|
||||
offset := parsePositive(query.Get("offset"), 0)
|
||||
canonicalSourceCount := strconv.Itoa(len(canonicalVehicleProtocols))
|
||||
args := []any{}
|
||||
@@ -415,7 +417,7 @@ func buildVehicleRealtimeSQL(query url.Values) SQLQuery {
|
||||
}
|
||||
|
||||
func buildDailyMileageSQL(query url.Values) SQLQuery {
|
||||
limit := parsePositive(query.Get("limit"), 20)
|
||||
limit := parseSQLPageSize(query.Get("limit"), 20)
|
||||
offset := parsePositive(query.Get("offset"), 0)
|
||||
args := []any{}
|
||||
where := []string{"1 = 1"}
|
||||
@@ -671,11 +673,22 @@ func appendVINListFilter(where []string, args []any, column string, raw string)
|
||||
}
|
||||
|
||||
func buildLimitOffset(query url.Values) (int, int) {
|
||||
return parsePositive(query.Get("limit"), 20), parsePositive(query.Get("offset"), 0)
|
||||
return parseSQLPageSize(query.Get("limit"), 20), parsePositive(query.Get("offset"), 0)
|
||||
}
|
||||
|
||||
func parseLimitOffset(rawLimit, rawOffset string) (int, int) {
|
||||
return parsePositive(rawLimit, 20), parsePositive(rawOffset, 0)
|
||||
return parseSQLPageSize(rawLimit, 20), parsePositive(rawOffset, 0)
|
||||
}
|
||||
|
||||
func parseSQLPageSize(raw string, fallback int) int {
|
||||
value, err := strconv.Atoi(raw)
|
||||
if err != nil || value <= 0 {
|
||||
return fallback
|
||||
}
|
||||
if value > maxSQLPageSize {
|
||||
return maxSQLPageSize
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func mustInt(value string) int {
|
||||
|
||||
@@ -319,6 +319,29 @@ func TestBuildDailyMileageSQLCanMatchStatisticsVehicleDayScope(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMySQLListBuildersClampUntrustedPageSizes(t *testing.T) {
|
||||
query := url.Values{"limit": {"1000000000"}, "offset": {"7"}}
|
||||
builders := map[string]SQLQuery{
|
||||
"vehicles": buildVehicleListSQL(query),
|
||||
"vehicle coverage": buildVehicleCoverageSQL(query),
|
||||
"realtime location": buildRealtimeLocationSQL(query),
|
||||
"vehicle realtime": buildVehicleRealtimeSQL(query),
|
||||
"daily mileage": buildDailyMileageSQL(query),
|
||||
}
|
||||
for name, built := range builders {
|
||||
if len(built.Args) < 2 || built.Args[len(built.Args)-2] != maxSQLPageSize || built.Args[len(built.Args)-1] != 7 {
|
||||
t.Fatalf("%s pagination was not clamped: %#v", name, built.Args)
|
||||
}
|
||||
}
|
||||
limit, offset := buildLimitOffset(query)
|
||||
if limit != maxSQLPageSize || offset != 7 {
|
||||
t.Fatalf("response pagination metadata must match SQL: limit=%d offset=%d", limit, offset)
|
||||
}
|
||||
if limit, _ := parseLimitOffset("0", "0"); limit != 20 {
|
||||
t.Fatalf("zero page size must use the safe default, got %d", limit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMileageQueriesRespectEnabledProtocolPriority(t *testing.T) {
|
||||
query := url.Values{
|
||||
"protocols": {"JT808,GB32960"},
|
||||
|
||||
Reference in New Issue
Block a user