fix(dufe): 修复 base64 %3D 容错与 commit 判定 fail-open

ginkgo_sign_in.py:
- 顶部新增 import urllib.parse
- _b64_decode: 先 urllib.parse.unquote 再做 空格->+ 替换
  (iOS 快捷指令会把 base64 的 = 编码成 %3D,旧实现直接 binascii.Error;
    顺序必须是 unquote 在 空格->+ 之前,否则 %20 会被误当成被 form-urlencoded 吃掉的 +)
- commit 判定改为 fail-closed:要求 commit_info 是 dict 且 success is True,
  否则一律返回 msg="sign-in failed: <原因>";
  do_commit=false 时不检查 commit 字段,msg 仍为 "ok"
- reason 取值修正:非 dict 真值(字符串/数字)不再触发 AttributeError 500,
  统一回落到 "no commit result returned"
- parse_course_url 报错文案改为
  "URL is not a sign-in page (missing /signin/<signInId>/<type>): <url>"

文档:README 版本要点与 docs/部署与运维.md 变更记录同步
This commit is contained in:
RRRosmontis
2026-09-13 11:08:40 +08:00
parent a436bf58b7
commit 9ba194ae97
3 changed files with 33 additions and 17 deletions
+28 -16
View File
@@ -15,6 +15,7 @@ import json
import logging
import re
import sys
import urllib.parse
import uuid
try:
@@ -250,7 +251,8 @@ def parse_course_url(url):
r"/course/detail/(?P<courseId>\d+)/(?P<batchNo>[^/]+)/"
r"(?P<weekTimeId>\d+)/signin/(?P<signInId>\d+)/(?P<type>\d+)", url)
if not m:
raise ValueError("Cannot parse course URL: %s" % url)
raise ValueError(
"URL is not a sign-in page (missing /signin/<signInId>/<type>): %s" % url)
return m.groupdict()
@@ -462,13 +464,18 @@ def _b64_decode(value, field):
"""
Decode a base64 query/form parameter tolerantly.
Handles:
* '+' turned into ' ' by application/x-www-form-urlencoded
* trailing newline from `base64` CLI without -w0
* both standard (+/) and urlsafe (-_) alphabets
Handles, in order:
* '%3D' / '%2B' / '%20' -- iOS Shortcuts and some HTTP clients
URL-encode the base64 payload (notably the trailing '='), so we
percent-decode first.
* '+' turned into ' ' by application/x-www-form-urlencoded.
* trailing newline from `base64` CLI without -w0.
* both standard (+/) and urlsafe (-_) alphabets.
"""
v = (value or "").strip()
v = v.replace(" ", "+").replace("\r", "").replace("\n", "")
v = urllib.parse.unquote(value or "")
v = v.strip()
v = v.replace(" ", "+")
v = v.replace("\r", "").replace("\n", "")
if not v:
raise ValueError("Empty base64 for '%s'" % field)
try:
@@ -503,15 +510,20 @@ def ginkgostu_sign_in():
log.info("query start user=%s commit=%s", username[:2] + "***", do_commit)
try:
data = fetch_sign_in_info(username, password, course_url, do_commit=do_commit)
commit_info = data.get("commit") or {}
if do_commit and not commit_info.get("success", True):
log.warning("sign-in commit failed user=%s reason=%s",
username[:2] + "***", commit_info.get("message"))
return jsonify({
"code": 200,
"msg": "sign-in failed: %s" % commit_info.get("message", "unknown reason"),
"data": data,
})
if do_commit:
# Fail-closed: only an explicit success=true counts as signed in.
commit_info = data.get("commit")
if not isinstance(commit_info, dict) or commit_info.get("success") is not True:
reason = (commit_info.get("message") if isinstance(commit_info, dict) else None) or "no commit result returned"
log.warning("sign-in commit failed user=%s reason=%s",
username[:2] + "***", reason)
return jsonify({
"code": 200,
"msg": "sign-in failed: %s" % reason,
"data": data,
})
log.info("query ok user=%s code=%s", username[:2] + "***", data.get("signInCode"))
return jsonify({"code": 200, "msg": "ok", "data": data})
except AuthError as e: