JavaScriptでのBEncodeの解析。 Firefoxでトレントファイルを表示する

I.なぜ



トレントファイルを表示するにはいくつかの方法があります。トレントクライアント、 BEncode Editor 、プラグインを使用したファイルマネージャ、ネットワークサービスなど(ただし、これは少し馬鹿げています)。



ただし、ブラウザから外部プログラムを呼び出すのは必ずしも便利ではありません。 このプログラムは常に完全な情報を提供するわけではありません。 常に便利というわけではありません。 常に検索可能というわけではありません。 したがって、ブラウザでトレントファイルを表示する簡単な方法が必要です。次に例を示します。



-配布の内容を見つけます。

-ディストリビューション内のファイルの数を調べます。

-ファイルに関する情報を見つけます(一部のトラッカーは不完全な説明に非常に甘く、ファイルに関する詳細情報はトレントファイルに表示されます-たとえば、解像度、ビデオおよびオーディオコーデック、映画の長さなど)。

-トレントファイル自体に関する情報(作成時間、トラッカー、プライバシーフラグなど)を見つけます。

-すべての情報をテキスト検索できるようにします。



II。 戦略



このタスクでは、ブラウザに拡張機能を作成できますが、これには多くの追加の困難が伴います。 したがって、簡単な方法を使用します。



カスタムボタン拡張機能を使用すると、任意のコードでボタンを作成できます。 さらに良いことに、このコードはブラウザコンテキストで実行され、拡張機能と同じコンポーネントとインターフェイスにアクセスし、任意の複雑さのGUI要素を作成することさえできます。 したがって、単純に新しいボタンを作成してコードを入力します(すべてに200行が必要です)。ブラウザが起動されるたびに実行されるように、次のすべてのコードを新しく作成されたボタンの初期化タブに挿入する必要があります。 または、それを挿入することはできません:拡張機能はカスタムボタンを追加します://プロトコルをブラウザに追加し、記事の最後にクリックするだけで、コードで既製のボタンを作成できるリンクを提供します(ツールパレットから便利な場所に転送するためだけに残ります)。



III。 戦術



1.ユーザーインターフェイス


var btn = this; var imgMain = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAdVBMVEX////////////////////////////////XzeWjisHx7vaKaq5nNpV8VqSDYaqtmMiTdbS2pM/MwN6YfLjBstd7VqRuQZnk3u7y7/eKa6+SdbR1S56CX6jXzuabgLv49/uhh7+JaK39/P3e1ury7vbLv97g2es6Rn8YAAAAB3RSTlMAYMAg0PDzqTbVzAAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAACcSURBVBjTXY/bAoIgEEQRsWERUykDMu1m/f8ntqC+dJ6WYfYyQjCFBCMLsVIqaGI0VJnflaltpjZVUpRp7EZjFPcj/R/bLntQCKm56IE2e7QUIGsJ7tSeaUhVEi5wgw/OR2uvWegQvR9zy+ogWDi7CzyUMO4CD+W1EdQj7mvTYT7cJkx0nO9qPf2B8JxdwOtQbuHeC5bPdwv3F/8HCk4KcI8+awQAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTItMDYtMjZUMjE6MDk6MDQrMDQ6MDD1mOHZAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEyLTA2LTI2VDIxOjA5OjA0KzA0OjAwhMVZZQAAAABJRU5ErkJggg=="; var imgThrobber = "data:image/gif;base64,R0lGODlhEAAQAOMIAAAAABoaGjMzM0xMTGZmZoCAgJmZmbKysv///////////////////////////////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQBCgAIACwAAAAAEAAQAAAESBDJiQCgmFqbZwjVhhwH9n3hSJbeSa1sm5GUIHSTYSC2jeu63q0D3PlwCB1lMMgUChgmk/J8LqUIAgFRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+UKgmFqbpxDV9gAA9n3hSJbeSa1sm5HUMHTTcTy2jeu63q0D3PlwDx2FQMgYDBgmk/J8LqWPQuFRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+YSgmFqb5xjV9gQB9n3hSJbeSa1sm5EUQXQTADy2jeu63q0D3PlwDx2lUMgcDhgmk/J8LqUPg+FRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+cagmFqbJyHV9ggC9n3hSJbeSa1sm5FUUXRTEDy2jeu63q0D3PlwDx3FYMgAABgmk/J8LqWPw+FRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+QihmFqbZynV9gwD9n3hSJbeSa1sm5GUYXSTIDy2jeu63q0D3PlwDx3lcMgEAhgmk/J8LqUPAOBRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+UqhmFqbpzHV9hAE9n3hSJbeSa1sm5HUcXTTMDy2jeu63q0D3PlwDx0FAMgIBBgmk/J8LqWPQOBRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+YyhmFqb5znV9hQF9n3hSJbeSa1sm5EUAHQTQTy2jeu63q0D3PlwDx0lEMgMBhgmk/J8LqUPgeBRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+c6hmFqbJwDV9hgG9n3hSJbeSa1sm5FUEHRTUTy2jeu63q0D3PlwDx1FIMgQCBgmk/J8LqWPweBRhV6z2q0VF94iJ9pOBAA7"; function clickBtn(event) { if (event.button == 0) { event.preventDefault(); var tFileURL = prompt("Torrent File URL:"); if (tFileURL) { getTFile(tFileURL); } } } function checkDrag(event) { if (event.dataTransfer.types.contains("text/uri-list")) { event.preventDefault(); } } function onDrop(event) { var tFileURL = event.dataTransfer.getData("URL"); if (tFileURL) { getTFile(tFileURL); } event.preventDefault(); } btn.addEventListener("click", clickBtn, true); btn.addEventListener("dragenter", checkDrag, true); btn.addEventListener("dragover", checkDrag, true); btn.addEventListener("drop", onDrop, true); btn.onDestroy = function() { btn.removeEventListener("click", clickBtn, true); btn.removeEventListener("dragenter", checkDrag, true); btn.removeEventListener("dragover", checkDrag, true); btn.removeEventListener("drop", onDrop, true); }
      
      







