fix(api): bound mysql page sizes
This commit is contained in:
@@ -13,8 +13,10 @@ type SQLQuery struct {
|
|||||||
CountArgs []any
|
CountArgs []any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maxSQLPageSize = 10_000
|
||||||
|
|
||||||
func buildVehicleListSQL(query url.Values) SQLQuery {
|
func buildVehicleListSQL(query url.Values) SQLQuery {
|
||||||
limit := parsePositive(query.Get("limit"), 20)
|
limit := parseSQLPageSize(query.Get("limit"), 20)
|
||||||
offset := parsePositive(query.Get("offset"), 0)
|
offset := parsePositive(query.Get("offset"), 0)
|
||||||
canonicalSourceCount := strconv.Itoa(len(canonicalVehicleProtocols))
|
canonicalSourceCount := strconv.Itoa(len(canonicalVehicleProtocols))
|
||||||
args := []any{}
|
args := []any{}
|
||||||
@@ -65,7 +67,7 @@ func buildVehicleListSQL(query url.Values) SQLQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildVehicleCoverageSQL(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)
|
offset := parsePositive(query.Get("offset"), 0)
|
||||||
canonicalSourceCount := strconv.Itoa(len(canonicalVehicleProtocols))
|
canonicalSourceCount := strconv.Itoa(len(canonicalVehicleProtocols))
|
||||||
args := []any{}
|
args := []any{}
|
||||||
@@ -288,7 +290,7 @@ func archiveMissingFieldPredicate(field string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildRealtimeLocationSQL(query url.Values) SQLQuery {
|
func buildRealtimeLocationSQL(query url.Values) SQLQuery {
|
||||||
limit := parsePositive(query.Get("limit"), 20)
|
limit := parseSQLPageSize(query.Get("limit"), 20)
|
||||||
offset := parsePositive(query.Get("offset"), 0)
|
offset := parsePositive(query.Get("offset"), 0)
|
||||||
args := []any{}
|
args := []any{}
|
||||||
where := []string{"1 = 1"}
|
where := []string{"1 = 1"}
|
||||||
@@ -316,7 +318,7 @@ func buildRealtimeLocationSQL(query url.Values) SQLQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildVehicleRealtimeSQL(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)
|
offset := parsePositive(query.Get("offset"), 0)
|
||||||
canonicalSourceCount := strconv.Itoa(len(canonicalVehicleProtocols))
|
canonicalSourceCount := strconv.Itoa(len(canonicalVehicleProtocols))
|
||||||
args := []any{}
|
args := []any{}
|
||||||
@@ -415,7 +417,7 @@ func buildVehicleRealtimeSQL(query url.Values) SQLQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildDailyMileageSQL(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)
|
offset := parsePositive(query.Get("offset"), 0)
|
||||||
args := []any{}
|
args := []any{}
|
||||||
where := []string{"1 = 1"}
|
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) {
|
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) {
|
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 {
|
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) {
|
func TestMileageQueriesRespectEnabledProtocolPriority(t *testing.T) {
|
||||||
query := url.Values{
|
query := url.Values{
|
||||||
"protocols": {"JT808,GB32960"},
|
"protocols": {"JT808,GB32960"},
|
||||||
|
|||||||
Reference in New Issue
Block a user