Приём/UI: пачка фиксов границ и парсинга (F7–F10, N2)

Пять независимых bugfix'ов из ревью приёма (docs/backlog/review-f7-f10-ingest-ui-fixes.md):

- F7: oversized .torrent через веб отдавал 500. Введён sentinel
  ingest.ErrTorrentTooLarge, classifyErr транслирует его в 400.
- F8: гонка fast-path attach с cancel. Пред-рид
  FindReingestBlockingByInfohash больше не короткозамыкает активную
  запись — авторитетное дедуп-решение принимает CreateDownloadIfNoActive
  под BEGIN IMMEDIATE; короткозамыкание оставлено только для терминальных
  desync-записей (target_missing/orphaned). F6-апгрейд сохранён.
- F9: magnet — регистронезависимый URN-префикс xt (RFC 2141);
  tgbot.ParseMessage срезает хвостовую пунктуацию, приклеенную жадным
  matchем.
- F10: cap контекста до 16 KiB в ingest.Ingest (единственное место
  слияния — покрывает все транспорты), рунобезопасная обрезка + маркер.
- N2: httpapi.shorten режет по рунам, не байтам — кириллица не рвётся в
  U+FFFD.

Добавлены юнит-тесты на каждое исправленное поведение.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-07-17 20:35:14 +03:00
co-authored by Claude Opus 4.8
parent 17d9f23630
commit b8017d65eb
9 changed files with 244 additions and 15 deletions
+17 -4
View File
@@ -30,6 +30,19 @@ type Info struct {
// ErrNotMagnet возвращается, если строка не является magnet-ссылкой.
var ErrNotMagnet = errors.New("not a magnet link")
// URN-префиксы xt-топиков. По RFC 2141 namespace-идентификатор URN
// регистронезависим, поэтому сверяем их EqualFold (magnet:?xt=URN:BTIH:… —
// валиден). Сам хеш нормализует по регистру normalizeBTIH/normalizeBTMH.
const (
prefixBTIH = "urn:btih:"
prefixBTMH = "urn:btmh:"
)
// hasURNPrefix сообщает, начинается ли xt с URN-префикса регистронезависимо.
func hasURNPrefix(xt, prefix string) bool {
return len(xt) >= len(prefix) && strings.EqualFold(xt[:len(prefix)], prefix)
}
// Parse разбирает magnet-ссылку. Поддерживаются btih (v1: 40-hex или
// 32-символьный base32) и btmh (v2: sha256-multihash). При нескольких xt
// предпочитается v1.
@@ -43,12 +56,12 @@ func Parse(raw string) (Info, error) {
var v1, v2 string
for _, xt := range vals["xt"] {
switch {
case strings.HasPrefix(xt, "urn:btih:"):
if h, err := normalizeBTIH(strings.TrimPrefix(xt, "urn:btih:")); err == nil && v1 == "" {
case hasURNPrefix(xt, prefixBTIH):
if h, err := normalizeBTIH(xt[len(prefixBTIH):]); err == nil && v1 == "" {
v1 = h
}
case strings.HasPrefix(xt, "urn:btmh:"):
if h, err := normalizeBTMH(strings.TrimPrefix(xt, "urn:btmh:")); err == nil && v2 == "" {
case hasURNPrefix(xt, prefixBTMH):
if h, err := normalizeBTMH(xt[len(prefixBTMH):]); err == nil && v2 == "" {
v2 = h
}
}
+7
View File
@@ -31,6 +31,13 @@ func TestParse(t *testing.T) {
raw: "magnet:?xt=urn:btmh:12200123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
infohash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
},
{
// RFC 2141: namespace URN регистронезависим — URN:BTIH: валиден.
name: "btih с URN-префиксом в верхнем регистре",
raw: "magnet:?xt=URN:BTIH:541ADCFF3B6DD5DBA7088EA83317D9D6FAC331D6&dn=Dune",
infohash: "541adcff3b6dd5dba7088ea83317d9d6fac331d6",
dn: "Dune",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {