From 77238c16e2f4ddfcb6a0a0f5c7d1656a1d37f50e Mon Sep 17 00:00:00 2001 From: Horis <8674809+821938089@users.noreply.github.com> Date: Fri, 28 Jun 2024 12:20:18 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE/01-bugReport.yml | 2 +- .../io/legado/app/model/localBook/EpubFile.kt | 7 +- .../io/legado/app/utils/StringExtensions.kt | 4 +- .../epublib/epub/PackageDocumentReader.java | 18 ++- .../me/ag2s/epublib/util/URLEncodeUtil.java | 114 ++++++++++++++++++ 5 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 modules/book/src/main/java/me/ag2s/epublib/util/URLEncodeUtil.java diff --git a/.github/ISSUE_TEMPLATE/01-bugReport.yml b/.github/ISSUE_TEMPLATE/01-bugReport.yml index 956cc6cde..828088e36 100644 --- a/.github/ISSUE_TEMPLATE/01-bugReport.yml +++ b/.github/ISSUE_TEMPLATE/01-bugReport.yml @@ -11,7 +11,7 @@ body: required: true - label: 最新[测试版](https://github.com/gedoor/legado/actions/workflows/test.yml)依然存在此问题 / Latest beta app does not work required: true - - label: 此问题和Xposed、Lsposed、Magisk、手机主题、浏览器插件等无关 / Make sure your machine is not touched by hook frameworks, plugins etc + - label: 此问题和Xposed、Lsposed、Magisk、手机主题、浏览器插件、无障碍服务等无关 / Make sure your machine is not touched by hook frameworks, plugins, accessibility etc required: true - type: textarea diff --git a/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt b/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt index 69bc4fe1a..77a37b1cd 100644 --- a/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt +++ b/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt @@ -10,6 +10,7 @@ import io.legado.app.data.entities.BookChapter import io.legado.app.help.book.BookHelp import io.legado.app.utils.FileUtils import io.legado.app.utils.HtmlFormatter +import io.legado.app.utils.encodeURI import io.legado.app.utils.isXml import io.legado.app.utils.printOnDebug import me.ag2s.epublib.domain.EpubBook @@ -252,8 +253,10 @@ class EpubFile(var book: Book) { } } bodyElement.select("img").forEach { - val src = it.attr("src") - it.attr("src", URI(res.href).resolve(src).toString()) + val src = it.attr("src").encodeURI() + val href = res.href.encodeURI() + val resolvedHref = URLDecoder.decode(URI(href).resolve(src).toString(), "UTF-8") + it.attr("src", resolvedHref) } return bodyElement } diff --git a/app/src/main/java/io/legado/app/utils/StringExtensions.kt b/app/src/main/java/io/legado/app/utils/StringExtensions.kt index e5f262f5b..0bb75668f 100644 --- a/app/src/main/java/io/legado/app/utils/StringExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/StringExtensions.kt @@ -7,12 +7,13 @@ import android.icu.text.Collator import android.icu.util.ULocale import android.net.Uri import android.text.Editable +import cn.hutool.core.net.URLEncodeUtil import io.legado.app.constant.AppPattern import io.legado.app.constant.AppPattern.dataUriRegex import java.io.File import java.lang.Character.codePointCount import java.lang.Character.offsetByCodePoints -import java.util.* +import java.util.Locale import java.util.regex.Pattern fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim() @@ -132,3 +133,4 @@ fun String.escapeRegex(): String { return replace(AppPattern.regexCharRegex, "\\\\$0") } +fun String.encodeURI(): String = URLEncodeUtil.encodeQuery(this) diff --git a/modules/book/src/main/java/me/ag2s/epublib/epub/PackageDocumentReader.java b/modules/book/src/main/java/me/ag2s/epublib/epub/PackageDocumentReader.java index 0b019dda6..9f52e4780 100644 --- a/modules/book/src/main/java/me/ag2s/epublib/epub/PackageDocumentReader.java +++ b/modules/book/src/main/java/me/ag2s/epublib/epub/PackageDocumentReader.java @@ -35,6 +35,7 @@ import me.ag2s.epublib.domain.Spine; import me.ag2s.epublib.domain.SpineReference; import me.ag2s.epublib.util.ResourceUtil; import me.ag2s.epublib.util.StringUtil; +import me.ag2s.epublib.util.URLEncodeUtil; /** * Reads the opf package document as defined by namespace http://www.idpf.org/2007/opf @@ -108,6 +109,7 @@ public class PackageDocumentReader extends PackageDocumentBase { for (int i = 0; i < originItemElements.getLength(); i++) { Element itemElement = (Element) originItemElements.item(i).cloneNode(false); String href = DOMUtil.getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.href); + href = URLEncodeUtil.encode(href); String resolvedHref = packagePath.resolve(href).toString(); itemElement.setAttribute("href", resolvedHref); fixedElements.add(itemElement); @@ -451,9 +453,9 @@ public class PackageDocumentReader extends PackageDocumentBase { OPFTags.item, OPFAttributes.id, coverResourceId, OPFAttributes.href); if (StringUtil.isNotBlank(coverHref)) { - result.add(packagePath.resolve(coverHref).toString()); + result.add(resolvePath(packagePath, coverHref)); } else { - String resolved = packagePath.resolve(coverResourceId).toString(); + String resolved = resolvePath(packagePath, coverResourceId); result.add( resolved); // maybe there was a cover href put in the cover id attribute } @@ -464,11 +466,21 @@ public class PackageDocumentReader extends PackageDocumentBase { OPFTags.reference, OPFAttributes.type, OPFValues.reference_cover, OPFAttributes.href); if (StringUtil.isNotBlank(coverHref)) { - result.add(packagePath.resolve(coverHref).toString()); + result.add(resolvePath(packagePath, coverHref)); } return result; } + private static String resolvePath(URI parentPath, String href) { + href = URLEncodeUtil.encode(href); + String resolved = parentPath.resolve(href).toString(); + try { + return URLDecoder.decode(resolved, Constants.CHARACTER_ENCODING); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + /** * Finds the cover resource in the packageDocument and adds it to the book if found. * Keeps the cover resource in the resources map diff --git a/modules/book/src/main/java/me/ag2s/epublib/util/URLEncodeUtil.java b/modules/book/src/main/java/me/ag2s/epublib/util/URLEncodeUtil.java new file mode 100644 index 000000000..f6404bb61 --- /dev/null +++ b/modules/book/src/main/java/me/ag2s/epublib/util/URLEncodeUtil.java @@ -0,0 +1,114 @@ +package me.ag2s.epublib.util; + +import java.io.CharArrayWriter; +import java.nio.charset.Charset; +import java.util.BitSet; +import java.util.Objects; + +import kotlin.text.Charsets; + +public class URLEncodeUtil { + + static BitSet dontNeedEncoding; + static final int caseDiff = ('a' - 'A'); + + static { + dontNeedEncoding = new BitSet(256); + int i; + for (i = 'a'; i <= 'z'; i++) { + dontNeedEncoding.set(i); + } + for (i = 'A'; i <= 'Z'; i++) { + dontNeedEncoding.set(i); + } + for (i = '0'; i <= '9'; i++) { + dontNeedEncoding.set(i); + } + String reversed = "+-_.$:()!*@&#,[]"; + for (i = 0; i < reversed.length(); i++) { + dontNeedEncoding.set(reversed.charAt(i)); + } + } + + public static String encode(String s, Charset charset) { + Objects.requireNonNull(charset, "charset"); + + boolean needToChange = false; + StringBuilder out = new StringBuilder(s.length()); + CharArrayWriter charArrayWriter = new CharArrayWriter(); + + for (int i = 0; i < s.length(); ) { + int c = s.charAt(i); + //System.out.println("Examining character: " + c); + if (dontNeedEncoding.get(c)) { + //System.out.println("Storing: " + c); + out.append((char) c); + i++; + } else { + // convert to external encoding before hex conversion + do { + charArrayWriter.write(c); + /* + * If this character represents the start of a Unicode + * surrogate pair, then pass in two characters. It's not + * clear what should be done if a byte reserved in the + * surrogate pairs range occurs outside of a legal + * surrogate pair. For now, just treat it as if it were + * any other character. + */ + if (c >= 0xD800 && c <= 0xDBFF) { + /* + System.out.println(Integer.toHexString(c) + + " is high surrogate"); + */ + if ((i + 1) < s.length()) { + int d = (int) s.charAt(i + 1); + /* + System.out.println("\tExamining " + + Integer.toHexString(d)); + */ + if (d >= 0xDC00 && d <= 0xDFFF) { + /* + System.out.println("\t" + + Integer.toHexString(d) + + " is low surrogate"); + */ + charArrayWriter.write(d); + i++; + } + } + } + i++; + } while (i < s.length() && !dontNeedEncoding.get((c = (int) s.charAt(i)))); + + charArrayWriter.flush(); + String str = new String(charArrayWriter.toCharArray()); + byte[] ba = str.getBytes(charset); + for (int j = 0; j < ba.length; j++) { + out.append('%'); + char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16); + // converting to use uppercase letter as part of + // the hex value if ch is a letter. + if (Character.isLetter(ch)) { + ch -= caseDiff; + } + out.append(ch); + ch = Character.forDigit(ba[j] & 0xF, 16); + if (Character.isLetter(ch)) { + ch -= caseDiff; + } + out.append(ch); + } + charArrayWriter.reset(); + needToChange = true; + } + } + + return (needToChange ? out.toString() : s); + } + + public static String encode(String s) { + return encode(s, Charsets.UTF_8); + } + +}