Java HtmlUnit でコンテンツをフィルタする

HtmlUnit を簡易ブラウザエミュレータとして使う。
取得した Web ページを一部分書き換えたい。
Proxomitron などのプロキシサーバーを介して、フィルタリングするのもよい。
DOM の範囲で変更できる箇所なら、Document.Write でもよい。

JavaScript で URL を生成して、アクセスするものもある。
URL をアクセスするときにフィルタリングしたい。

import java.net.URL;
import java.io.IOException;

import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.util.FalsifyingWebConnection;

public class test {

    public static void main(String[] args) throws Exception {

        WebClient webClient = new WebClient(BrowserVersion.FIREFOX_3);

        new FalsifyingWebConnection(webClient) {

            @Override
            public WebResponse getResponse(WebRequest request) throws IOException {

                WebResponse response;
                if ( request.getUrl().toString().contains("hoge.js") ) {
                    // 空のコンテンツを返す
                    response = createWebResponse(request, "", "application/javascript", 200, "OK");
                } else {
                    response = super.getResponse(request);
                }
                
                return response;
            }
        };

        HtmlPage page = (HtmlPage)webClient.getPage(new URL("http://www.hoge.com"));
        System.out.print(page.asText());
    }
}

取得したコンテンツを HtmlUnit 内で書き換える。

        new FalsifyingWebConnection(webClient) {

            @Override
            public WebResponse getResponse(WebRequest request) throws IOException {

                WebResponse response = super.getResponse(request);
                String content = response.getContentAsString();
                content = content.replaceAll("var i=12345", "var i=0");
                response = replaceContent(response, content);
                
                return response;
            }
        };

Perl WWW::HtmlUnit を Windows で使用する

HtmlUnit とは Java で作成されている HTML のテスト用のフレームワークJavaScript のテストにも対応している。

JavaScript を使用している Web ページを外部から取得してみたくなった。


最初に ActivePerl 5.12.1.1201 をインストールしていたが、
Windows の環境によって、dmake ではなく nmake が使用され、エラーとなる。
PPM でインストールできる GCC のバージョンも古いため、
Strawberry Perl 5.12.1.0 を使用する。(2010/09/04)

Windows 9x/2000/XP/Vista/7 の Win32 をターゲットとする。
Strawberry Perl 5.12.1.0 をインストール
C:\strawberry

コマンドプロンプトから
> set
PATH に C:\strawberry\perl\bin;C:\strawberry\perl\site\bin;C:\strawberry\c\bin;%PATH%
と、Perl.exe にパスが通っていることを確認する。

Java Development Kit をインストール
jdk-6u21-windows-i586.exe
C:\Program Files\Java\jdk1.6.0_21 を DOS 用の空白文字の無いパス 8 + 3 文字を調べておく。
C:\PROGRA~1\JAVA\JDK16~1.0_2
最初から短いパス名でインストールしたり、
NTFS のジャンクション機能を使用して、短いパスを作っておくのも良い。

コントロールパネルのシステムで環境変数を設定する
(その都度 コマンドプロンプトから set コマンドを使ってもよい)

set JAVA_HOME=C:\PROGRA~1\JAVA\JDK16~1.0_2

set PERL_INLINE_JAVA_JNI=1
Inline::Java で JNI を使わない場合は必要ない。

set PATH=%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin\client;%PATH%

システムの環境設定を変更した場合は、ここで再起動かログオフをして環境変数を適用させる。
コマンドプロンプトから
> set
で、設定した環境変数が表示出来ている場合はログインしなおす必要はない。


コマンドプロンプトから次のモジュールをインストールする。
Test-Pod-1.44
Inline--0.46
Inline-Java-0.52
WWW-HtmlUnit-0.09

> perl -V:make
で dmake が設定されていることを確認する。

> perl -MCPAN -e shell
cpan> install Test::Pod
cpan> install Inline

ここで Inline::Java をインストールするけれど、JNI を使用するオプションでは、リンクエラーとなる。
JNI を使用しないなら
cpan> install Inline::Java
にて、JNI のオプションに n と入力すればよい。

JNI を使用するなら
cpan> get Inline::Java
にて C:\strawberry\cpan\build\Inline-Java-0.52-??? が作成される。

Inline-Java-0.52-???\Java\Makefile.PL をテキストエディタで開き
196 行目の OTHERLDFLAGS の下に
. ' C:\\PROGRA~1\\JAVA\\JDK16~1.0_2\\lib\\jvm.lib '
を追加する。

				dynamic_lib => { 
					OTHERLDFLAGS => Inline::Java::Portable::portable('OTHERLDFLAGS') 
					. ' C:\\PROGRA~1\\JAVA\\JDK16~1.0_2\\lib\\jvm.lib ' # 追加した行
				},

cpan> install Inline::Java
PerlNatives extension や PerlInterpreter extension は有効にするとコンパイルエラーとなる。
このオプションを有効にする解決方法は調べていない。
オプションはデフォルトのまま Enter キーで入力していく。


WWW::HtmlUnit もそのままインストールすると make test で実行エラーとなる。
cpan> get WWW::HtmlUnit

C:\strawberry\cpan\build\WWW-HtmlUnit-0.09-???\lib\HtmlUnit.pm をテキストエディタで開く。

88 行目の
  return join ':', map { "$jar_path/$_" } qw(
のコロンをセミコロンに変更する。
  return join ';', map { "$jar_path/$_" } qw(

このエラーは、Unix 系では一つのディレクトリにドライブをマウントして使用するため、
パスにコロンは含まれず、区切り文字として利用できるが、
Windows ではドライブレターにコロンを使用しているため、パスの区切りとしてコロンは使用できない。
よって、HtmlUnit の jar ファイルを認識できなくなっていた。
他にも区切り文字としてコロンを使用している箇所もある。

cpan> install WWW::HtmlUnit

以上で WWW::HtmlUnit を使用できる環境が整った。


SYNOPSIS にあるサンプルプログラムでも走らせて動作確認してみよう。

use WWW::HtmlUnit;
my $webClient = WWW::HtmlUnit->new;
my $page = $webClient->getPage("http://google.com/");
my $f = $page->getFormByName('f');
my $submit = $f->getInputByName("btnG");
my $query  = $f->getInputByName("q");
$page = $query->type("HtmlUnit");
$page = $query->type("\n");

my $content = $page->asXml;
print "Result:\n$content\n\n";

test.pl と保存して
> perl test.pl

取得した html が問題なく出力された。

HtmlUnitJavaフレームワークだし、
JavaScript に対応するためなので Java で使う方がスマート。
今回は Perl の他のプログラムと連携させたかったのと、
JRuby + Celerity(HtmlUnit) では shiftjis をそのまま使用することが出来なかったため。

SEH、構造化例外処理のテスト

try ... catch() や __try ... __except() を使用できれば、そちらが簡単。

#include <stdio.h>
#include <windows.h>

LONG CALLBACK ExceptionHandler(EXCEPTION_POINTERS *ExceptionInfo)
{
	printf("ExceptionHandler\n");
	
	// c で次の命令へのアドレスを取得する方法が現在の所は不明。
	ExceptionInfo->ContextRecord->Eip += 10; // 例外が発生した次の命令へ飛ばす

	return(EXCEPTION_CONTINUE_EXECUTION);
}

int main()
{
	printf("null pointer exception\n");

	SetUnhandledExceptionFilter(ExceptionHandler);

	*(int*)0 = 0x12345678;
	
	printf("end.\n\n");
	return 0;
}

インラインアセンブラで fs:[0] を使用する

#include <stdio.h>
#include <windows.h>

// __cdecl __stdcall のどちらでも動作したけれど、要確認
int __cdecl exception(EXCEPTION_RECORD *lpException, DWORD *lpFrame, CONTEXT *lpContext, DWORD* lpDispatch)
{
	printf("ExceptionHandler\n");

	context->Eip = context->Edx;
	
	return ExceptionContinueExecution;
}	

int main()
{
	printf("null pointer exception\n");

	__asm {
		xor ebx,ebx
		mov eax, exception
		push eax
		push fs:[ebx]
		mov fs:[ebx], esp

		mov edx, offset nextstep // edx にジャンプ先アドレスを格納

		xor ebx, ebx
		mov dword ptr [ebx], 12345678h

	nextstep:
		xor ebx, ebx
		mov eax, [esp]
		mov fs:[ebx], eax
		add esp, 8
	}
	
	printf("end.\n\n");
	
	return 0;
}

アセンブラではよいけれど、マシン語環境でハンドラ関数のアドレスの扱い方が今後の課題

TabMixPlus の javascript を改造する

タブを追加するときにアクティブタブの右に挿入させている
タブを削除した場合は左のタブへ移動する設定をしている

タブを閉じたときにアクティブタブの右側に未読のタブがあれば
一番右の未読のタブへ移動したくなった
未読タブが右側に無ければ、設定通りに左のタブへシフトさせる


TabMixPlus 0.3.7.4pre 090404
tabmixplus.jar->content\tabmixplus\tab\tab.js
TMP_PL_setUnreadTab()関数に追加
この関数はページの読み込み完了時に呼び出される

   setUnreadTab: function TMP_PL_setUnreadTab(aTab) {
      if (aTab.parentNode.childNodes.length == 1) {
        var hidebutton = gBrowser.isBlankTab(aTab) || tabxPrefs.getBoolPref("keepLastTab");
        TMP_setItem(aTab.parentNode, "hidebutton", hidebutton || null);
      }
//追加部分
      aTab.setAttribute("ma_pageLoad", "unread"); // 未読タブにフラグを付ける

      aTab.removeAttribute("tab-progress");
      if (gPref.getBoolPref("extensions.tabmix.unreadTab") &&
            aTab.hasAttribute("selected") &&
            gPref.getBoolPref("extensions.tabmix.unreadTabreload") &&
            !aTab.hasAttribute("dontremoveselected") &&
            aTab.getAttribute("selected") == "false")
         aTab.removeAttribute("selected");

       // see gBrowser.openLinkWithHistory in tablib.js
       if (aTab.hasAttribute("dontremoveselected"))
          aTab.removeAttribute("dontremoveselected")
   },

tabmixplus.jar->content\tabmixplus\tabmix.js
TMP_EL_handleEvent()関数の改造
この関数はタブのイベント処理をする

  handleEvent: function TMP_EL_handleEvent(aEvent) {
    switch (aEvent.type) {
      case "SSTabRestoring":
        this.onSSTabRestoring(aEvent);
        break;
      case "TabOpen":
        this.onTabOpen(aEvent);
        break;
      case "TabClose":
        this.onTabClose(aEvent);
        break;
      case "TabSelect":

// 追加部分 選択したタブからフラグを除去
        if ( aEvent.target.hasAttribute("ma_pageLoad") )
            aEvent.target.removeAttribute("ma_pageLoad");

        this.onTabSelect(aEvent);
        break;
      case "DOMMouseScroll":
        this.onTabBarScroll(aEvent);
        break;
      case "load":
        this.onWindowOpen(aEvent);
        break;
      case "unload":
        this.onWindowClose(aEvent);
        break;
    }
},

tabmixplus.jar->content\tabmixplus\minit\tablib.js
タブを閉じたときの挙動の処理

gBrowser.selectIndexAfterRemove = function (oldTab) {
   var currentIndex = this.mCurrentTab._tPos;
   if (this.mCurrentTab != oldTab)
     return currentIndex;
   var l = this.mTabs.length;
   if (l==1)
     return 0;
   var mode = this.mPrefs.getIntPref("extensions.tabmix.focusTab");
   switch ( mode ) {
      case 0: // first tab
            return currentIndex == 0 ? 1 : 0;
         break;
      case 1: // left tab

// 追加部分
        var lastIndex = gBrowser.mTabContainer.childNodes.length - 1;
        for (var i=lastIndex; i>currentIndex; i--) {
            if ( gBrowser.mTabs[i].hasAttribute("ma_pageLoad") ) // 未読フラグの確認
                return i;
        }
// ここまで

        return currentIndex == 0 ? 1 : currentIndex-1;
        break;
      case 3: // last tab
         return currentIndex == l - 1 ? currentIndex - 1 : l - 1;
         break;
      case 6: // last opened
            var lastTab = this.getTabForLastPanel();
            if (lastTab == oldTab && l > 1) {
              lastTab = document.getAnonymousElementByAttribute(this, "linkedpanel",
                                    this.mPanelContainer.childNodes[l-2].id);
            }
            return lastTab._tPos;
      case 4: // last selected
            var tempIndex = this.previousTabIndex(oldTab);
            // if we don't find last selected we fall back to default
            if (tempIndex > -1)
               return tempIndex;
      case 2: // opener / right  (default )
      case 5: // right tab
      default:
            if (mode != 5 && this.mPrefs.getBoolPref("browser.tabs.selectOwnerOnClose") && "owner" in oldTab) {
               var owner = oldTab.owner;
               if (owner && owner.parentNode && owner != oldTab)
                 // oldTab and owner still exist just return its position
                 return owner._tPos;
            }
   }
   return currentIndex == l - 1 ? currentIndex - 1 : currentIndex + 1;
}

TabMixPlus でタブを閉じたときに左のタブへを設定すれば右側にある未読のタブへ飛ぶ

追記 多段タブ設定にしていると段にタブがあふれたときに未読フラグがおかしくなった こりゃいかん
追記090412 Firefox 3.5 を導入する際にちょこっと改良

Perl HTML数値文字参照(HTMLエンティティとも呼ばれている模様)

&#12354;

などの 主にWEB上でユニコード文字の文字コード化に使われているHTML数値文字参照の変換スクリプト

use Encode qw/ encode decode from_to /;
use HTML::Entities;

my $str = 'abc&#12354;DEF';

# HTML数値文字参照はセミコロンで終わっていなければならない
HTML::Entities::decode_entities( $str );

# シフトJISに変換できない文字はHTML数値文字参照に
$str = Encode::encode( 'shiftjis', $str, Encode::FB_HTMLCREF );

print $str > abcあDEF

use Encode qw/ encode decode from_to /;

my $str = 'abc&#12354;DEF';

$str =~ s/\&\#(\d{1,5});?/encode('shiftjis',decode('utf16be',pack("n*",$1)),Encode::FB_HTMLCREF)/eg;

print $str > abcあDEF

PerlでSSLアクセス

ActivePerl v5.10.0
SSLhttps:// にアクセスするために Crypt-SSLeay をインストール

cmdから
ppm install http://cpam.uwinnipeg.ca/PPMPackages/10xx/Crypt-SSLeay.ppd

use LWP::UserAgent;

my $ua = new LWP::UserAgent;
my $req = new HTTP::Request('GET', 'https://www.〜');
my $res = $ua->request($req);
print $res->content . "\n";

インラインアセンブラの関数

__declspec( naked )
void __fastcall memcpy_test( void* dst, const void* src, size_t nsize )
{
    mov [esp-4], edi
    mov [esp-8], esi
    mov edi, ecx // dst
    mov esi, edx // src
    mov ecx, [esp+4] // nsize
    cld
    rep movsb
    mov esi, [esp-8]
    mov edi, [esp-4]
    ret 4
}

上記簡易memcpy関数は大抵の場合は問題なく動いたが、割り込みが入るプログラムではスタックが書き換わり、保存したedi、esiの内容が不定となった

    push edi
    push esi
    ;
    pop esi
    pop edi

もしくは

    sub esp, 8
    mov [esp], edi
    mov [esp+4], esi
    ;
    mov esi, [esp+4]
    mov edi, [esp]
    add esp, 8

とespをキチンと指定する
MMXやSSEレジスタを使う場合も元の値に復元してやる
フラグレジスタは・・・とりあえず未定