Non-UTF-8 extension catalog aborts the whole merge

已關閉 適合新手
#5,195 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

維護者通常 1 天內回覆

已經有一個關聯 PR 被合併了。

  • #5196 來自 @chelsealong —— 已合併

評估

難度
2/5
預估耗時
1-3 小時
新手友好度
74/100
Issue 類型
缺陷
描述清晰度
描述清楚
活躍度
活躍
技術堆疊
python
領域
cli

研究方向

閱讀 src/specify_cli/extensions/__init__.py 中 _fetch_single_catalog 和 _get_merged_extensions 附近的程式碼,接著追蹤擷取錯誤和 JSON 解析錯誤的處理方式。使用一個包含無效 UTF-8 的 catalog 和一個正常的 catalog 重現合併過程,並以無效 JSON 的情況作為比較。完成標準是:失敗的來源會被略過並發出警告,正常的 catalog 仍會提供結果;所有來源都失敗時仍應引發例外。

由索引模型根據 Issue 內容生成。

描述

  • Repository: github/spec-kit
  • Target commit: 1c799bda6c136fcf42fb3475a922ccaac57e6c71
  • File: src/specify_cli/extensions/__init__.py:4700-4725

Summary

non-UTF-8 extension catalog aborts the whole merge.

Root cause

ExtensionCatalog._fetch_single_catalog catches URLError and JSONDecodeError, but the UTF-8 decode raises UnicodeDecodeError, which escapes.

Affected code

src/specify_cli/extensions/__init__.py:

 4690  
 4691          Returns:
 4692              URL of the primary catalog
 4693  
 4694          Raises:
 4695              ValidationError: If a catalog URL is invalid
 4696          """
 4697          active = self.get_active_catalogs()
 4698          return active[0].url if active else self.DEFAULT_CATALOG_URL
 4699  
 4700      def _fetch_single_catalog(
 4701          self, entry: CatalogEntry, force_refresh: bool = False
 4702      ) -> Dict[str, Any]:
 4703          """Fetch a single catalog with per-URL caching.
 4704  
 4705          For the DEFAULT_CATALOG_URL, uses legacy cache files (self.cache_file /
 4706          self.cache_metadata_file) for backward compatibility. For all other URLs,
 4707          uses URL-hash-based cache files in self.cache_dir.
 4708  
 4709          Args:
 4710              entry: CatalogEntry describing the catalog to fetch
 4711              force_refresh: If True, bypass cache
 4712  
 4713          Returns:
 4714              Catalog data dictionary
 4715  
 4716          Raises:
 4717              ExtensionError: If catalog cannot be fetched or has invalid format
 4718          """
 4719          import urllib.error
 4720  
 4721          # Determine cache file paths (backward compat for default catalog)
 4722          if entry.url == self.DEFAULT_CATALOG_URL:
 4723              cache_file = self.cache_file
 4724              cache_meta_file = self.cache_metadata_file
 4725              is_valid = not force_refresh and self.is_cache_valid()
 4726          else:
 4727              url_hash = hashlib.sha256(entry.url.encode()).hexdigest()[:16]
 4728              cache_file = self.cache_dir / f"catalog-{url_hash}.json"
 4729              cache_meta_file = self.cache_dir / f"catalog-{url_hash}-metadata.json"
 4730              is_valid = False
 4731              if not force_refresh and cache_file.exists() and cache_meta_file.exists():
 4732                  try:
 4733                      metadata = json.loads(cache_meta_file.read_text(encoding="utf-8"))
 4734                      cached_at = datetime.fromisoformat(metadata.get("cached_at", ""))
 4735                      if cached_at.tzinfo is None:
 4736                          cached_at = cached_at.replace(tzinfo=timezone.utc)
 4737                      age = (datetime.now(timezone.utc) - cached_at).total_seconds()
 4738                      is_valid = age < self.CACHE_DURATION
 4739                  except (
 4740                      json.JSONDecodeError,
 4741                      OSError,
 4742                      UnicodeError,
 4743                      ValueError,
 4744                      KeyError,
 4745                      TypeError,
 4746                      AttributeError,
 4747                  ):
 4748                      # Cache validity is best-effort: invalid/missing metadata
 4749                      # fields, an unreadable metadata file (permissions / disk),
 4750                      # a wrongly-encoded metadata file (written by a tool using

Expected behavior

Extension catalogs warn and skip a failed source and raise only when every selected source fails.

Actual behavior

ExtensionCatalog._fetch_single_catalog parses untrusted response bytes with json.loads but catches only urllib.errors.URLError and json.JSONDecodeError; an invalid-UTF-8 response raises UnicodeDecodeError, and _get_merged_extensions catches only ExtensionError, so a non-UTF-8 source aborts the whole extension catalog operation instead of warning and continuing.

Steps to reproduce

  1. Run the real ExtensionCatalog merge with one catalog whose body is invalid UTF-8 and one healthy catalog.
  2. Observe the returned list and the exception path.
  3. Compare with an invalid-JSON catalog as a control.

Suggested fix

Translate UnicodeDecodeError into the catalog error type, or catch it alongside JSONDecodeError.

Impact

One corrupt remote catalog disables all extension operations that depend on the merged catalog, including unrelated healthy catalogs.

主要語言
Python
星號
144k
分支
12.9k
平均合併
1 天 4 小時
30 天內合併 PR
181

環境準備

在 Codespaces 中開啟

在瀏覽器裡用你自己的 GitHub 帳號啟動這個專案的開發容器。

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

github/spec-kit 的其他 Issue

查看 github/spec-kit 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。