Ver código fonte

fix(googlereader): `/items/contents` should accept short form item IDs

Frédéric Guillot 11 meses atrás
pai
commit
6cc8d8abf1

+ 3 - 27
internal/googlereader/handler.go

@@ -60,8 +60,6 @@ const (
 	BroadcastFriends = "broadcast-friends"
 	// Like is the suffix for like stream
 	Like = "like"
-	// EntryIDLong is the long entry id representation
-	EntryIDLong = "tag:google.com,2005:reader/item/%016x"
 )
 
 const (
@@ -370,28 +368,6 @@ func checkAndSimplifyTags(addTags []Stream, removeTags []Stream) (map[StreamType
 	return tags, nil
 }
 
-func getItemIDs(r *http.Request) ([]int64, error) {
-	items := r.Form[ParamItemIDs]
-	if len(items) == 0 {
-		return nil, fmt.Errorf("googlereader: no items requested")
-	}
-
-	itemIDs := make([]int64, len(items))
-
-	for i, item := range items {
-		var itemID int64
-		_, err := fmt.Sscanf(item, EntryIDLong, &itemID)
-		if err != nil {
-			itemID, err = strconv.ParseInt(item, 16, 64)
-			if err != nil {
-				return nil, fmt.Errorf("googlereader: could not parse item: %v", item)
-			}
-		}
-		itemIDs[i] = itemID
-	}
-	return itemIDs, nil
-}
-
 func checkOutputFormat(r *http.Request) error {
 	var output string
 	if r.Method == http.MethodPost {
@@ -568,7 +544,7 @@ func (h *handler) editTagHandler(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	itemIDs, err := getItemIDs(r)
+	itemIDs, err := parseItemIDsFromRequest(r)
 	if err != nil {
 		json.ServerError(w, r, err)
 		return
@@ -985,7 +961,7 @@ func (h *handler) streamItemContentsHandler(w http.ResponseWriter, r *http.Reque
 	userRead := fmt.Sprintf(UserStreamPrefix, userID) + Read
 	userStarred := fmt.Sprintf(UserStreamPrefix, userID) + Starred
 
-	itemIDs, err := getItemIDs(r)
+	itemIDs, err := parseItemIDsFromRequest(r)
 	if err != nil {
 		json.ServerError(w, r, err)
 		return
@@ -1058,7 +1034,7 @@ func (h *handler) streamItemContentsHandler(w http.ResponseWriter, r *http.Reque
 		entry.Enclosures.ProxifyEnclosureURL(h.router)
 
 		contentItems[i] = contentItem{
-			ID:            fmt.Sprintf(EntryIDLong, entry.ID),
+			ID:            convertEntryIDToLongFormItemID(entry.ID),
 			Title:         entry.Title,
 			Author:        entry.Author,
 			TimestampUsec: fmt.Sprintf("%d", entry.Date.UnixMicro()),

+ 65 - 0
internal/googlereader/item.go

@@ -0,0 +1,65 @@
+// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+package googlereader // import "miniflux.app/v2/internal/googlereader"
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+	"strconv"
+	"strings"
+)
+
+const (
+	ItemIDPrefix = "tag:google.com,2005:reader/item/"
+)
+
+func convertEntryIDToLongFormItemID(entryID int64) string {
+	// The entry ID is a 64-bit integer, so we need to format it as a 16-character hexadecimal string.
+	return ItemIDPrefix + fmt.Sprintf("%016x", entryID)
+}
+
+// parseItemID parses a Google Reader ID string.
+// It supports both the long form (tag:google.com,2005:reader/item/<hex_id>) and the short form (<decimal_id>).
+// It returns the parsed ID as a int64 and an error if parsing fails.
+func parseItemID(itemIDValue string) (int64, error) {
+	if strings.HasPrefix(itemIDValue, ItemIDPrefix) {
+		hexID := strings.TrimPrefix(itemIDValue, ItemIDPrefix)
+
+		// It's always 16 characters wide.
+		if len(hexID) != 16 {
+			return 0, errors.New("long form ID has incorrect length")
+		}
+
+		parsedID, err := strconv.ParseInt(hexID, 16, 64)
+		if err != nil {
+			return 0, errors.New("failed to parse long form hex ID: " + err.Error())
+		}
+		return parsedID, nil
+	} else {
+		parsedID, err := strconv.ParseInt(itemIDValue, 10, 64)
+		if err != nil {
+			return 0, errors.New("failed to parse short form decimal ID: " + err.Error())
+		}
+		return parsedID, nil
+	}
+}
+
+func parseItemIDsFromRequest(r *http.Request) ([]int64, error) {
+	items := r.Form[ParamItemIDs]
+	if len(items) == 0 {
+		return nil, fmt.Errorf("googlereader: no items requested")
+	}
+
+	itemIDs := make([]int64, len(items))
+	for i, item := range items {
+		itemID, err := parseItemID(item)
+		if err != nil {
+			return nil, fmt.Errorf("googlereader: failed to parse item ID %s: %w", item, err)
+		}
+		itemIDs[i] = itemID
+	}
+
+	return itemIDs, nil
+}

+ 109 - 0
internal/googlereader/item_test.go

@@ -0,0 +1,109 @@
+// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+package googlereader // import "miniflux.app/v2/internal/googlereader"
+
+import (
+	"net/http"
+	"net/url"
+	"reflect"
+	"testing"
+)
+
+func TestConvertEntryIDToLongFormItemID(t *testing.T) {
+	entryID := int64(344691561)
+	expected := "tag:google.com,2005:reader/item/00000000148b9369"
+	result := convertEntryIDToLongFormItemID(entryID)
+
+	if result != expected {
+		t.Errorf("expected %s, got %s", expected, result)
+	}
+}
+
+func TestParseItemIDsFromRequest(t *testing.T) {
+	formValues := url.Values{}
+	formValues.Add("i", "12345")
+	formValues.Add("i", convertEntryIDToLongFormItemID(45678))
+
+	request := &http.Request{
+		Form: formValues,
+	}
+
+	result, err := parseItemIDsFromRequest(request)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+
+	var expected = []int64{12345, 45678}
+	if !reflect.DeepEqual(result, expected) {
+		t.Errorf("expected %v, got %v", expected, result)
+	}
+
+	// Test with no item IDs
+	formValues = url.Values{}
+	request = &http.Request{
+		Form: formValues,
+	}
+	_, err = parseItemIDsFromRequest(request)
+	if err == nil {
+		t.Fatalf("expected error, got nil")
+	}
+}
+
+func TestParseItemID(t *testing.T) {
+	// Test with long form ID
+	result, err := parseItemID("tag:google.com,2005:reader/item/0000000000000001")
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	expected := int64(1)
+	if result != expected {
+		t.Errorf("expected %d, got %d", expected, result)
+	}
+
+	// Test with short form ID
+	result, err = parseItemID("12345")
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	expected = int64(12345)
+	if result != expected {
+		t.Errorf("expected %d, got %d", expected, result)
+	}
+
+	// Test with invalid long form ID
+	_, err = parseItemID("tag:google.com,2005:reader/item/000000000000000g")
+	if err == nil {
+		t.Fatalf("expected error, got nil")
+	}
+
+	// Test with invalid short form ID
+	_, err = parseItemID("invalid_id")
+	if err == nil {
+		t.Fatalf("expected error, got nil")
+	}
+
+	// Test with empty ID
+	_, err = parseItemID("")
+	if err == nil {
+		t.Fatalf("expected error, got nil")
+	}
+
+	// Test with ID that is too short
+	_, err = parseItemID("tag:google.com,2005:reader/item/00000000000000")
+	if err == nil {
+		t.Fatalf("expected error, got nil")
+	}
+
+	// Test with ID that is too long
+	_, err = parseItemID("tag:google.com,2005:reader/item/000000000000000000")
+	if err == nil {
+		t.Fatalf("expected error, got nil")
+	}
+
+	// Test with ID that is not a number
+	_, err = parseItemID("tag:google.com,2005:reader/item/abc")
+	if err == nil {
+		t.Fatalf("expected error, got nil")
+	}
+}