Web系PG一年生必携ポケットリファレンスセット

[改訂版] PHP ポケットリファレンス (Pocket reference)

[改訂版] PHP ポケットリファレンス (Pocket reference)

改訂第6版 HTML&スタイルシート ポケットリファレンス (POCKET REFERENCE)

改訂第6版 HTML&スタイルシート ポケットリファレンス (POCKET REFERENCE)

改訂第4版 JavaScript ポケットリファレンス

改訂第4版 JavaScript ポケットリファレンス

  • 懐が寒い人は・・・

中古商品は早い者勝ちです!お早めに!
*1

  • あと、お約束

ポケリだけを鵜呑みにせずに、ぐぐって本当に正しいか裏を取りましょう!
*2

*1:ポケリはすぐに古くなるので新品で買う必要なしに一票w

*2:記述ミスが無いとも言えない・・・。あと紙メディアだから最新バージョンへの追従がむずかしい。

WebBrowserコントロールのDocumentTextプロパティの罠

WebBrowserコントロールで表示しているHTMLをXHTMLに清書しようと

webBrowser1.DocumentText

を入力としてTidy.NETtidy.dllHTML2XHTMLを使うと不思議と文字化けします。エンコーディングUTF-8を指定してもダメ。

で、原因がわかりました。HTMLがShift-JISで書かれている場合にDocumentTextプロパティの値が文字化けするようです。ヘッダでエンコーディングが指定されてないのが原因かも。

webBrowser1.Document.Body.OuterHtml

などは正常なので、気づきにくい罠です。

こうやって文字化けを直してあげると、Tidy.NETで正常にXHTMLへ変換できました。

MemoryStream reader = (MemoryStream)this.window.DocumentStream;
byte[] bytes = reader.ToArray();
string html = Encoding.GetEncoding("shift_jis").GetString(bytes);

ネイティブバイナリのtidy.dllでHTMLを清書する

が失敗しました。orz

やはり、日本語が文字化けします。何か間違ってるんだろうか・・・
↑ウソ:これが原因

using System;
using System.Text;
using System.Runtime.InteropServices;

namespace TMGBCReserver
{
    public class Tidy32
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct TidyBuffer
        {
            public IntPtr bp;           /**< Pointer to bytes */
            public uint size;         /**< # bytes currently in use */
            public uint allocated;    /**< # bytes allocated */
            public uint next;         /**< Offset of current input position */
        };

        [DllImport("tidy.dll")]
        public static extern int tidyBufAlloc(ref TidyBuffer tidyBuffer, int allocSize);

        [DllImport("tidy.dll")]
        public static extern int tidyBufFree(ref TidyBuffer tidyBuffer);

        [DllImport("tidy.dll")]
        public static extern IntPtr tidyCreate();

        [DllImport("tidy.dll")]
        public static extern int tidyParseFile(IntPtr tidyPointer, [MarshalAs(UnmanagedType.LPStr)]string fileName);

        [DllImport("tidy.dll")]
        public static extern int tidyParseBuffer(IntPtr tidyPointer, [MarshalAs(UnmanagedType.Struct)] ref TidyBuffer tidyBuffer);

        [DllImport("tidy.dll")]
        public static extern int tidyCleanAndRepair(IntPtr tidyPointer);

        [DllImport("tidy.dll")]
        public static extern int tidySaveFile(IntPtr tidyPointer, [MarshalAs(UnmanagedType.LPStr)]string outFileName);

        [DllImport("tidy.dll")]
        public static extern int tidySaveBuffer(IntPtr tidyPointer, [MarshalAs(UnmanagedType.Struct)] ref TidyBuffer tidyBuffer);

        [DllImport("tidy.dll")]
        public static extern int tidyRelease(IntPtr tidyPointer);

        [DllImport("tidy.dll")]
        public static extern int tidySetCharEncoding(IntPtr tidyPointer, [MarshalAs(UnmanagedType.LPStr)]string encoding);

        [DllImport("tidy.dll")]
        public static extern int tidyOptSetBool(IntPtr tidyPointer, int value, int Bool);

        public static string CleanFile(string inputHtml)
        {
            string result;
            byte[] inputArray = Encoding.UTF8.GetBytes(inputHtml);

            TidyBuffer tidyBuffer2;
            tidyBuffer2.size = 0;
            tidyBuffer2.allocated = 0;
            tidyBuffer2.next = 0;
            tidyBuffer2.bp = (IntPtr)0;
            tidyBufAlloc(ref tidyBuffer2, 1024 * 1024);

            IntPtr tidyPointer = tidyCreate();
            try
            {
                // We want the resulting file to be UTF8 encoded
                tidySetCharEncoding(tidyPointer, "utf8");

                TidyBuffer tidyBuffer1;
                tidyBuffer1.size = (uint)inputArray.Length;
                tidyBuffer1.allocated = (uint)inputArray.Length;
                tidyBuffer1.next = 0;
                GCHandle pinHandle = GCHandle.Alloc(inputArray, GCHandleType.Pinned);

                try
                {
                    tidyBuffer1.bp = Marshal.UnsafeAddrOfPinnedArrayElement(inputArray, 0);

                    if (tidyParseBuffer(tidyPointer, ref tidyBuffer1) >= 0)
                    {
                        tidyOptSetBool(tidyPointer, 29, 1);
                        tidyOptSetBool(tidyPointer, 23, 1);
                        if (tidyCleanAndRepair(tidyPointer) >= 0)
                        {
                            int rc = tidySaveBuffer(tidyPointer, ref tidyBuffer2);
                        }
                    }
                }
                finally
                {
                    pinHandle.Free();
                }

                byte[] outputArray = new byte[tidyBuffer2.size];
                Marshal.Copy(tidyBuffer2.bp, outputArray, 0, outputArray.Length);
                result = Encoding.UTF8.GetString(outputArray, 0, outputArray.Length);
            }
            finally
            {
                tidyBufFree(ref tidyBuffer2);
                tidyRelease(tidyPointer);
            }

            return result;
        }
    }
}

formのenctypeの変更

http://d.hatena.ne.jp/babydaemons/20080520/1211278047 でファイルをアップロードするときにenctypeに"multipart/form-data"を指定することを書いたが、逆にアップロードしない場合はクラシックASPのRequest.Formでフォームの情報が読めなくなるので、"multipart/form-data"をenctypeに指定できない。

では、アップロードするときにのみenctypeを再設定するようにすれば良いはずだが、IEでは再設定されていないような振る舞いだった。

function upload() {
    var myForm = document.getElementById("myForm");
    myForm.enctype = "multipart/form-data"; // これで良いはずなんだけど…
    myForm.action = "upload.asp";
    myForm.submit();
}

仕方ないのでフォームを2つ使うように作り変えたが、画面仕様的には見た目フォームひとつなので、CSSでstyle="position:relative;top:-100px;left:-100px"とかやってつじつま合わせした。

なんだかなー。

formのでファイルのアップロード

フォームデータの送信 -- ごく簡単なHTMLの説明アメリカ住まいの双子のパパの日記でお勉強したが、formタグに「enctype="multipart/form-data"」を指定しないとうまくいかない。

<form id="main" action="hoge.cgi" method="post" enctype="multipart/form-data">
                                 <!--ここが重要 ▲▲▲▲▲▲▲▲▲▲▲▲▲-->
    <label for="filename">取込ファイル</label>
    <input type="file" id="filename"  />
</form>

enctypeを指定しなければディフォルトの"application/x-www-form-urlencoded"になるが、この状態でpostした様子をWiresharkで覗いてみたら"filename="と空データになっていた。

業務系Webアプリでファイルアップロード後にローカルファイルを削除する

とか「ええええーーー!」な仕様も、IE (ないしIEコンポーネントを使用したアプリ) & ActiveX & FileSystemオブジェクトで実現できてしまう。さすがはM$

ちなみに、この実現方法を思いつかせるインスピレーションを与えてくれたエントリはこちら↓
http://d.hatena.ne.jp/taka_2/20080306#p1

セキュリティの設定に穴をあける

WindowsXP SP2以前のスクリプトウィルスの大流行で判るとおり、Webで安全ではないActiveX(特にFileSystemオブジェクト)が利用できるとセキュリティホールになりえます。FileSystemオブジェクトの場合はデータ消失やデータ流出のリスクがあります。
故に、安全でないActiveXの実行許可は必要最低限のサイトに抑える必要があります。業務系WebアプリではHTTPサーバはプライベートIPで通信できるでしょうから、そのプライベートIPアドレスのみ許可するのが妥当でしょう。プライベートIPアドレスに限定することで外部からの攻撃に利用されることを未然に防ぎます。
気が向いたら設定方法についてエントリ書きます。

ファイルをアップロードする

クラシックASPではBASP21を利用して実現できます。ASP.NETはぐぐればいっぱい例があるでしょう。今回のお話はクライアント側のJavaScriptのお話なので、サーバ側はPerlC/C++で書いたCGIでもPHPでもJavaでも大丈夫です。
どのプラットフォームで実装するにしてもHTMLのフォームに必ず指定する記述があります。詳しくは下記エントリ参照。
http://d.hatena.ne.jp/babydaemons/20080520/1211278047

アップロードされたフォームデータから得られたクライアントPCのローカルファイル名は、処理完了後のファイル削除のときに必要ですから、セッション変数なりhiddenタグなりDBなりで記憶しておく必要があります。

ちなみにファイル削除のタイミングは、アップロード完了時点ではなく、そのデータを使った処理が完了した時点が親切でしょう。ユーザが処理をキャンセルしたり、サーバのスクリプトがバグってて落ちることも想定しましょう。

ファイルを削除する

いちおう、これだけ書けば目的のファイルは削除できます。

var fso = new ActiveXObject("Scripting.FileSystemObject");

if (fso.FileExists(file)) {
	fso.DeleteFile(file, true);
}

しかし、あらかじめクライアントPCのセキュリティ設定で穴をあけてないとJavaScriptエラーになりますし、そもそもIEで無ければActiveXObjectすらありません。現実的には、下記のチェックが必要になるでしょう。

  • ブラウザはIEか?
  • (クライアントのIPアドレスでチェックするなどして、)安全でないActiveXの実行が許可されているか?

私が実装したときは、IEコンポーネント利用の専用アプリだったので前者のチェックは省略しましたw
#だって、デスマって来てるし。orz

後者のチェックは本番環境にデプロイして足りないことに気づいてあわてて実装。
#あまり、クラシックASPでデプロイって言わないよねw

「お気に入り」のアイコンファイルをダウンロード

HTML: Adding a Favorite Icon to Your Site (favicon.ico) - Favorites Iconsによれば、HTMLの<head>タグの中に

とか、

とか書いてあるとのこと。運良く<link REL="SHORTCUT ICON" HREF="〜">が書いてあればそこからダウンロードすればよい。

運悪く<link REL="SHORTCUT ICON" HREF="〜">が無い場合は、ダメもとで上記に相当するURLからダウンロードしてみると取得できることもあるみたい。

例えば、ゴールドマンサックス証券のサイトだと、http://www2.goldmansachs.com/favicon.icoから取得できる。(http://www.gs.com/favicon.icoからリダイレクトされる。)

C#であるサイトからスクレイピングしたりダウンロードしたりするアプリケーションとか作ってると、アイコンを安直にそのサイトのお気に入りアイコンにしたいんだよねー。

日本語でまとまった解説はwikipedia:Faviconにある。
#というか最初からそっちで調べた方が良かったんだけど、ぐぐった結果最初のページを先に見たんだよね。orz