WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

WebBrowser で HttpOnly 属性の Cookie 取得

by WebSurfer 2012年8月13日 15:35

.NET Framework の WebBrowser を利用した Windows アプリで、ドキュメントに関連付けられている HTTP Cookie を取得するには HtmlDocument.Cookie プロパティ が利用できます。

ただし、HttpOnly 属性を持つ HTTP Cookie は例外です。その理由は、The Code Project のページ Retrieve HttpOnly Session Cookie in WebBrowser に述べられていますが、IE6 以降でクロスサイトスクリプティング対応のため HttpOnly 属性が追加され、その属性を持つ HTTP Cookie にはクライアントスクリプトからアクセスできなくなっているからだそうです。

HttpOnly 属性を持つ HTTP Cookie には、例えば、ASP.NET のセッションクッキー、匿名ユーザー識別用クッキーが該当します(下の Fiddler による応答ヘッダーの画像で、名前が ASP.NET_SessionId と .ASPXANONYMOUS のもの)。

HTTP Cookies(Fiddler による応答ヘッダー)

上の画像の HTTP Cookie の内、HtmlDocument.Cookie プロパティで取得できるのは RandomNumber と DateTimeCookie のみです。この例では、HtmlDocument.Cookie プロパティから取得できる文字列は以下のようになります。

DateTimeCookie=2012/08/13 14:22:48; RandomNumber=1123940529

すべての HTTP Cookie を取得するには、WININET.dll の InternetGetCookieExA 関数 (wininet.h) を利用できます(mshtml.dll ではない点に注意)。以下のような感じです。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WebBrowserHttpOnlyCookie
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();

      textBox1.Text = 
          "http://msdntestnew/171-SendCookies.aspx";
    }

    private void button1_Click(object sender, EventArgs e)
    {
      webBrowser1.Navigate(textBox1.Text);
    }

    [DllImport("wininet.dll", CharSet = CharSet.Auto, 
      SetLastError = true)]
    static extern bool InternetGetCookieEx(
        string pchURL, 
        string pchCookieName, 
        StringBuilder pchCookieData, 
        ref uint pcchCookieData, 
        int dwFlags, 
        IntPtr lpReserved);

    const int INTERNET_COOKIE_HTTPONLY = 0x00002000;

    public static string GetCookies(string uri)
    {
      uint datasize = 1024;           
      StringBuilder cookieData = 
        new StringBuilder((int)datasize);
      bool result = InternetGetCookieEx(
                         uri,
                         null,
                         cookieData,
                         ref datasize,
                         INTERNET_COOKIE_HTTPONLY,
                         IntPtr.Zero);

      if (result && cookieData.Length > 0)
      {
        return cookieData.ToString();
      }
      else
      {
        return null;
      }
    }

    private void webBrowser1_DocumentCompleted(
      object sender, 
      WebBrowserDocumentCompletedEventArgs e)
    {
      label1.Text = webBrowser1.Document.Cookie;
      label2.Text = GetCookies(textBox1.Text);
    }
  }
}

Tags: , ,

.NET Framework

WebBrowser で iframe の中の要素を取得

by WebSurfer 2012年8月10日 21:45
注意:
iframe に表示するページは親ページと同じドメインのものでないと、以下に紹介する方法では DOM は取得できないので注意してください(クロスサイトスクリプト対策だそうです)。

.NET Framework の WebBrowser に表示したページに iframe が含まれる場合、iframe の中の 要素 (HtmlElement) を取得するにはどうしたらいいかという話です。

WebBrowser に iframe を含むページを表示

ページが単一のドキュメントで構成されている場合は、WebBrowser.Document プロパティ で取得できる HtmlDocument オブジェクト から、HtmlDocument.GetElementById メソッドなどを使って、ドキュメントを構成する要素 (HtmlElement) を取得できます。

しかしながら、ページに iframe が含まれている場合、HtmlDocument オブジェクトや、その All プロパティで取得できる HtmlElementCollection オブジェクトから iframe の中の要素を直接取得することはできません。(iframe は取得できますが、その子要素は取得できません)

iframe の中の子要素 (HtmlElement) を取得するには一工夫必要です。では、どうしたらいいでしょう?

例として、以下のように iframe を持ち、iframe の src 属性にテキストボックス 2 つとボタン 1 つを持つログインページページを指定してあるページで、iframe の中の要素 (HtmlElement) を取得して操作することを考えます。

親ページ(iframe を持つ)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
</head>
<body>
    <h1>iframe 中の Login ページ</h1>
    <iframe id="iframe1" src="168-Login.aspx" />
</body>
</html>

ログインページ(iframe に表示)

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        ID:<input id="Text1" 
            name="Text1" 
            type="text" />
        <br />
        PW:<input id="Text2" 
            name="Text2" 
            type="text" />
        <br />
        <input id="Submit1" 
            name="Submit1" 
            type="submit" 
            value="Login" />
    </div>
    </form>
</body>
</html>

上の「親ページ」を WebBrowser に表示したとします。

