FirefoxとLunascapeのブックマーク情報のリバースエンジニアリングのポエム

#概要

- Firefoxのブックマークのjsonファイルを解析した
- Lunascapeのブックマークのファイルを解析した
- 自作でLunascapeのブックマークをFirefoxのブックマークへ移行するスクリプトを書いた
- なぜか郷愁に浸った

#FireFoxのブックマーク解析

とある事情があり，Firefoxのブックマークのjsonファイルを解析する話がでてきました．
Firefoxぐらいオープンなものだと，jsonファイルの仕様書とかあるだろう．と高をくくっていたのですが，思ったよりない．むしろ，ユーザー向けのブックマークのバックアップ方法のみで，そのような情報がほとんど出てきませんでした．そこで，FireFoxのブックマーク情報のリバースエンジニアリングを試みてみました．（あまり気は乗らなかったのですが・・・理由は後述）

```json
{
    "children": [
        {
            "children": [
                {
                    "annos": [
                        {
                            "expires": 4, 
                            "flags": 0, 
                            "name": "Places/SmartBookmark", 
                            "value": "RecentlyBookmarked"
                        }
                    ], 
                    "dateAdded": 1441383197588000, 
                    "guid": "OoLxpYMFn8bL", 
                    "id": 13, 
                    "index": 0, 
                    "lastModified": 1441383197861000, 
                    "title": "最近ブックマークしたページ", 
                    "type": "text/x-moz-place", 
                    "uri": "place:folder=BOOKMARKS_MENU&folder=UNFILED_BOOKMARKS&folder=TOOLBAR&queryType=1&sort=12&maxResults=10&excludeQueries=1"
                }, 
                {
                    "annos": [
                        {
                            "expires": 4, 
                            "flags": 0, 
                            "name": "Places/SmartBookmark", 
                            "value": "RecentTags"
                        }
                    ], 
                    "dateAdded": 1441383197870000, 
                    "guid": "CgxZYOj-uzHz", 
                    "id": 14, 
                    "index": 1, 
                    "lastModified": 1441383198759000, 
                    "title": "最近付けたタグ", 
                    "type": "text/x-moz-place", 
                    "uri": "place:type=6&sort=14&maxResults=10"
                }, 
                {
                    "dateAdded": 1441383198776000, 
                    "guid": "8wGuYyizMCCH", 
                    "id": 15, 
                    "index": 2, 
                    "lastModified": 1441383198776000, 
                    "type": "text/x-moz-place-separator"
                }, 
                {
                    "children": [
                        {
                            "dateAdded": 1441383197363000, 
                            "guid": "iTGC3U2ZVqDx", 
                            "iconuri": "http://www.mozilla.org/2005/made-up-favicon/0-1441383197365", 
                            "id": 8, 
                            "index": 0, 
                            "lastModified": 1441383197363000, 
                            "title": "ヘルプとチュートリアル", 
                            "type": "text/x-moz-place", 
                            "uri": "https://www.mozilla.org/ja/firefox/help/"
                        }, 
                        {
                            "dateAdded": 1441383197363000, 
                            "guid": "1-gn5b3uAXDE", 
                            "iconuri": "http://www.mozilla.org/2005/made-up-favicon/1-1441383197366", 
                            "id": 9, 
                            "index": 1, 
                            "lastModified": 1441383197363000, 
                            "title": "Firefox をカスタマイズしてみよう", 
                            "type": "text/x-moz-place", 
                            "uri": "https://www.mozilla.org/ja/firefox/customize/"
                        }, 
                        {
                            "dateAdded": 1441383197363000, 
                            "guid": "AHnNTOSM-fZx", 
                            "iconuri": "http://www.mozilla.org/2005/made-up-favicon/2-1441383197367", 
                            "id": 10, 
                            "index": 2, 
                            "lastModified": 1441383197363000, 
                            "title": "Mozilla のコミュニティ", 
                            "type": "text/x-moz-place", 
                            "uri": "https://www.mozilla.org/ja/contribute/"
                        }, 
                        {
                            "dateAdded": 1441383197363000, 
                            "guid": "AHVzJ-ZtV-yS", 
                            "iconuri": "http://www.mozilla.org/2005/made-up-favicon/3-1441383197369", 
                            "id": 11, 
                            "index": 3, 
                            "lastModified": 1441383197363000, 
                            "title": "Mozilla について", 
                            "type": "text/x-moz-place", 
                            "uri": "https://www.mozilla.org/ja/about/"
                        }
                    ], 
                    "dateAdded": 1441383197363000, 
                    "guid": "rL5DV7pow3gS", 
                    "id": 7, 
                    "index": 3, 
                    "lastModified": 1441383197363000, 
                    "title": "Mozilla Firefox", 
                    "type": "text/x-moz-place-container"
                }
            ], 
            "dateAdded": 1441383195902000, 
            "guid": "menu________", 
            "id": 2, 
            "index": 0, 
            "lastModified": 1441383198776000, 
            "root": "bookmarksMenuFolder", 
            "title": "ブックマークメニュー", 
            "type": "text/x-moz-place-container"
        }, 
        {
            "annos": [
                {
                    "expires": 4, 
                    "flags": 0, 
                    "name": "bookmarkProperties/description", 
                    "value": "このフォルダの中身がブックマークツールバーに表示されます"
                }
            ], 
            "children": [        /*注目ポイント*/
                {
                    "children": [
                        {
                            "dateAdded": 1493880118862000, 
                            "guid": "a-AiqAeY9mo4", 
                            "id": 26, 
                            "index": 0, 
                            "lastModified": 1493880125200000, 
                            "title": "Yahoo", 
                            "type": "text/x-moz-place",  
                            "uri": "http://yahoo.co.jp/"
                        }, 
                        {
                            "dateAdded": 1493880098865000, 
                            "guid": "5g8CK6cHdTHr", 
                            "id": 25, 
                            "index": 1, 
                            "lastModified": 1493880110976000, 
                            "title": "Google", 
                            "type": "text/x-moz-place", 
                            "uri": "http://google.co.jp/"
                        }
                    ], 
                    "dateAdded": 1493880093484000, 
                    "guid": "DHZHObvuCJ5X", 
                    "id": 24, 
                    "index": 0, 
                    "lastModified": 1493880118862000, 
                    "title": "hoge",  
                    "type": "text/x-moz-place-container"
                }, 
                {
                    "annos": [
                        {
                            "expires": 4, 
                            "flags": 0, 
                            "name": "Places/SmartBookmark", 
                            "value": "MostVisited"
                        }
                    ], 
                    "dateAdded": 1441383197448000, 
                    "guid": "9frqLvaL8FTw", 
                    "id": 12, 
                    "index": 1, 
                    "lastModified": 1441383197564000, 
                    "title": "よく見るページ", 
                    "type": "text/x-moz-place", 
                    "uri": "place:sort=8&maxResults=10"
                }, 
                {
                    "dateAdded": 1441383197363000, 
                    "guid": "fZ7Ku4oKLW-s", 
                    "id": 6, 
                    "index": 2, 
                    "lastModified": 1441383197363000, 
                    "title": "Firefox を使いこなそう", 
                    "type": "text/x-moz-place", 
                    "uri": "https://www.mozilla.org/ja/firefox/central/"
                }
            ], 
            "dateAdded": 1441383195902000, 
            "guid": "toolbar_____", 
            "id": 3, 
            "index": 1, 
            "lastModified": 1493880113691000, 
            "root": "toolbarFolder", 
            "title": "ブックマークツールバー", 
            "type": "text/x-moz-place-container"
        }, 
        {
            "dateAdded": 1441383195902000, 
            "guid": "unfiled_____", 
            "id": 5, 
            "index": 3, 
            "lastModified": 1441383197347000, 
            "root": "unfiledBookmarksFolder", 
            "title": "未整理のブックマーク", 
            "type": "text/x-moz-place-container"
        }
    ], 
    "dateAdded": 1441383195902000, 
    "guid": "root________", 
    "id": 1, 
    "index": 0, 
    "lastModified": 1493880080910000, 
    "root": "placesRoot", 
    "title": "", 
    "type": "text/x-moz-place-container"
}
```

