Coding 的痕迹

一位互联网奔跑者的网上日记

0%

Cookie 默认的 Path 是什么

在做爬虫的时候,发现登录后的请求始终只带部分 Cookie。如,登录的 Path为 /unifri-flow/login,Response 中携带的 headers 为:

1
2
Set-Cookie: No=序号; Max-Age=691200; Expires=Mon, 24-Jan-2022 04:55:42 GMT
Set-Cookie: SESSION=Yjk3YjI0NGUtMjhiZi00NzExLTg2ZTItOTIyYzM3ZDE1Njk1; Path=/unifri-flow/; HttpOnly; SameSite=Lax

在后续请求 /unifri-flow/WF/Comm/ProcessRequest.do 中,Cookie 字段只能见到 SESSION 而缺失了 No,导致整个响应的返回为空。但是在响应头中,服务器又 Set-Cookie 了一遍。

找问题

调试发现 Dio 库的行为是使用了路径 /unifri-flow/login 作为登录响应的 Cookie 路径:

Screenshot

根据我的理解,当 Path 字段为空时,用户代理(浏览器)默认使用 /,但。MDN 上没有相关解释,找到 RFC 文档(RFC6265):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
4.1.2.4.  The Path Attribute

The scope of each cookie is limited to a set of paths, controlled by
the Path attribute. If the server omits the Path attribute, the user
agent will use the "directory" of the request-uri's path component as
the default value. (See Section 5.1.4 for more details.)

The user agent will include the cookie in an HTTP request only if the
path portion of the request-uri matches (or is a subdirectory of) the
cookie's Path attribute, where the %x2F ("/") character is
interpreted as a directory separator.

Although seemingly useful for isolating cookies between different
paths within a given host, the Path attribute cannot be relied upon
for security (see Section 8).

按照 RFC 的解释,当 Path 部分缺失时,用户代理应该默认使用请求资源的“文件夹”作为 Path 部分。也就是说,应当使用 /unifri-flow/ 作为默认 Path。使用 Firefox 测试,检查调试工具中的 Storage,只存在 Path 为 /unifri-flow/ 的 Cookie,行为正常。说明 DefaultCookieJar 的实现存在问题。

找到保存 Cookie 的函数 saveFromResponse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@override
Future<void> saveFromResponse(Uri uri, List<Cookie> cookies) async {
for (final cookie in cookies) {
var domain = cookie.domain;
String path;
var index = 0;
// Save cookies with "domain" attribute
if (domain != null) {
if (domain.startsWith('.')) {
domain = domain.substring(1);
}
path = cookie.path ?? '/';
} else {
index = 1;
// Save cookies without "domain" attribute
// 注意,问题出在这里.
path = cookie.path ?? (uri.path.isEmpty ? '/' : uri.path);
domain = uri.host;
}
var mapDomain =
_cookies[index][domain] ?? <String, Map<String, dynamic>>{};
mapDomain = mapDomain.cast<String, Map<String, dynamic>>();

final map = mapDomain[path] ?? <String, dynamic>{};
map[cookie.name] = SerializableCookie(cookie);
if (_isExpired(map[cookie.name])) {
map.remove(cookie.name);
}
mapDomain[path] = map.cast<String, SerializableCookie>();
_cookies[index][domain] =
mapDomain.cast<String, Map<String, SerializableCookie>>();
}
}

第 17 行,当 cookie.path 为空时,默认使用了 uri.path ,而没有取 uri.path 所在的目录,导致了此问题。在 issue 列表没有找到类似问题,赶紧提一个(这里)。

解决方案

uri.path 最后一个 / 后的内容删除即可。

1
2
3
4
5
6
7
String uriParentPath() {
if (uri.path != '/') {
final end = uri.path.lastIndexOf('/');
return uri.path.substring(0, end);
}
return uri.path;
}

欢迎关注我的其它发布渠道