この作品では、ボタンオブジェクトを取得し、2つの画像を設定し(1つは主要なもの、もう1つは標準のファイルダウンロードインジケーターで、交互に表示されます)、イベントハンドラーを定義してボタンにバインドします。



プログラムにトレントファイルのアドレスを与える方法は2つあります。リンクがある場合は、それをボタンにドラッグするだけです( メカニズムの説明 )。 バッファ内にアドレスを含む行がある場合、ボタンをクリックして、表示されるフィールドにアドレスを貼り付けます。



最後に、バインドされたハンドラーのデストラクターを定義します。 カスタムボタンには不快なバグがあります。デタッチメントを明示的に設定しない場合、ツールパレットを開いたり閉じたりするたびにハンドラーがオーバーラップします(他の設定を行っても)。



2.トレントファイルの取得


 function getTFile(tFileURL) { btn.image = imgThrobber; var xhr = new XMLHttpRequest(); xhr.mozBackgroundRequest = true; var sendData; if (tFileURL.indexOf("http://dl.rutracker.org/forum/dl.php") > -1) { xhr.open("POST", tFileURL, true); sendData = tFileURL.replace(/^.+\b(t=\d+).*$/, "$1"); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.setRequestHeader("Referer", "http://rutracker.org/forum/viewtopic.php?t=" + tFileURL.replace(/^.+\bt=(\d+).*$/, "$1")); } else { xhr.open("GET", tFileURL, true); sendData = null; } xhr.timeout = 10000; if(!/^file:/.test(tFileURL)) { xhr.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE; xhr.channel.QueryInterface(Components.interfaces.nsIHttpChannelInternal) .forceAllowThirdPartyCookie = true; } xhr.responseType = "arraybuffer"; xhr.onload = function() { btn.image = imgMain; processTFile(this.response); } xhr.ontimeout = function() { btn.image = imgMain; alert("Timeout"); } xhr.onerror = function() { btn.image = imgMain; alert("HTTP error"); } xhr.send(sendData); }
      
      







まず、トレントファイルはBEncode言語( トレントファイルの 言語の説明形式)で 記述されていることに注意してください。 その機能はJSONに少し似ています。 ただし、そこに1つの問題があります。タグ、数値、および行は原則としてUTF-8でエンコードされますが、一部の行にはバイナリデータ(UTF-8の規則に従わない単純なバイトシーケンス)が含まれます。 したがって、行全体をUTF-8として扱うことはできません。デコーダーがロックアップし、誤ったUTF-8についてのエラーが発生します。 ファイルを解析するときは、この注意事項に留意する必要があります。



ここで、リクエスト自体とファイルの取得に関するいくつかのメモがあります。



