fix: panic on restoring with numeric position fields (#1089)

Co-authored-by: kolaente <k@knt.li>
This commit is contained in:
Tobias 2025-07-15 17:44:21 +02:00 committed by GitHub
parent 07a77a1117
commit ecc95e9139
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 161 additions and 18 deletions

View File

@ -190,6 +190,55 @@ func Restore(filename string) error {
return nil
}
func convertFieldValue(fieldName string, value interface{}, isFloat bool) (interface{}, error) {
// Check if this is a float field and the value is already a number
if isFloat {
switch v := value.(type) {
case float64:
// Already a float64, no need to process
return v, nil
case int:
// Convert int to float64
return float64(v), nil
case string:
// Try to decode from base64 string and convert to float
decoded, err := base64.StdEncoding.DecodeString(v)
if err != nil {
var corruptErr base64.CorruptInputError
if !errors.As(err, &corruptErr) {
return nil, fmt.Errorf("could not decode field '%s' %s: %w", fieldName, value, err)
}
// If it's a CorruptInputError, treat the string as raw data
decoded = []byte(v)
}
val, err := strconv.ParseFloat(string(decoded), 64)
if err != nil {
return nil, fmt.Errorf("could not parse double value for field '%s': %w", fieldName, err)
}
return val, nil
default:
return nil, fmt.Errorf("unexpected type for float field '%s': %T", fieldName, v)
}
}
// Handle JSON fields (non-float)
switch v := value.(type) {
case string:
decoded, err := base64.StdEncoding.DecodeString(v)
if err != nil {
var corruptErr base64.CorruptInputError
if !errors.As(err, &corruptErr) {
return nil, fmt.Errorf("could not decode field '%s' %s: %w", fieldName, value, err)
}
// If it's a CorruptInputError, treat the string as raw data
decoded = []byte(v)
}
return string(decoded), nil
default:
return nil, fmt.Errorf("expected string for JSON field '%s', got %T", fieldName, v)
}
}
func restoreTableData(tables map[string]*zip.File) error {
jsonFields := map[string][]string{
"api_tokens": {"permissions"},
@ -221,25 +270,11 @@ func restoreTableData(tables map[string]*zip.File) error {
continue
}
var decoded []byte
decoded, err = base64.StdEncoding.DecodeString(content[i][f].(string))
if err != nil && !errors.Is(err, base64.CorruptInputError(0)) {
return fmt.Errorf("could not decode field '%s' %s: %w", f, content[i][f], err)
}
if err != nil && errors.Is(err, base64.CorruptInputError(0)) {
decoded = []byte(content[i][f].(string))
}
if isFloat {
val, err := strconv.ParseFloat(string(decoded), 64)
if err != nil {
return fmt.Errorf("could not parse double value for field '%s': %w", f, err)
}
content[i][f] = val
} else {
content[i][f] = string(decoded)
convertedValue, err := convertFieldValue(f, content[i][f], isFloat)
if err != nil {
return err
}
content[i][f] = convertedValue
}
}
return nil

View File

@ -0,0 +1,108 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dump
import (
"encoding/base64"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestConvertFieldValue(t *testing.T) {
t.Run("Float field conversions", func(t *testing.T) {
t.Run("should return float64 as-is", func(t *testing.T) {
result, err := convertFieldValue("position", 123.45, true)
require.NoError(t, err)
assert.InEpsilon(t, 123.45, result, 0.0001)
})
t.Run("should convert int to float64", func(t *testing.T) {
result, err := convertFieldValue("position", 42, true)
require.NoError(t, err)
assert.InEpsilon(t, 42.0, result, 0.0001)
})
t.Run("should decode base64 string and convert to float", func(t *testing.T) {
encoded := base64.StdEncoding.EncodeToString([]byte("123.45"))
result, err := convertFieldValue("position", encoded, true)
require.NoError(t, err)
assert.InEpsilon(t, 123.45, result, 0.0001)
})
t.Run("should handle non-base64 string and convert to float", func(t *testing.T) {
result, err := convertFieldValue("position", "67.89", true)
require.NoError(t, err)
assert.InEpsilon(t, 67.89, result, 0.0001)
})
t.Run("should return error for invalid float string", func(t *testing.T) {
_, err := convertFieldValue("position", "not-a-number", true)
require.Error(t, err)
assert.Contains(t, err.Error(), "could not parse double value")
})
t.Run("should return error for unexpected type", func(t *testing.T) {
_, err := convertFieldValue("position", []int{1, 2, 3}, true)
require.Error(t, err)
assert.Contains(t, err.Error(), "unexpected type for float field")
})
})
t.Run("JSON field conversions", func(t *testing.T) {
t.Run("should decode base64 string", func(t *testing.T) {
jsonData := `{"key": "value"}`
encoded := base64.StdEncoding.EncodeToString([]byte(jsonData))
result, err := convertFieldValue("permissions", encoded, false)
require.NoError(t, err)
assert.JSONEq(t, jsonData, result.(string))
})
t.Run("should handle non-base64 string", func(t *testing.T) {
jsonData := `{"key": "value"}`
result, err := convertFieldValue("permissions", jsonData, false)
require.NoError(t, err)
assert.JSONEq(t, jsonData, result.(string))
})
t.Run("should return error for non-string type", func(t *testing.T) {
_, err := convertFieldValue("permissions", 123, false)
require.Error(t, err)
assert.Contains(t, err.Error(), "expected string for JSON field")
})
})
t.Run("Base64 error handling", func(t *testing.T) {
t.Run("should handle base64 decode error for float field", func(t *testing.T) {
invalidBase64 := "invalid base64 with spaces and special chars!!!"
result, err := convertFieldValue("position", invalidBase64, true)
// Should not error on decode, but should error on parse float
require.Error(t, err)
assert.Contains(t, err.Error(), "could not parse double value")
assert.Nil(t, result)
})
t.Run("should handle base64 decode error for JSON field", func(t *testing.T) {
// For JSON fields, CorruptInputError just returns the raw string
invalidBase64 := "invalid base64 with spaces and special chars!!!"
result, err := convertFieldValue("permissions", invalidBase64, false)
require.NoError(t, err)
assert.Equal(t, invalidBase64, result)
})
})
}