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:
@@ -121,7 +121,7 @@ for p in 9005 9006 9007 9008; do timeout 3 bash -c "cat < /dev/null > /dev/tcp/<
|
||||
|
||||
## 版本要点
|
||||
|
||||
- **2026-09-13**:新增 **dufe 银杏签到码查询服务**(`source/dufe/ginkgostu_sign_in/ginkgo_sign_in.py`,Flask + waitress 16 线程,`127.0.0.1:9005`,端点 `GET|POST /dufe/ginkgostu_sign_in`),并在聚合层 `main.py` 注册该路由——为此新增 `proxy_forward()`(保留请求方法与请求体,原 `proxy()` 保持固定 GET 不变,零改动既有行为)
|
||||
- **2026-09-13**:新增 **dufe 银杏签到码查询服务**(`source/dufe/ginkgostu_sign_in/ginkgo_sign_in.py`,Flask + waitress 16 线程,`127.0.0.1:9005`,端点 `GET|POST /dufe/ginkgostu_sign_in`),并在聚合层 `main.py` 注册该路由——为此新增 `proxy_forward()`(保留请求方法与请求体,原 `proxy()` 保持固定 GET 不变,零改动既有行为)。同期修复该服务两处缺陷:base64 参数补 `urllib.parse.unquote`(兼容 iOS 快捷指令的 `%3D` 编码),`commit` 判定改为 fail-closed(须 `commit.success is True` 才算签到成功)
|
||||
- **2026-09-12**
|
||||
- 端口重排 `9888/9088/19990 → 9008/9007/9006`,全部仅绑 `127.0.0.1`;pixiv 由 `0.0.0.0:9088` 收口
|
||||
- 主入口 `main.py` 从 `source/openapi/` 上移到 `source/`,systemd `WorkingDirectory` 同步为 `source/`
|
||||
|
||||
@@ -122,6 +122,10 @@
|
||||
- 新增 **dufe 银杏签到码查询服务**:`source/dufe/ginkgostu_sign_in/ginkgo_sign_in.py` + `ginkgo_sign_in.service`(`127.0.0.1:9005`,Flask + waitress 16 线程,端点 `GET|POST /dufe/ginkgostu_sign_in`)
|
||||
- 在聚合层 `main.py` 注册该端点:新增 `DUFE_BACKEND` 与 `proxy_forward()`(保留请求方法/请求体;原 `proxy()` 固定 GET 行为未改动)
|
||||
- nginx **无需改动**:`location /` 兜底已把 `/dufe/...` 转发给 `127.0.0.1:9008`
|
||||
- 修复 `ginkgo_sign_in.py` 两处缺陷:
|
||||
- `_b64_decode` 先做 `urllib.parse.unquote`(iOS 快捷指令会把 base64 的 `=` 编码成 `%3D`,旧实现直接 `binascii.Error`);顺序为先 unquote、再 `" "→"+"`
|
||||
- `commit` 判定由 fail-open 改为 fail-closed:须 `isinstance(dict)` 且 `success is True`,否则一律判失败并返回 `msg = "sign-in failed: <原因>"`
|
||||
- `parse_course_url` 报错文案改为 `URL is not a sign-in page (missing /signin/<signInId>/<type>): <url>`
|
||||
|
||||
**2026-09-12**
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
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] + "***", commit_info.get("message"))
|
||||
username[:2] + "***", reason)
|
||||
return jsonify({
|
||||
"code": 200,
|
||||
"msg": "sign-in failed: %s" % commit_info.get("message", "unknown reason"),
|
||||
"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