パルセータ画像を開始し、バックグラウンドリクエストフラグを設定してブラウザを簡素化します。 次に、アドレスを確認し、最も有名な国内のトレントトラッカーの保護を再確認します。これは、サイトのページからではなくトレントファイルのダウンロードを禁止します(この方法は、トラッカーのトレントファイルと拡張機能を使用できるようにするもので、保護を導入するときにサイト開発者によって説明されていました)。



ユーザーがサードパーティのサイトからのCookieを無効にしている場合でも、FirefoxにXHRを使用してCookieを送信させるには、強制送信フラグを設定します(これがないと、Cookieは受け入れられず送信されません)。 もちろん、Cookieの強制送信とキャッシュバイパスのフラグはネットワークプロトコルにのみ必要であるため、ローカルファイルプロトコルの場合はインストールしません。



以前は、XMLHttpRequestからバイナリを取得するには、 いくつかのソーサリーに頼らなければなりませんでし 。 XHRに新しい応答タイプが導入されたことで、 事態より簡単になりました 。 したがって、タイプarraybuffer



を使用し、将来型付き配列を使用します



最後に、リクエストのさまざまな結果に対してハンドラーを設定します(毎回パルセーターを通常の画像に変更することを忘れずに)。 成功した場合、結果ファイルの解析に進みます。



3.トレントファイルの処理と出力


 function processTFile(tFile) { var byteArray = new Uint8Array(tFile); var torrentObject = bdecode(byteArray); if (torrentObject) { if (torrentObject['creation date']) { torrentObject['creation date'] = (new Date(torrentObject['creation date']*1000)).toLocaleString(); } if (torrentObject.info) { var files = torrentObject.info.files; if (files && files instanceof Array) { for (var i = 0, file; file = files[i]; i++) { if (file.length) { file.length = Number((file.length / 1024).toFixed(2)).toLocaleString() + " KB"; } if (file['path.utf-8']) { file['path.utf-8'] = file['path.utf-8'].join("/"); } if (file.path) { file.path = file.path.join("/"); } } if (files[0]['path.utf-8']) { files = files.sort( function(o1, o2) { if (o1['path.utf-8'] > o2['path.utf-8']) {return 1;} else if (o1['path.utf-8'] < o2['path.utf-8']) {return -1;} else {return 0;} } ); } else if (files[0].path) { files = files.sort( function(o1, o2) { if (o1['path'] > o2['path']) {return 1;} else if (o1['path'] < o2['path']) {return -1;} else {return 0;} } ); } files.unshift(files.length); } else { if (torrentObject.info.length) { torrentObject.info.length = Number((torrentObject.info.length / 1024).toFixed(2)).toLocaleString() + " KB"; } } } if (gBrowser.selectedBrowser.currentURI.spec == "about:blank" && !gBrowser.selectedBrowser.webProgress.isLoadingDocument) { gBrowser.selectedBrowser.loadURI( "data:application/json;charset=utf-8," + encodeURIComponent(JSON.stringify(torrentObject, null, '\t')) ); } else { gBrowser.selectedTab = gBrowser.addTab( "data:application/json;charset=utf-8," + encodeURIComponent(JSON.stringify(torrentObject, null, '\t')) ); } torrentObject = null; } else { alert("Parsing error"); } }
      
      







バイトの配列を作成したら、それをBEncodeデコーダーに渡し(少し後で)、そこからトレントファイルの構造をコピーする通常のオブジェクト(関連する配列、ハッシュ)を取得します(取得できない場合、解析エラーメッセージを取得します)。 次に、いくつかのデータをより読みやすい形式(作成日、ファイルサイズ、およびそれらへのパス)で取り込み、ファイルオブジェクトをpathプロパティでソートし、ファイルリストの一番上にファイルの総数を挿入します。 その後、現在のタブで空白のページが開いているかどうか、何かが読み込まれているかどうかを確認します。 開いていてロードされていない場合は、現在のタブに表示します。開いていない場合は、新しいタブを開いて表示します。



シンプルさと汎用性のために、JSONで出力を実装します。 出力を少しフォーマットします。 ただし、JSONを強調表示し、ノードを折りたたみ、 展開するツリー構造のように扱うことができる拡張機能( JSONViewなど )をインストールすることをお勧めします。 結論の後、念のため、巨大なオブジェクトをリセットします(パラノイアでない場合)。



4.パーサーBEncode


 function bdecode(byteArray, byteIndex, isRawBytes) { if (byteIndex === undefined) { byteIndex = [0]; } var item = String.fromCharCode(byteArray[byteIndex[0]++]); if(item == 'd') { var dic = {}; item = String.fromCharCode(byteArray[byteIndex[0]++]); while(item != 'e') { byteIndex[0]--; var key = bdecode(byteArray, byteIndex); if (key == "pieces") { dic[key] = bdecode(byteArray, byteIndex, true); } else { dic[key] = bdecode(byteArray, byteIndex); } item = String.fromCharCode(byteArray[byteIndex[0]++]); } return dic; } if(item == 'l') { var list = []; item = String.fromCharCode(byteArray[byteIndex[0]++]); while(item != 'e') { byteIndex[0]--; list.push(bdecode(byteArray, byteIndex)); item = String.fromCharCode(byteArray[byteIndex[0]++]); } return list; } if(item == 'i') { var num = ''; item = String.fromCharCode(byteArray[byteIndex[0]++]); while(item != 'e') { num += item; item = String.fromCharCode(byteArray[byteIndex[0]++]); } return Number(num); } if(/\d/.test(item)) { var num = ''; while(/\d/.test(item)) { num += item; item = String.fromCharCode(byteArray[byteIndex[0]++]); } num = Number(num); var line = ''; if (isRawBytes) { byteIndex[0] += num; return "[" + num + "]"; } else { while(num--) { line += escape(String.fromCharCode(byteArray[byteIndex[0]++])); } try { return decodeURIComponent(line); } catch(e) { return unescape(line) + " (?!)"; } } } return null; }
      
      







基礎として、私はPerlパーサーを取り上げ、その簡潔さと単純さに魅了されました。 最初に、 shift



で作業できるように、型付き配列を通常のバイト配列に変換しようとしましたが、この実装は非常にゆっくりと動作しました(おそらく大きな配列の絶え間ない変更のため)。 そのため、増加するアクセスインデックスを導入し、それを配列でラップする必要がありました(再帰で参照渡しできるようにするため)。



元のサンプルとの主な違いは、行解析ブロックです。 最初に、セグメントのハッシュを含むバイトを持つ巨大な文字列を出力から削除します(明確な場所があるため、関連する配列の解析で目的のキーに到達したため、このキーの値を解析するために呼び出しでエンコード無効フラグを一時的に設定します)。 次に、残りの行についてバイトをUTF-8に変換する操作を実行します。 ここに危険があります:時折、行にUTF-8ではない(たとえば、人気のあるtracker.0day.kiev.uaトラッカーは、「ソース」キーのWindows-1251エンコードに「トラッカー」という単語を挿入する)、decodeURIComponentがエラーでクラッシュする。 したがって、このような場合、生のビューを文字列に返し、少しマークします。 そのような行を完全に削除することは可能ですが、問題の原因となるのはその一部のみである場合があります。ASCIIとUTF-8の一致により、主要部分は非常に読みやすくなります。



IV。 見込み



説明した情報の解析と取得に基づいて、より複雑なタスクを実装できます。 例:



-同じアドレスでトレントファイルの更新を監視し(内容または作成時間を確認)、再アップロードについて通知します。 そのようなチェックの例(Webページに関してですが、すべてが簡単にやり直されます)はここにあります。



-ファイルが更新された場合、ブラウザからディスク上のフォルダに自動的に保存され、torrentクライアントがそれを取得します(ここでは、インターフェイスnsIFilensILocalFilensIFilePickernsIFileOutputStreamnsIBinaryOutputStreamおよびサンプルコード興味があります )。



-最新のXHR実装はfile://プロトコルをサポートしているため、ボタンを使用してローカルトレントファイルやクライアントデータベース(settings.datやresume.datなど)を表示することもできますが、後者の場合はクロコズームを含むバイナリ文字列が多くなります。 これを行うには、ブラウザーでファイルを含むフォルダーを開き、ボタンにドラッグします。






ボタンをインストールするための約束されたリンク(誰かがコードを部分的に初期化タブにコピーしたくない場合):habraparserはhttpプロトコルを使用してリンクを再作成するため、このページに移動して「View torrent files」リンクをクリックする必要があります(もちろん、Customをインストールした後ボタン)。



工芸品や不適切な言葉遣いの不適当さで誰かを動揺させた場合、私は謝罪します。 この経験の少なくとも一部が誰かに役立つことを願っています。



All Articles