大きなjsonですがブックマークの情報を見るだけなら大したことはありません．
まず初めにこのブックマークですが

```
フォルダhoge
 - タイトル Yahoo   URL  http://yahoo.co.jp
 - タイトル Google  URL　http://google.co.jp
```

という非常に簡単な状態を作り，ダンプしました．そこから，構造を簡単に予測することが出来ます．
フォルダの情報は

```py
{
    "dateAdded": 1493880093484000,           #追加日時
    "guid": "DHZHObvuCJ5X",                  #ユニークなid?
    "id": 24,                                #ユニークなid?
    "index": 0,                              #表示順?
    "lastModified": 1493880118862000,        #変更日時
    "title": "hoge",                         #フォルダのタイトル
    "type": "text/x-moz-place-container"     #フォルダを表す固定値
    "children" : []                          #フォルダに入っているブックマーク・フォルダ
}
```

大体こんな感じ．ブックマークの情報は

```py
{
   "dateAdded": 1493880098865000,            #追加日時
   "guid": "5g8CK6cHdTHr",                   #ユニークなid?
   "id": 25,                                 #ユニークなid?
   "index": 1,                               #表示順?
   "lastModified": 1493880110976000,         #変更日時
   "title": "Google",                        #ブックマークののタイトル
   "type": "text/x-moz-place",               #ブックマークを表す固定値
   "uri": "http://google.co.jp/"             #ブックマークされたURL
}
```

