Horis 2 лет назад
Родитель
Сommit
3f68ed291b

+ 1 - 1
.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

+ 5 - 2
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
     }

+ 3 - 1
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)

+ 15 - 3
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

+ 114 - 0
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);
+    }
+
+}