码上焚香

Yahocen

利用 floccus 自建导航页,极致压缩效率空间

2024-05-10

Floccus 是一个开源的浏览器书签同步工具,它允许用户在不同的浏览器之间同步书签,并且支持将书签同步到自己的 Nextcloud 或 WebDAV 服务器上,并且不会受限于特定的浏览器或平台。

我发现无论使用何种同步方式,Floccus 最终都会在目标服务器上生成一个资源文件来存储书签信息。通过解析资源文件,我自建了一个导航页用来展示书签,这样我在任意设备上都可以很方便的访问书签了,哪怕不是自己的设备。

接下来我不会分享导航页源码等内容,仅对如何访问和解密 Floccus 资源文件进行说明。大家可以根据自己的需求解析自己的资源文件,实现想要的功能。

访问资源文件

资源文件的储存位置与 Floccus 的设置有关,可以在 Floccus 选项中的服务器详细信息部分查看。Floccus 提供了 Nextcloud、WebDAV、Git over HTTPS 和 Google Drive 的同步方式。个人推荐使用自建的 WebDAV 服务器,这样资源文件直接储存在个人服务器硬盘上,访问方便且同步效率高。

图片-hacy.png

如果使用其他同步方式或者资源文件未同步到本地服务器上,就需要通过HTTP或FTP协议使用API来访问资源文件。Nextcloud、WebDAV、Git over HTTPS和Google Drive这几种同步方式的API各不相同,需要自行研究。下面举例一个通过WebDAV访问资源文件的Java伪代码。


	public static void main(String[] args) throws Exception {
        //Webdav信息
        String url = "https://dav.jianguoyun.com/dav/bookmark/bookmarks.xbel";
        String username = "Webdav用户名";
        String password = "Webdav密码";

		//获取资源文件内容
        String xmlDocStr = getFileString(url, username, password);
        
		//TODO 实现其他逻辑
    }

	public static String getFileString(String url, String username, String password) {
        try {
            URL urlObj = new URL(url);
            HttpsURLConnection connection = (HttpsURLConnection) urlObj.openConnection();

            // Set basic authentication
            String userCredentials = username + ":" + password;
            String basicAuth = "Basic " + new String(java.util.Base64.getEncoder().encode(userCredentials.getBytes()));
            connection.setRequestProperty("Authorization", basicAuth);

            // Set request method
            connection.setRequestMethod("GET");

            // Disable SSL verification
            connection.setSSLSocketFactory((javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault());
            connection.setHostnameVerifier((hostname, session) -> true);

            // Get response
            int responseCode = connection.getResponseCode();
            if(responseCode != 200) {
                return null;
            }

            BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String inputLine;
            StringBuilder response = new StringBuilder();

            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
            in.close();
            return response.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

解密资源文件

这部分内容仅适用于在 Floccus 中设置过密码的资源文件。如果资源文件没有设置密码,则其内容就是明文的,可以直接解析。重要提醒:如果你设置了密码保护书签内容,务必确保不让其他人看到。同时,如果通过解密方式访问,请务必谨慎保管,以防泄密。

我参考了 Floccus 的源码文件:https://github.com/floccusaddon/floccus/blob/develop/src/lib/Crypto.ts。解密过程包括准备密钥、解码加密数据、提取初始化向量和密文部分、使用密钥和算法进行解密,最后将解密后的数据转换为可读格式。以下是解密资源文件的Java伪代码示例。

	public static void main(String[] args) throws Exception {
		//...
        //获取资源文件内容
        String xmlDocStr = getFileString(...);
        //书签文件加密信息
        String key = "Floccus中设置的密码";
        String salt = "bookmarks.xbel";
        //解密后输出到文件
        Files.write("D://...", salt), decryptAES(key, xmlDocStr, salt).getBytes());
    }

	private static final int ITERATIONS = 250000;
    private static final int IV_LENGTH = 16;

	public static SecretKey prepareKey(String passphrase, String salt) throws Exception {
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        PBEKeySpec spec = new PBEKeySpec(passphrase.toCharArray(), salt.getBytes(StandardCharsets.UTF_8), ITERATIONS, 256);
        SecretKey tmp = factory.generateSecret(spec);
        return new SecretKeySpec(tmp.getEncoded(), "AES");
    }

    public static String decryptAES(String key, String payload, String salt) throws Exception {
        SecretKey cryptoKey = prepareKey(key, salt);
        byte[] buffer = Base64.getDecoder().decode(payload);
        byte[] iv = Arrays.copyOfRange(buffer, 0, IV_LENGTH);
        byte[] ciphertext = Arrays.copyOfRange(buffer, IV_LENGTH, buffer.length);
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec spec = new GCMParameterSpec(128, iv);
        cipher.init(Cipher.DECRYPT_MODE, cryptoKey, spec);
        byte[] plaintextBytes = cipher.doFinal(ciphertext);
        return new String(plaintextBytes, StandardCharsets.UTF_8);
    }

解析资源文件

拿到明文资源文件后就只剩下解析了,Floccus 的资源文件格式目前分两种 XBEL 和 HTML,这也与你的设置有关。

更推荐使用XBEL格式,因为XBEL使用XML标记来表示书签数据,使得数据易于解析和处理。许多浏览器和书签管理工具都支持导入和导出XBEL格式的书签数据。以下是一段将XBEL解析为JSON格式的PHP代码,以便前端使用。

<?php
// 读取XML文件内容
$xmlString = file_get_contents("D:\\...\\bookmarks.xbel");
// 将文件内容转换为UTF-8编码
$xmlString = iconv(mb_detect_encoding($xmlString, mb_detect_order(), true), "UTF-8", $xmlString);
// 将XML字符串转换为SimpleXMLElement对象
$xml = simplexml_load_string($xmlString);
// 递归处理XML节点
function parseXml($xml) {
    $result = [];
    
    foreach ($xml->children() as $child) {
        if ($child->getName() == 'folder') {
            $node = [
                'type' => 'folder',
                'title' => (string) $child->title,
                'children' => parseXml($child)
            ];
            $result[] = $node;
        } elseif ($child->getName() == 'bookmark' && trim((string)$child->title) !== '') {
            $node = [
                'type' => 'bookmark',
                'title' => (string) $child->title,
                'href' => (string) $child['href']
            ];
            $result[] = $node;
        }
    }
    
    return $result;
}
// 解析XML节点转换为JSON格式
$json = json_encode(array(
	"title" => "根目录",
	"type" => "folder",
	"children" => parseXml($xml)
));

结束

通过以上方式,我们可以轻松地获取 Floccus 中保存的书签内容,并根据个人需求进行处理。如果你觉得这篇文章对你有帮助,那就太好了【你懂的】。