`/*注目ポイント*/`とjsonに書いてある場所がありますが，その構造から，フォルダやブックマークの構造が分かります．

#なぜ解析することになったか？

実は昔WindowsXPのマシンを使っていた時代に，[Lunascape](http://www.lunascape.jp/)というブラウザを使っていました．あまり他の人が使っているという話は聞かない，ブラウザではマイナーな部類だと思います．私は，その時代に勝手に親のPCにLunascapeを入れて，ネットをしてたのですが，親も，このLunascapeを愛用していました．

　私はChromeが出て以降，ほぼずっとChromeを使い続けていますが，PCに疎い親はPCを買い替えても，Lunascapeをインストールし愛用し続けていました．しかし，時が流れるにつれ，Lunascapeの保守も怪しい感じになっているようです．ここ2年ぐらいは，クラッシュがひどく，ブックマークが破損することが多々ありました．そして，帰省のたびに，Lunascapeのブックマークの復旧をする．ということをやっていました．

　そして，先日のGWにも，クラッシュしたLunascapeのブックマークを直してほしいという依頼が来たので，直していたのですが，いつもの方法でどうも直らない・・・まぁ今まで直ってたのが奇跡だった気もするんですが．そんなわけで，Lunascapeのブックマークファイルをリバースエンジニアリングを行い，Firefoxへ移行するというタスクが生まれたわけです．

#Lunascapeのブックマーク解析

とりあえず，Lunascapeのブックマークのファイル位置を探します．これは割とネットに転がっているので難なく見つかります．

[Lunascape6のお気に入り](https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1435769294)

そのお気に入りファイルは大体default2.ld2というファイル名で存在します．

とりあえず，そのファイルを開いてみます

```
<?xml version="1.0" encoding="UTF-8"?>
<root><d s="FavoriteDocument" v="1"><o>{1282ccdf-21a8-4c9f-12f410}</o><o k="FavoriteItemTree" t="SerializeInfo"><d s="LBMRootItem" v="1"><o k="Item" t="SerializeInfo"><d s="LBMRootItemData" v="1"><o>標準</o><o></o><o>20041118T174536</o><o>3</o><o>2
2
3
4
5
8
5
2
1
4
1
5
2
0
6
3
5
2
6
2
0
0
4
0
0
4
3
4
2
14</o><o>20170211T014325</o><o>0</o><o>2</o><o>{c33d7c6d-6c1f-4bd6-12fc1c}</o><o>20151202T161701</o><o>2</o><o>default</o><o></o><o>20051007T030819</o><o>200</o></d></o><o k="TreeItem" t="SerializeInfo"><d s="LBMFolderItem" v="1"><o k="Item" t="SerializeInfo"><d s="LBMFolderItemData" v="1"><o>t</o><o>Lunascape</o><o>f</o><o></o><o>20050825T063641</o><o>1
(省略)
```

・・・・・・・・その昔，私も中身なんだろう？と思って開いたことがあったんですが，**なかなか吐き気のするxmlファイル**なんですよ．
xmlの体裁が若干怪しいのは目をつむる．よくわからないタイミングでxmlのタグの変な位置で改行されてたりするのは受容する．

**なんかxml内部に意味不明な改行された数列があるのはなんなんだ？**

そうこれが実は一番やりたくなかった理由です．そして，このファイルがLunascape自身が読み込めなくなった．ということは**普通のxmlパーサーライブラリで読めない可能性すらある**．
しかも，普通のxmlパーサーライブラリで読んだところで，特殊な改行の意味のある数列を出されると，**ライブラリをいれたことによって読めないデータが出てくる可能性がある．**

そう．**非常にヤバい臭いのするファイルデータ&データ構造です．**

しかし，親には「データは復旧できないかもしれない」と言ってあるので，最悪できなくてもよい．
あとフォルダ構造をきれいに作れれば，良いのでそこそこ雑なパースでもよいだろう．ということで，ゴリゴリとシェルを書きました．

```bash
cat default.ld2  | nkf -Lwu | grep "^[0-9]*</o>.*[0-9]" \
    | sed "s/<[^>]*>/|/g" | sed "s/{[^}]*}//g" | sed "s/20[0-9]*T[0-9]*//g" \
    | sed "s/|[tsf]|//g" | sed "s/|[0-9][0-9]*|//g" | sed "s/^[0-9]*|*//g" \
    | sed "s/[0-9]$//g" | tr "|" "\n" | grep -v '^\s*$' | grep -v "^[0-9]\s*$" \
    | sed "s/\(http.*\)/\1\n/g" > bookmark.txt
```

細かくは解説しませんが，主に

- 不要な`</o>`の削除 
- 不要な{xxxx-xxxx-xxxx}の削除
- 不要な日時情報20041118T174536の削除
- よくわからないt,s,fというバリューの削除
- 数字だけの行の削除
- 空行の削除
- "\n"や区切り文字を全て"|"に変更
- URL部の切り出し

を行いました．そうすると出てくるデータが

```
default
Lunascape
default
Lunascape公式サイト
http://www.lunascape.jp/

LunaTV
http://www.luna.tv/

オンラインヘルプ
http://help.lunascape.tv/LunascapeHelp-ja/

サポートセンター
http://lunascapesc.lunascape.jp/
```

とこんな感じ．ここでdefaultというのが若干のポイントで，defaultという行が出てきた次の行が「フォルダの名前」(Lunascape)になっています．そして，日本語文字列の後，URLが来ているのがブックマークのタイトル(LunaTV)とそのURL( http://www.luna.tv/ )となっています．

というわけで，Lunascapeのブックマークのデータのパースと，理解が出来たので，こいつを読み込んで，Firefox用のjsonに書き換えるプログラムを書きます．

```py:bookmark.py
#coding:utf-8
import sys
import datetime

class folder(object):
    def __init__(self,_id,guid,index,title):
        self._id = _id
        self.guid = guid
        self.index = index
        self.title = title
        self._type = "text/x-moz-place-container"
        self.children = []

    def dump(self):
        now = datetime.datetime.now()
        unix = int(now.strftime('%s'))

        return {
            "dateAdded" : unix,
            "guid" : self.guid,
            "id"   : self._id,
            "index" : self.index,
            "lastModified" : unix,
            "title" : self.title,
            "type" : self._type,
            "children" : [
                c.dump() 
                for c
                in self.children
            ]
        }

    def add_children(self,child):
        self.children.append(child)

class bookmark(object):
    def __init__(self,_id,guid,index,title,uri):
        self._id = _id
        self.guid = guid
        self.index = index
        self.title = title
        self.uri = uri
        self._type = "text/x-moz-place"

    def dump(self):
        now = datetime.datetime.now()
        unix = int(now.strftime('%s'))

        return {
            "dateAdded" : unix,
            "guid" : self.guid,
            "id" : self._id,
            "index" : self.index,
            "lastModified" : unix,
            "title" : self.title,
            "type" : self._type,
            "uri" : self.uri
        }

    def add_children(self,child):
        self.children.append(child)


if __name__=="__main__":
    import json

    counter = 10000
    dir_index = 0
    bk_index = 0

    top = folder(counter,"DIR%d"%counter,0,"Lunascape20170504")
    bk1 = bookmark(101,"BK",0,"bookmark1","http://yahoo.co.jp")


    parent_dir = None
    buf = ""

    for l in open("bookmark2.txt"):
        l = l.strip()
        #print l
        #urlでない時
        if not l.startswith("http"):
            #defaultの文字列だった時
            if l.startswith("default"):
                counter += 1
                dir_index += 1
                bk_index = 0
                local_dir = folder(
                    counter,
                    "DIR%d" % counter,
                    dir_index,
                    buf
                )
                buf = ""
                top.add_children(local_dir)
                parent_dir = local_dir
            else:
                if buf != "" and l.strip() != "":
                    #print "-*-"*10
                    buf = buf + " " + l
                    #print buf
                else:
                    buf = l.strip()
        #urlの時
        else:
            counter += 1
            bk_index += 1
            bk = bookmark(
                counter,
                "BK%d" % counter,
                bk_index,
                buf,
                l.strip()
            )
            
            parent_dir.add_children(bk)


    print json.dumps(top.dump(),ensure_ascii=False)
```

はい．ちょっとだけ細かい修正も加えていますが省略．見るべきところはほとんどないですが，

```py
json.dumps(top.dump(),ensure_ascii=False)
```

という部分だけは頭に入れおくと便利かも．これはjsonを出力する際に，ensure_ascii=Falseを設定することで，日本語がエスケープされた文字列ではなく，普通に出力することが出来る．

[Python の json.dumps() で日本語が文字化けする場合のメモ](http://cortyuming.hateblo.jp/entry/20140920/p2)

へー．意外に便利．
というわけで，得られたjsonファイルを，Firefoxのブックマークが入っている該当箇所に追記し，Firefoxで読み込み直せば，完成！


#最後に
　コードを見てもらえばわかりますが，実はちゃんとLunascapeの階層構造は再現していません．めんどくさいんで．でも，フォルダ数は大した数じゃないので，それこそ手動で，整理してもらえば，そこそこ何とかなるはずなので，そこまでにしました．
　今も昔も，テレビの企画で，エンジニアの人にテレビ映えのする一点限りのものを作らせたりする企画って，結構あると思います．例えばですが，鉄腕DASHでわざわざ無人島に技術者を呼んで電車走らせたり，マツコデラックスのロボット作らせたり，あとは番組で言うと大科学実験とか．でも，アレって私は結構好きなんですよね．その昔，「はぁ～エンジニアってのはすごいなぁ～こんな感じで簡単に作ってしまうんだなぁ～」と思いました．(今となっては相当苦労しているのも分かりますが・・・)
　そういうわけで他人が「困った．何とかしてほしい．」というのを「エンジニアがちゃちゃっとなんとかしてしまう」みたいなことって，「原体験として自分が持っていたものを，与える側になったんだなぁ～」と思った次第です．不思議な感じですね．やってみたら2,3時間でできてしまうものです．
　あんまり自分をエンジニアとして感じることはなかったので，「そうかーもう人生において，そういうフェーズなのかなぁ．」と郷愁に浸った一件でした．