解析、还原、修改 Chromium Simple Cache 的 Java 类库。
支持两类缓存:
- Chromium / Chrome 的 HTTP 磁盘缓存(
Default/HTTP Cache) - Android WebView 的 V8 code cache(
Code Cache,键格式_key<url> \n)
库本身不接触文件系统:所有方法都是 byte[] 进、byte[] 出,读文件与写文件由调用方自行实现。
- 读取以索引文件(
index-dir/the-real-index)为准,索引是存活条目的权威列表 - 还原:从索引出发,取出每个条目的 key 与响应体(stream 1),并自动按
content-encoding解压(gzip / deflate) - 修改:替换响应体并保持条目结构有效,自动重算 CRC32、key_hash,同步 HTTP 元数据里的
content-length,并按content-encoding重新压缩 - 解析:完整解析条目头、stream 0/1、EOF、SHA256(key)、索引 pickle
- 附带 WebView V8 code cache 的键与布局编解码(
CodeCache) - 纯 Java,无第三方依赖
- JDK 21(
javac/jar)
simplecache-tool/
├── build.sh # 用 javac + jar 编译打包为 simplecache.jar
├── build.gradle # Gradle 构建 / JitPack 发布配置
├── settings.gradle
├── jitpack.yml # JitPack 构建使用的 JDK
├── gradlew / gradlew.bat / gradle/wrapper/ # Gradle Wrapper
├── src/com/cfks/simplecache/
│ ├── SimpleCacheLibrary.java # 对外 API:parse / restore / modify
│ ├── SimpleCacheFormat.java # 常量、小端读写、哈希、fake index、文件名
│ ├── SimpleCacheEntry.java # 条目文件 parse / serialize(stream 0/1)
│ ├── SimpleCacheIndex.java # the-real-index 的 parse / serialize
│ ├── HttpCacheMetadata.java # stream 0 中 content-length 与响应头的定位与修正
│ ├── ContentEncoding.java # content-encoding(gzip / deflate)的编解码
│ └── CodeCache.java # WebView V8 code cache 的键与布局
├── examples/
│ └── LibraryTest.java # 示例 / 冒烟测试(自己实现文件 I/O)
├── reference/chromium/ # 上游 Chromium 参考源码(BSD-3-Clause)
├── README.md
└── LICENSE
cd simplecache-tool
./build.sh产物为 simplecache.jar(无 Main-Class,作为类库使用)。
用 Gradle 构建:
cd simplecache-tool
./gradlew build项目已配置好 JitPack 发布(build.gradle + jitpack.yml + Gradle Wrapper),打 tag 后即可通过 JitPack 依赖。
Gradle:
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'com.github.caofangkuai:SimpleCache-Tool:1.0.0'
}Maven:
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.caofangkuai</groupId>
<artifactId>SimpleCache-Tool</artifactId>
<version>1.0.0</version>
</dependency>版本号也可以是某个 commit 短哈希,或分支快照 master-SNAPSHOT。
所有 API 位于 com.cfks.simplecache.SimpleCacheLibrary,签名均为 byte[] 入、byte[] 出。
import com.cfks.simplecache.SimpleCacheIndex;
import com.cfks.simplecache.SimpleCacheEntry;
import com.cfks.simplecache.SimpleCacheLibrary;
byte[] indexBytes = readAllBytes(indexPath); // index-dir/the-real-index
SimpleCacheIndex index = SimpleCacheLibrary.parseIndex(indexBytes);
byte[] entryBytes = readAllBytes(entryPath); // <hash>_0
SimpleCacheEntry entry = SimpleCacheLibrary.parseEntry(entryBytes);
// entry.key / entry.stream0 / entry.stream1 / entry.warnings ...辅助方法:
SimpleCacheLibrary.entryFileName(long hash):由索引里的 hash 得到文件名<hash>_0SimpleCacheLibrary.entryHash(byte[] key):由 key(如 URL)计算条目 hashSimpleCacheLibrary.buildFakeIndex():生成 24 字节的index(fake index)SimpleCacheLibrary.describeFakeIndex(byte[] bytes):校验并描述 fake index
还原以索引为准:遍历索引条目,调用方按 hash 读取对应的 <hash>_0,库负责解析。
import java.nio.file.*;
import java.util.List;
import com.cfks.simplecache.SimpleCacheLibrary;
Path cacheDir = Paths.get("/path/to/HTTP Cache");
byte[] indexBytes = Files.readAllBytes(cacheDir.resolve("index-dir/the-real-index"));
List<SimpleCacheLibrary.RestoredEntry> entries = SimpleCacheLibrary.restore(
indexBytes,
hash -> {
Path p = cacheDir.resolve(SimpleCacheLibrary.entryFileName(hash));
try {
return Files.exists(p) ? Files.readAllBytes(p) : null;
} catch (Exception e) {
return null;
}
});
for (SimpleCacheLibrary.RestoredEntry e : entries) {
String key = e.keyString(); // 例如 https://example.com/app.js
byte[] body = e.body; // 响应体,已按 content-encoding 解压
byte[] raw = e.rawBody; // 缓存中实际存储的字节(可能是 gzip)
byte[] meta = e.metadata; // stream 0,即响应元数据
if (e.hasProblems()) {
System.out.println(key + " -> " + e.warnings);
}
// 由调用方决定如何落盘
}RestoredEntry 字段:
| 字段 | 说明 |
|---|---|
hash |
索引中的 64 位条目 hash |
key |
条目 key(byte[]),HTTP 缓存里是 URL |
body |
响应体,已按 contentEncoding 解压(gzip / deflate 自动还原) |
rawBody |
stream 1 的原始字节,即缓存里实际存储的内容(可能仍是压缩态) |
metadata |
stream 0,响应元数据(HTTP 头等) |
contentEncoding |
条目声明的 content-encoding(如 gzip),无则为 null |
entry |
完整的 SimpleCacheEntry 对象,可能为 null(文件缺失时) |
warnings |
解析与校验过程中发现的问题 |
替换条目响应体,返回新的条目字节与新的索引字节,由调用方写回。
import com.cfks.simplecache.SimpleCacheFormat;
import com.cfks.simplecache.SimpleCacheLibrary;
byte[] entryBytes = Files.readAllBytes(entryPath);
byte[] newBody = /* 新的响应体,例如修改后的 JS */;
long nowInternal = SimpleCacheFormat.fromUnixMillis(System.currentTimeMillis());
SimpleCacheLibrary.ModifyResult result =
SimpleCacheLibrary.modify(indexBytes, entryBytes, newBody, nowInternal);
Files.write(entryPath, result.entryFile); // 新的 <hash>_0
Files.write(indexPath, result.indexFile); // 新的 the-real-index
Files.write(cacheDir.resolve("index"), SimpleCacheLibrary.buildFakeIndex()); // fake index也可以分开调用:
modifyEntryBody(byte[] entryFileBytes, byte[] newBody)-> 新的条目字节(自动按content-encoding重新压缩)modifyEntryBody(byte[] entryFileBytes, byte[] newBody, boolean autoContentEncoding)-> 关闭自动压缩时传false(newBody已是压缩字节)updateIndex(byte[] indexFileBytes, byte[] newEntryFileBytes, long lastUsedInternal)-> 新的索引字节
修改时会:
- 保留 key、文件头与 stream 0
- 重算 stream 0/1 的 CRC32、文件头的 key_hash
- 同步 HTTP 元数据(stream 0)里的
content-length,并调整其 pickle 长度字段 - 按条目声明的
content-encoding(gzip / deflate)重新压缩新响应体,使存储字节与响应头保持一致 - 刷新索引中的条目大小与 last-used 时间
content-encoding 的处理规则:还原时自动解压,body 为明文;修改时把传入的 newBody 视为明文并自动压缩。若你的 newBody 本身已是压缩数据,请使用 modifyEntryBody(..., false)。br 等不支持的编码会原样保留,不做转换。
examples/LibraryTest.java 演示了完整的“读索引 -> 还原 -> 修改 -> 再还原”流程(包含 gzip 条目的解码与再压缩),并自行完成所有文件 I/O。
编译与运行:
cd simplecache-tool
javac -cp simplecache.jar -d . examples/LibraryTest.java
java -cp simplecache.jar:. LibraryTest <cacheDir> <restoreOutDir> [modifyOutDir]参数说明:
cacheDir:Simple Cache 目录(HTTP 缓存或 WebView code cache)restoreOutDir:还原出的*.js输出目录modifyOutDir:可选,给出后会把一个.js条目与一个带content-encoding的条目修改后写到该目录,并再次还原校验
条目文件 <hash>_0 布局:
[SimpleFileHeader] 24 字节:u64 magic、u32 version、u32 key_length、u32 key_hash、u32 padding
[key] key_length 字节
[stream 1 data] 响应体
[SimpleFileEOF stream 1] 24 字节:u64 magic、u32 flags、u32 crc32、u32 stream_size、u32 padding
[stream 0 data] 响应元数据
[SHA256(key)] 32 字节,仅当 stream 0 EOF 带 HAS_KEY_SHA256
[SimpleFileEOF stream 0] 24 字节(文件末尾)
关键常量:
- 魔数:
INITIAL=0xfcfb6d1ba7725c30、FINAL=0xf4fa6f45970d41d8、INDEX=0x656e74657220796f - 版本:条目
5、索引9、fake index(kSimpleVersion)9 - 文件名 hash:
SHA1(key)前 8 字节按小端解释为 u64,格式化为%016x - 文件头
key_hash:base::PersistentHash(Paul Hsieh SuperFastHash) - 校验:
CRC32(java.util.zip)、SHA256(MessageDigest) - 索引:
base::Pickle,8 字节头(u32 payload_size + u32 crc)后接 4 字节对齐字段 index(fake index):u64 initial_magic、u32 version、u32 zero、u32 zero2、u32 encryption_status,共 24 字节
WebView code cache 的键为 _key<url> \n(可带上下文 key),stream 0 头部为 [response_time i64][data_size u32],随后按大小分为 INLINE / DEDICATED / INDIRECT 三种布局,详见 CodeCache。
reference/chromium/ 下为上游 Chromium 参考源码,来源为 Chromium 项目,遵循 BSD-3-Clause,版权归 The Chromium Authors 所有。主要参考文件:
net/disk_cache/simple/simple_entry_format.hnet/disk_cache/simple/simple_index.ccnet/disk_cache/simple/simple_index_file.ccnet/disk_cache/simple/simple_synchronous_entry.ccnet/disk_cache/simple/simple_util.ccnet/disk_cache/simple/simple_version_upgrade.ccnet/disk_cache/simple/simple_backend_impl.cccontent/browser/code_cache/generated_code_cache.cc
MIT License,详见 LICENSE。