このとき、以下のコードでコメントアウトしたコードのように webBrowser1.Document.All で取得できる HtmlElementCollection には iframe の子要素は含まれないので id や pw は null になってしまいます。

iframe は WebBrowser コントロール内の別のウィンドウです。HtmlDocument.Window プロパティでドキュメントに関連付けられている HtmlWindow オブジェクトを取得し、HtmlWindow.Frames プロパティで Web ページ内で定義されている各 iframe 要素への参照を取得できます。(frame 要素の場合も同様です)

以下のようにして、iframe 内の要素 (HtmlElement) を取得して、操作することができます。

// これでは取得できない
//HtmlElement id = 
//    webBrowser1.Document.All.GetElementsByName("Text1")[0];
//HtmlElement pw = 
//    webBrowser1.Document.All.GetElementsByName("Text2")[0];
//id.InnerText = "loginName";
//pw.InnerText = "password";
//HtmlElement login = 
//    webBrowser1.Document.All.GetElementsByName("Submit1")[0];
//login.InvokeMember("click");


HtmlWindow iframe = webBrowser1.Document.Window.Frames[0];
HtmlElementCollection elements = iframe.Document.All;
            
HtmlElement id = elements.GetElementsByName("Text1")[0];
HtmlElement pw = elements.GetElementsByName("Text2")[0];
id.InnerText = "loginName";
pw.InnerText = "password";

HtmlElement login = elements.GetElementsByName("Submit1")[0];
login.InvokeMember("click");

Tags: ,

.NET Framework

WebBrowser の拡張

by WebSurfer 2012年7月1日 18:04

先の記事 SHDocVw.dll と AxSHDocVw.dll の作り方と使い方 では、ActiveX の WebBrowser コントロール (shdocvw.dll) をホストする、AxHost クラス から派生するラッパーコントロールを Visual Studio で生成し、それ使って NewWindow2 イベントを利用する例を書きました。

それと比較するために、.NET Framework の WebBrowser(これも shdocvw.dll のマネージラッパー)を拡張して同様なことを行うコードを書いてみました。

かなり面倒で、最初は COM の相互運用の知識がなかったのでお手上げ状態でした。あちこちググって調べて、動くようになるまで 3 日ぐらいかかりました。(笑)

詳しくは以下のコードとそのコメントを参照してください。参考にしたページの一覧も書いておきます。(手抜きですみません)

WebBrowser.CreateSink メソッド

Extended .NET 2.0 WebBrowser Control

COM相互運用機能の利用

COM相互運用機能の利用 - パート2

Microsoft .NET/COM の移行と相互運用性

COM ラッパー

ランタイム呼び出し可能ラッパー

COM 相互運用性 - 第 1 部 : C# クライアント チュートリアル

方法: COM ソースによって発生したイベントを処理する

.NET の WebBrowser を拡張したクラス

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Reflection;

namespace WebBrowserExtended
{
  public class MyWebBrowser : WebBrowser
  {
    // WebBrowser の AxtiveX への参照
    private IWebBrowser2 axIWebBrowser2;

    // WebBrowser の AxtiveX が作成されたとき呼び出される
    [PermissionSet(SecurityAction.LinkDemand, 
      Name = "FullTrust")]
    protected override void 
      AttachInterfaces(object nativeActiveXObject)
    {
      this.axIWebBrowser2 = 
        (IWebBrowser2)nativeActiveXObject;
      base.AttachInterfaces(nativeActiveXObject);
    }

    [PermissionSet(SecurityAction.LinkDemand, 
      Name = "FullTrust")]
    protected override void DetachInterfaces()
    {
      this.axIWebBrowser2 = null;
      base.DetachInterfaces();
    }

    public object Application
    {
      get
      {
        if ((this.axIWebBrowser2 == null))
        {
          throw new 
            AxHost.InvalidActiveXStateException(
              "Application",
              AxHost.ActiveXInvokeKind.PropertyGet);
        }

        // この Application プロパティは COM の
        // マネージラッパー
        return this.axIWebBrowser2.Application;
      }
    }

    public bool RegisterAsBrowser
    {
      get
      {
        if ((this.axIWebBrowser2 == null))
        {
          throw new 
            AxHost.InvalidActiveXStateException(
              "RegisterAsBrowser",
              AxHost.ActiveXInvokeKind.PropertyGet);
        }

        // この RegisterAsBrowser プロパティは
        // COM のマネージラッパー
        return this.axIWebBrowser2.RegisterAsBrowser;
      }
      set
      {
        if ((this.axIWebBrowser2 == null))
        {
          throw new 
            AxHost.InvalidActiveXStateException(
              "RegisterAsBrowser",
              AxHost.ActiveXInvokeKind.PropertySet);
        }

        // この RegisterAsBrowser プロパティは
        // COM のマネージラッパー
        this.axIWebBrowser2.RegisterAsBrowser = value;
      }
    }

    // シンクオブジェクトへの参照
    private MyWebBrowserEventSink sink;

    // HTTP 通信の cookie とは関係ないので注意
    private AxHost.ConnectionPointCookie cookie;

