Delphiでファイルタイプフィールドに入力するWebBrowser

WebBrowserコンポーネントを使用する場合、多くの場合、サイトのフォームフィールドに入力する必要があります。 通常のフォームフィールドに問題はなく、それらを入力するための標準的な方法がありますが、ブラウザはフィールドにファイルタイプを入力することを永続的に拒否します。 この理由は、ユーザー保護です。 ブラウザがこのフィールドでユーザーのコンピューター上のファイルへのパスを自由に置き換えることを許可した場合、サイトのページに組み込まれた単純なJavaScriptを使用して、任意のファイルを簡単に削除できます。



しかし、プログラムはブラウザを使用するため、ユーザーファイルにアクセスできるため、理論的にはこのような保護は機能しません。 しかし、私たちは持っているものを持っているので、回避策を探す必要があります。



問題を解決するためのオプション



  1. ファイル送信フィールドを除くフォームのすべての必要なフィールドに入力し、データ送信イベントをインターセプトして、POSTを変更します。 実際、必要なファイルに関するデータをリクエストに追加するだけです
  2. すべてのブラウザーCookieを取得する必要があることを忘れずに、手動で(ブラウザーを使用せずに)POST要求を生成して送信し、要求を送信した後、新しい値をブラウザーに書き直します


私はすぐに2番目のオプションである多くの作業と落とし穴を捨てました。 最初に停止しました。 しかし、これも不運です:リクエストのインターセプト中に、リクエスト全体が渡されるのではなく、フィールドに入力しなかったという事実にもかかわらず、ファイルのコンテンツがあるべき場所に行く部分のみが渡されることが判明しました。 Googleは、これはCOMオブジェクトを作成したMicrosoftプログラマーの誤りであり、新しいバージョンではこのエラーは修正されていないと示唆しました(IE 9でテスト済み)。



少し恥ずかしがり屋で、この問題に対する完璧な解決策はないことに気づきました(私は一般的に既製の解決策については黙っています)、したがって、唯一の受け入れられるオプションは、POST要求を自分で完全に生成し、ブラウザーを使用して送信することです(問題に対する最初と2番目の解決策の組み合わせが判明します)将来便利に使用できるように、そのようなリクエストを生成および送信するための便利なクラスをすぐに書くことにしました(記事の最後にあるソースコードへのリンク)。



クラス開発



まず、私は自分でクラスのスキームを決定しました。 要するに、パラメータの配列の作成、リクエストの生成、リクエストの送信です。 また、パラメーター配列の要素の構造についても説明しました。



TCustomPostParam = record name : string; value : string; filename : string; content_type : string; end;
      
      







次に、パラメーターの配列を埋める方法を決定しました。 私は3を得た:

  1. パラメータを追加することは、テキストフィールドまたはファイルにかかわらず、パラメータを追加できる汎用的な方法です。
  2. ファイルの追加は、あらゆるタイプのファイルを追加するための便利な方法です。 ファイルの内容を考慮し、必要なデータを以前のメソッドに送信します
  3. テキストファイルの追加 - テキストファイルを追加する方法。前のファイルとの唯一の違いは、コンテンツタイプ自体を埋めることです。


次のことが判明しました。



 function TCustomPostDataSender.AddParam(name, value: string; content_type : string = ''; filename : string = ''):integer; var h : integer; begin SetLength(FParams, Count() + 1); h := high(FParams); FParams[h].name := name; FParams[h].value := value; FParams[h].content_type := content_type; FParams[h].filename := filename; CheckBoundary(name + ' ' + value + ' ' + content_type); result := h; end; procedure TCustomPostDataSender.AddFile(name, path, file_content_type: string); var s : TStringStream; buf : string; begin if not FileExists(path) then exit; file_content_type := Trim(file_content_type); if file_content_type = '' then file_content_type := 'text/plain'; s := TStringStream.Create; try s.LoadFromFile(path); buf := s.DataString; if Pos('text', LowerCase(file_content_type)) = 1 then CheckUTF8(buf); AddParam(name, buf, file_content_type, ExtractFileName(path)); finally s.Free; end; end; procedure TCustomPostDataSender.AddTextFile(name, path: string); begin if not FileExists(path) then exit; if LowerCase(ExtractFileExt(path)) = '.xml' then AddFile(name, path, 'text/xml') else AddFile(name, path, 'text/plain'); end;
      
      







次に、リクエストを生成して送信する機能を作成します。 私はこれを得ました:



 function TCustomPostDataSender.POST():integer; var sURL, sFlags, sTargetFrame, sPostData, sHeaders : OleVariant; i : integer; buf : string; begin Result := 1; if FURL = '' then exit; try sURL := FURL; sFlags := 64; //      ,       sHeaders := FGetHeader(); buf := ''; for i := 0 to Count() - 1 do begin if i = 0 then buf := buf + '--' + FBoundary + sLineBreak; buf := buf + 'Content-Disposition: form-data; name="'+ FParams[i].name +'"'; //   if FParams[i].filename <> '' then buf := buf + '; filename="'+ FParams[i].filename +'"' + sLineBreak + 'Content-Type: ' + FParams[i].content_type; buf := buf + sLineBreak + sLineBreak; buf := buf + FParams[i].value + sLineBreak; buf := buf + '--' + FBoundary; if i = Count() - 1 then buf := buf + '--'; buf := buf + sLineBreak; end; sPostData := StringToVariantArray(buf); WB.Navigate2(sURL, sFlags, sTargetFrame, sPostData, sHeaders); Clear(); Result := 0; except Result := 999; end; end;
      
      







残りのメソッドの詳細については触れません。興味のある方は、クラス全体のソースコードをより詳細に調べることができます。 メソッドは完全に自給自足であるとしか言えません:境界自体の正確さと一意性を監視し、必要なヘッダーを生成し、すべてのデータを目的の型に変換します。



戻り値については、このタイプの場合、リクエストが送信された場合は関数が0を返し、エラーが発生した場合は999を返すため、 boolean型を作成することをお勧めします(さらに、ユーザーにエラーテキストを表示しませんが、これも非常に悪いです)私の防御では、このメソッドは将来のために開発されたと言います-各エラーには独自のコードがあり、このコードだけで、プログラムは行動する方法とユーザーに通知するものを決定する必要があります。 いずれにせよ、これを変更することは初心者のプログラマにとっても難しくありません。



使用例



 var CustomRequest : TCustomPostDataSender; begin CustomRequest := TCustomPostDataSender.Create(WebBrowser); try CustomRequest.AddTextFile('import', FILE_PATH); CustomRequest.AddParam('action', 'save'); CustomRequest.AddParam('submit', 'Import'); CustomRequest.SetURL('http://site.ru/import-file.php'); CustomRequest.SetContentType('multipart/form-data'); CustomRequest.POST(); finally CustomRequest.Free(); end; end;
      
      







おわりに



完全なソースコードへのリンク: pastebin.com/DgykYAxK

md5hashへのリンク( 作業にDCPcryptライブラリが必要です): pastebin.com/cTS3ttwZ



このクラスは、Delphiバージョン2009+(つまり、Unicodeバージョンのコンパイラ)およびEmbeddedWBコンポーネント(標準のTWebBrowserの代わりに使用することを強くお勧めします)の下で動作します。



この記事は、デルフィストだけでなく、Microsoft Internet ExplorerブラウザーのCOMオブジェクトを使用する他のプログラミング言語でも説明されている問題に役立つ可能性があり、ソリューション自体(いくつかのスキルを備えた)を別の言語に翻訳することは難しくありません。



All Articles