VBScriptをエンコードしたVBEをHTAに使用する

 数年前にHTA+VBScriptで簡単に作った画面ツールに、メールを送信する機能を追加する事になりましたが、送信に使用するSMTPが認証を必要とするので、パスワードは暗号化しなくてはなりません。

 VBScriptだと、暗号仕様やIV、キーがスクリプトなので読めてしまうという事。どうしたものかと、下記を発見。スクリプトをエンコードし、読めない形に出来る模様。知らなかった。。

[blogcard url=”https://qiita.com/yoshi389111/items/7e4cfcf95ad986c56dcf”]

 やろうと思えばデコードも出来てしまうようですが、基本的に要員専用の業務端末なので、まあそこまでは気にしないという事にしときます。

 下記のようにHTA側には難読化していないこれまでのVBSとエンコードしたVBEの2つのスクリプトを指定して使用します。

<script language=”VBScript” src=”easytool.vbs”>
</script>
<script language=”VBScript.Encode” src=”crypt.vbe”>
</script>

 

 IEサポート終了のアナウンスがあり、そろそろHTAも終了かと思いきや、まだ大丈夫そうです。。

apache.commons.csvでinvalid char between encapsulated token and delimiter

 だいぶ前に作ったapache.commons.csvとapache.poiを使ったCSVファイルからエクセル出力処理で、

java.io.IOException: (line 3) invalid char between encapsulated token and delimiter

 が発生していました。原因は、ダブルクォーテーションで括っているデータ項目の中にダブルクォーテーションが混ざっていたからなのですが、データを出力する側で対処するのが出来そうにないので、CSVファイルを読み込む所で何とかならんものかと検討。
 
 CSVファイルを読む箇所は下記のようにInputStreamが渡され、CSVRecordリストを取得して色々やるという内容です。

    public static int parseCSV(InputStream input){
	BufferedReader in = new BufferedReader(new InputStreamReader(input));

	CSVParser parser = CSVFormat
	        .EXCEL
	        .withIgnoreEmptyLines(true)
	        .withIgnoreSurroundingSpaces(false)
	        .withRecordSeparator(System.getProperty("line.separator"))
	        .withDelimiter(',')
	        .withEscape('\\')
	        .withQuote('"')
	        .parse(in);
	List<CSVRecord> recordList = parser.getRecords();

//以下、略

 殆どの場合で問題は発生しないので、出来れば失敗した場合のみリトライ出来ればいいかなと、データ内に混ざりこんだダブルクォーテーションをエスケープするようにしてみます。正規表現での変換を3段階かますことで、一部ケース(データ内にカンマとダブルクォーテーションが連続した場合)は問題ですが、今回はまあこの位で。。

 次に失敗した後のフォローとなるので、読込が進んでしまっているInputStreamを何とかしなければなりません。結局、IOUtilsでテンポラリファイルにコピーしてから読み込むようにしました。

    public static int parseCSV(InputStream input){
//	BufferedReader in = new BufferedReader(new InputStreamReader(input));

        File fstream = File.createTempFile("csv",".txt");
        FileOutputStream out = new FileOutputStream(fstream);
        IOUtils.copy(input, out);
        out.close();

        List<CSVRecord> recordList;

        try(BufferedInputStream bf = new BufferedInputStream(new FileInputStream(fstream.getAbsoluteFile()));
            BufferedReader in = new BufferedReader(new InputStreamReader(bf));)
        {
            CSVParser parser = CSVFormat
                    .EXCEL
                    .withIgnoreEmptyLines(true)
                    .withIgnoreSurroundingSpaces(false)
                    .withRecordSeparator(System.getProperty("line.separator"))
                    .withDelimiter(',')
                    .withEscape('\\')
                    .withQuote('"')
                    .parse(in);

            recordList = parser.getRecords();
        }
        catch(Exception e){
            logger.error("retry parse tmpfile:" + fstream.getAbsoluteFile(),e );
            BufferedInputStream bf = new BufferedInputStream(new FileInputStream(fstream.getAbsoluteFile()));
            BufferedReader in = new BufferedReader(new InputStreamReader(bf,"UTF-8"));
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = in.readLine()) != null) {
                line.replaceAll("\\\\", "");
                line = line.replaceAll("(?!(^|,|\",))\"(?!$|\")","\\\\\"");
                line = line.replaceAll(",\\\\\"",",\"");
                sb.append(line+System.getProperty("line.separator"));
            }
            CSVParser parser = CSVFormat
                    .EXCEL
                    .withIgnoreEmptyLines(true)
                    .withIgnoreSurroundingSpaces(false)
                    .withRecordSeparator(System.getProperty("line.separator"))
                    .withDelimiter(',')
                    .withEscape('\\')
                    .withQuote('"')
                    .parse(new StringReader(sb.toString()));

            recordList = parser.getRecords();
            bf.close();
            in.close();
        }
        fstream.deleteOnExit();
//以下、略

JavaMailメール送信でSMTPサーバを指定するもlocalhostへ接続

 メール送受信をする処理が動いているサーバを別のインフラに移動したいという事で、移行作業し稼働させたところ、メール送信が出来なくなっている事が判明。

 正確には、メール送信処理は複数あり、単純なJavaアプリとして稼働しているプロセスでは問題が無く、SpringbootでWebサービスとして稼働している処理のみが問題となっています。どちらも元のサーバ上では何ら問題なく動作していました。新環境ではJavaアプリのテストはしてましたが、問題無かったのでSpringboot側は割愛してしまいました。なお、Javaアプリと比べて、Springboot側はjavamailのバージョンがSpringbootのバージョンに引きずられて微妙に古い状況(1.6.1)。

 アプリ的には正常終了扱いとなっており、どこでメールが止まっているのかと調べてみると、mail.smtp.hostで指定しているSMTPサーバにはメール送信ログは無く、なぜか移行先サーバー内の/var/log/maillogにログが、、
Network is unreachable で終わっています。

 どうやら指定しているSMTPサーバでなく、localhostに対しメール送信要求をしているようです。

 とりあえず、移行先のサーバでpostfixを稼働させる必要は無いので止めてもらい、メール送信テストを行うと、今度は接続出来ないとアプリ側でエラーになりました。

 どうも、、デフォルト設定がlocalhostで、そちらが優先されているようですね。。

アプリ側が
session = Session.getDefaultInstance(props);
だったので、
session = Session.getInstance(props);
に変えて問題は解消しましたが、どこでデフォルト設定されているのかは不明なままでした。。

こちらが参考になりました。影響は軽微で、ちょっと痛い目にあったという感じです。
[blogcard url=”https://qiita.com/ikemo/items/f2d8077cc35f707ad957″]

カテゴリー: Java

slatedocsを試してみた

 APIのドキュメントってどうしようか?
 という話の中で、メンバーからslateがいい感じらしいという推しがあり試してみました。

 とりあえず動きを確認するという事で、DockerHubにあったslateを手っ取り早くdocker pullして動かします。
 
 動きは確認出来たので、swagger-to-slateを使ってswaggerで作成してあったAPI仕様を変換し、作成されたmdファイルをindex.html.mdにリネームしてdocker-composeでボリューム指定します。

version: '3'
services:
  slate:
    image: slatedocs/slate:latest
    volumes:
      - ../slate/source/index.html.md:/srv/slate/source/index.html.md
#あとで      - ../slate/source/includes:/srv/slate/source/includes
    ports:
      - 4567:4567

    command: serve

 各言語向けの説明は別途書き込む必要がありますが、いい感じで表示されています。

 index.html.md内のincludes部分にファイルを追加指定すれば、複数のファイルに分割する事が出来そう(初期インストールでは_errors.mdが配置されています)なので、index.html.md内はイントロや認証関連のみ記載し、includesで複数ファイル構成にするのがよさそうですね。
 

VS Code+Docker php開発環境構築でxdebugバージョンにハマる

 phpで作られた既存システムの開発環境を、VS Code+Dockerで再構築していました。
 情報はたくさんWebで見つかるので、構築はそれ程困りませんでしたが、最後のVS Codeからのデバッグ実行でちっとも止まってくれません。

 phpはたまにしか触らないレベルなので、こうなると一気にハマって先に進みません。

 暫く試行錯誤しながら何となくphpinfoを確認していると、どうもxdebugのバージョンが3になっているようです。
 で、xdebug3でWebを探すと案の定、php.iniの記述がこれまで参考にしていたサイトと違う内容になってました。
[blogcard url=”https://gist.github.com/nishim/fa43bb903d948a6c077afd96ff96df66″]

 ハマった時は落ち着いて状況を確認しないと、無駄な時間が増えてしまいますね。

wordpressのチェックボックスが表示されない

 何時からか忘れましたが、wordpressでチェックボックスを押しても反応が無くなりました。これではチェックされているかが解りません。

 かなり不便なので、いい加減治したいと、何気なくブラウザの開発ツールを見ていたら、チェックボックスを押したタイミングで下記のようなエラーが出ているのを発見。

 あ、以前にセキュリティ対策としてnginxの設定を弄ってましたねえ。それが問題になっているようです。

 nginx.confを見直ししてadd_header Content-Security-Policyのimg-srcにdata:を追加。

 治りましたが、Mozillaのページでは非推奨だって書かれてますね。。

push.jsでWindows10端末に通知してみた

 システム障害時のアラートといった、イベント発生をトリガーに業務対応するようなシーンで、これまでメール受信で対応していましたが、多種のメールに埋もれて気が付けないケースが出てきたという事で、メールに変わる何らかの通知機能は無いかと検討。

 要件としては、対象はWindows10端末のみ、内部的な仕組みな為、外部サービスの使用は不可、イベントはチケットシステムに登録されるので、チケットシステム上に、イベント一覧画面を作成して定期的に画面をリロード、push.jsで通知する形なら、まあ気が付くだろうと試してみました。

 push.js自体の実装自体は簡単で、適当にググってサクッと完成。通知を出したら同じ通知を次は出さないとかは個別に工夫します。

 基本的に通知設定は端末・デバイス側の設定に依存しており、Windows端末ならシステム設定から通知設定をするんですが、それ以外にもブラウザ毎に通知仕様が微妙に異なるようです。FireFoxは通知を複数表示できるが、Chromeは1件だけとか。

 Push.Createコール時にrequireInteraction: trueにした場合、Chromeの通知は閉じるボタンが付き、かなり長時間通知が表示された状態になるようでしたが、Edge(Chromium前?)はそうでもなかったりとか。また、Push.Createのコール時のtimeout設定は、特に効いていないようでした。

 あと、基本的にブラウザが通知を許可してくれるのはlocalhostかhttpsのサイトだけという縛りがありますが、Edgeならhttpサイトでも通知が可能だったのでテストに便利です。

 でも、イベント発生トリガーをメール受信からおきかえられるかは業務利用次第ですね。

SpringBootでZabbixAPIを使ってみる

 Zabbixの作業を自動化したいという事で、ZabbixAPIを使ってみました。対象のZabbixは3.2とちょっと古めのようですが、API自体はあまりバージョンは気にしなくてよさそうでした。ドキュメントが充実しているので助かります。

認証後、2件のホストを指定してホスト情報を取ってくるテストを書いてみました。

	@Test
	public void testZabbixAPI() throws Exception {
		String zabbixUser = "zabbixuser";
		String zabbixPass = "****";
		String zabbixEndpoint = "http://zabbixserver/zabbix/api_jsonrpc.php";

		//認証
		JSONObject json = new JSONObject();
		json.put("user", zabbixUser);
		json.put("password", zabbixPass);
		json.put("userData", "false");
		
		String token = null;
		ResponseEntity<String> response = Request("user.login", json, zabbixEndpoint, token);
		
	    JsonNode root = new ObjectMapper().readTree(response.getBody());
	    
	    token = root.get("result").get("sessionid").asText();

		//ホスト情報取得
		JSONObject target = new JSONObject();
	    String[] chkTarget = {"server01","server02"};
		JSONArray targetArray = new JSONArray(chkTarget);
	    
    	JSONObject param = new JSONObject();
		param.put("output", new JSONArray(Arrays.asList("hostid","host","name")));
		target.put("host", targetArray);
		param.put("filter", target);

		response = Request("host.get", param, zabbixEndpoint, token);
		ObjectMapper mapper = new ObjectMapper();
		ArrayNode arrayHost = (ArrayNode) mapper.readTree(response.getBody()).path("result");
		
	    System.out.println("hosts: " + arrayHost);
    }
	
	private ResponseEntity<String> Request(String method, JSONObject param, String endPoint, String token) throws Exception {
		JSONObject json = new JSONObject();

		json.put("jsonrpc", "2.0");
        json.put("method", method);
        json.put("params", param);
        json.put("id", 1);
		if(token != null){
            json.put("auth", token);
        }
        HttpMethod httpMethod = HttpMethod.POST;
        
	    HttpEntity <String> entity ;
		HttpHeaders headers = new HttpHeaders();
	    headers.add("Content-Type", "application/json-rpc");
	    headers.add("Accept-Language", "ja");
	    
	    if(param == null){
	    	entity = new HttpEntity <String>(headers);
	    }
	    else{
	    	entity = new HttpEntity <String>(json.toString(), headers);
	    }
	    RestTemplate restTemplate = new RestTemplate();
	    restTemplate.getMessageConverters()
        .add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
	    
	    ResponseEntity<String> response = restTemplate.exchange(endPoint, httpMethod, entity, String.class);
	    
		return response;
	}

で、こんな感じで戻ってきます。

hosts: [{“hostid”:”10387″,”host”:”server01″,”name”:”server01_DB”},{“hostid”:”10388″,”host”:”server02″,”name”:”server02_DB”}]

問題無く使えそうなので、要件を整理してから実装してみます。

Webixでイベントを実装する

 今作っているWebのUIに、前回使ったw2uiをまた使おうかと思いましたが、今回対象のUIが他のWebサイトから遷移する前提なので、w2uiでは遷移元のWebサイト画面との違和感が強く、別のJavascriptUIフレームワークを探してみたら、Webixを発見。
 
 すっきりした見た目で違和感なさそうなので、今回はWebixを使ってみることにします。
 
 こちらのサイトに概要が書かれているのでとても参考になりました。

[blogcard url=”http://kumapanda.jp/?p=129″]

 で、UIの形は出来てきたので、イベントの実装をどうやるのかとドキュメントを見ていくと、ボタンのようなUI Controlでは、下記のようにidを指定してイベントを実装するようです。

webix.ready(function(){

   webix.i18n.setLocale("ja-JP");
   webix.ui({
      type: 'wide',
      cols: [
         { view: "button", id: "exec_button", label:"実行", width:300, align:"center"},
      ]
    });

   $$("exec_button").attachEvent("onItemClick", function(ids, some){
       webix.confirm({
          title:"",
          ok:"はい", 
          cancel:"いいえ",
          text: "実行してよろしいですか?"
       }).then(function(result){
          //OK時
       });
   });
});

ドキュメントが充実しているので、とても助かります。

SpringBoot ファイルアップロードでエラーが出る

 前にSpringBootで作ったWebアプリで、たまにファイルアップロードエラーが出ているとの事でログを確認。

 こんなログが吐かれています。

2020-08-03 16:31:28.826 ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/xx].[dispatcherServlet] Servlet.service() for servlet [dispatcherServlet] in context with path [/xx] threw exception [Request processing failed; nested exception is org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request; nested exception is java.io.IOException: The temporary upload location [/tmp/tomcat.1401623156062740234.8080/work/Tomcat/xx/xx] is not valid] with root cause
java.io.IOException: The temporary upload location [/tmp/tomcat.1401623156062740234.8080/work/Tomcat/xx/xx] is not valid

 同事象のコチラを参考にテンポラリの場所を変えてみたのですが、開発端末のWindowsだと、どうも設定した値はデフォルトのテンポラリディレクトリの中に指定したディレクトリに出力しようとしてやっぱりエラーになるような感じでした。

 で、次にコチラを参考にspring.http.multipart.locationとして設定したところ、エラーは解消したようなのですが、非推奨になっています。。

 最後に実行環境のEC2にデプロイしたところ、spring.servlet.multipart.locationで指定してちゃんと動きました。アップロード後にtmpファイルは自動的に浄化されています。

 OSで違うなんて微妙な感じです。。