    // シンクをサブスクライバ・リストに追加
    [PermissionSetAttribute(SecurityAction.LinkDemand, 
      Name="FullTrust")]
    protected override void CreateSink() 
    {
      base.CreateSink();

      if ((this.axIWebBrowser2 == null)) 
      {
        throw new AxHost.InvalidActiveXStateException(
            "CreateSink",
            AxHost.ActiveXInvokeKind.MethodInvoke);
      }

      this.sink = new MyWebBrowserEventSink(this);
      this.cookie = new AxHost.ConnectionPointCookie(
                          this.axIWebBrowser2,
                          this.sink, 
                          typeof(DWebBrowserEvents2));
    }

    // シンクのサブスクライブを解除
    [PermissionSetAttribute(SecurityAction.LinkDemand, 
      Name="FullTrust")]
    protected override void DetachSink() 
    {
      if (cookie != null) 
      {
        this.cookie.Disconnect();
        this.cookie = null;
      }
      base.DetachSink();
    }

    // NewWindow2 イベントの定義
    public event NewWindow2EventHandler NewWindow2;

    // .NET 側の NewWindow2 イベントを発動するメソッド
    protected virtual void 
      OnNewWindow2(NewWindow2EventArgs e) 
    {
      if ((this.NewWindow2 != null)) 
      {
        this.NewWindow2(this, e);
      }
    }

    // コネクションポイントからの呼び出しを受け取る
    // クライアント・シンクのクラス定義
    [ClassInterface(ClassInterfaceType.None)]
    public class MyWebBrowserEventSink :
      StandardOleMarshalObject, DWebBrowserEvents2
    {
      private MyWebBrowser browser;

      public MyWebBrowserEventSink(MyWebBrowser browser)
      {
        this.browser = browser;
      }

      // COM ソースから発生したイベントから呼び出される
      // メソッド
      public void 
        NewWindow2(ref object ppDisp, ref bool cancel)
      {
        NewWindow2EventArgs e = 
          new NewWindow2EventArgs(ppDisp, cancel);

        // .NET 側の NewWindow2 イベントを発動
        this.browser.OnNewWindow2(e);

        ppDisp = e.PpDisp;
        cancel = e.Cancel;
      }
    }
  }

  // NewWindow2 イベントのハンドラのデリゲート
  public delegate void NewWindow2EventHandler(object sender, 
    NewWindow2EventArgs e);

  // NewWindow2 イベントハンドラ引数のクラス定義
  public class NewWindow2EventArgs : EventArgs
  {        
    public object PpDisp { get; set; }
    public bool Cancel { get; set; }
        
    public NewWindow2EventArgs(object ppDisp, bool cancel)
    {
      this.PpDisp = ppDisp;
      this.Cancel = cancel;
    }
  }

  // DWebBrowserEvents2 インターフェイスの NewWindow2 メ
  // ソッド、IWebBrowser2 インターフェイスの Application
  // プロパティと RegisterAsBrowser プロパティをインポー
  // ト(つまり、マネージラッパーをコンパイル時に生成)。
  // ComImport, InterfaceType, Guid 指定は必須らしい。
  [ComImport, 
  InterfaceType(ComInterfaceType.InterfaceIsIDispatch), 
  Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D")]
  public interface DWebBrowserEvents2
  {
    [DispId(0xfb)]
    void NewWindow2(
      [In, Out, MarshalAs(UnmanagedType.IDispatch)]
      ref object ppDisp, 
      [In, Out, MarshalAs(UnmanagedType.VariantBool)] 
      ref bool Cancel);
  }

  [ComImport, 
  Guid("D30C1661-CDAF-11D0-8A3E-00C04FC9E26E"),
  InterfaceType(ComInterfaceType.InterfaceIsIDispatch), ]
  public interface IWebBrowser2
  {
    [DispId(200)]
    object Application
    {
      [return: MarshalAs(UnmanagedType.IDispatch)]
      get;
    }

    [DispId(0x228)]
    bool RegisterAsBrowser
    {
      get;
      set;
    }
  }
}

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WebBrowserExtended
{
  public partial class Form1 : Form
  {
    private MyWebBrowser browser;
        
    public Form1()
    {
      browser = new MyWebBrowser();
            
      InitializeComponent();
                        
      browser.Dock = DockStyle.Fill;
      this.Controls.Add(browser);
      browser.NewWindow2 += 
        new NewWindow2EventHandler(browser_NewWindow2);
    }

    private void button1_Click(object sender, EventArgs e)
    {
      browser.Navigate(
        "http://msdntestnew/159-HyperLinkToPdf.aspx");
    }

    private void browser_NewWindow2(object sender, 
      NewWindow2EventArgs e)
    {
      Form1 frmWB = new Form1();

      // WebBrowser.AttachInterfaces メソッドは Visible
      // プロパティを true にすると呼び出される。なので、
      // ここで設定しないと RegisterAsBrowser プロパティ、
      // Application プロパティで例外がスローされてしまう。
      frmWB.Visible = true;

      frmWB.browser.RegisterAsBrowser = true;
      e.PpDisp = frmWB.browser.Application;
    }
  }
}

Tags:

.NET Framework

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年3月  >>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar