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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user