feat: EPUB阅读器搜索、注释、CFI模块及大书远距跳转优化
- 实现EPUB阅读器搜索功能及选中注释功能 - 优化CFI模块,修复代码审查发现的11个问题 - 实现大书远距目录跳转与后台补全优化方案 - 优化设置面板与章节运行时联动 - 重构及大量改进优化
This commit is contained in:
@@ -1,15 +1,9 @@
|
||||
// RDEPUBParser+Archive.swift
|
||||
// EPUB 归档解压与 container.xml 解析
|
||||
// 负责将 EPUB ZIP 文件解压到缓存目录(~/Library/Caches/ssreaderview-epub/),
|
||||
// 并解析 META-INF/container.xml 获取 OPF 文件路径。
|
||||
|
||||
import Foundation
|
||||
import ZIPFoundation
|
||||
|
||||
extension RDEPUBParser {
|
||||
/// 解析 container.xml,提取 OPF(Package Document)的相对路径
|
||||
/// - Parameter containerURL: container.xml 的文件路径
|
||||
/// - Returns: OPF 文件的相对路径(如 "OEBPS/content.opf")
|
||||
|
||||
func parseContainerRootFile(at containerURL: URL) throws -> String {
|
||||
guard let parser = XMLParser(contentsOf: containerURL) else {
|
||||
throw RDEPUBParserError.invalidXML(containerURL)
|
||||
@@ -30,10 +24,6 @@ extension RDEPUBParser {
|
||||
return rootFilePath
|
||||
}
|
||||
|
||||
/// 解压 EPUB ZIP 文件到缓存目录(幂等:已存在则跳过)
|
||||
/// 目录名由 slug + 文件大小 + 修改时间戳组成,避免冲突
|
||||
/// - Parameter epubURL: EPUB 文件路径
|
||||
/// - Returns: 解压目标目录 URL
|
||||
func extractArchiveIfNeeded(epubURL: URL) throws -> URL {
|
||||
let fileManager = FileManager.default
|
||||
let extractionURL = temporaryExtractionDirectory(for: epubURL)
|
||||
@@ -66,17 +56,12 @@ extension RDEPUBParser {
|
||||
return extractionURL
|
||||
}
|
||||
|
||||
/// 校验归档条目路径,防止路径穿越攻击
|
||||
/// - Parameters:
|
||||
/// - entryPath: 归档中的原始条目路径
|
||||
/// - extractionRoot: 解压根目录
|
||||
/// - Returns: 校验通过的目标 URL,非法路径返回 nil
|
||||
private func validatedExtractionDestination(for entryPath: String, extractionRoot: URL) -> URL? {
|
||||
// 拒绝绝对路径
|
||||
|
||||
if entryPath.hasPrefix("/") {
|
||||
return nil
|
||||
}
|
||||
// 拒绝包含 .. 的路径段
|
||||
|
||||
let components = entryPath.split(separator: "/", omittingEmptySubsequences: true)
|
||||
if components.contains(where: { $0 == ".." }) {
|
||||
return nil
|
||||
@@ -84,15 +69,13 @@ extension RDEPUBParser {
|
||||
let destinationURL = extractionRoot.appendingPathComponent(entryPath)
|
||||
let standardizedDest = destinationURL.standardizedFileURL.path
|
||||
let standardizedRoot = extractionRoot.standardizedFileURL.path
|
||||
// 确保最终路径位于解压根目录下
|
||||
|
||||
guard standardizedDest.hasPrefix(standardizedRoot) else {
|
||||
return nil
|
||||
}
|
||||
return destinationURL
|
||||
}
|
||||
|
||||
/// 计算 EPUB 的临时解压目录路径
|
||||
/// 路径格式:~/Library/Caches/ssreaderview-epub/{slug}-{fileSize}-{modifiedTimestamp}/
|
||||
func temporaryExtractionDirectory(for epubURL: URL) -> URL {
|
||||
let baseURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?
|
||||
.appendingPathComponent("ssreaderview-epub", isDirectory: true)
|
||||
@@ -106,14 +89,12 @@ extension RDEPUBParser {
|
||||
return baseURL.appendingPathComponent("\(slug)-\(fileSize)-\(signature)", isDirectory: true)
|
||||
}
|
||||
|
||||
/// 重置解析器状态(清除解压路径和出版物数据)
|
||||
func reset() {
|
||||
extractionRootURL = nil
|
||||
opfURL = nil
|
||||
resetPublicationState()
|
||||
}
|
||||
|
||||
/// 重置出版物相关状态(metadata、manifest、spine、目录)
|
||||
func resetPublicationState() {
|
||||
metadata = RDEPUBMetadata()
|
||||
manifest = [:]
|
||||
@@ -122,9 +103,8 @@ extension RDEPUBParser {
|
||||
}
|
||||
}
|
||||
|
||||
/// container.xml 的 SAX 解析代理,提取 <rootfile> 元素的 full-path 属性
|
||||
private final class ContainerXMLParserDelegate: NSObject, XMLParserDelegate {
|
||||
/// 解析得到的 OPF 文件相对路径
|
||||
|
||||
private(set) var rootFilePath: String?
|
||||
|
||||
func parser(
|
||||
|
||||
Reference in New Issue
Block a user