Java 版 CSV パーサ (ダブルクォーテーション対応)
CSV に何のこだわりがあるかわかりませんが、今回は Java バージョンです。
デフォルトのパーサが見当たりませんでしたので、自作しました。
package file; import java.io.File; import java.io.FileReader; import java.io.BufferedReader; import java.util.regex.Pattern; import java.util.ArrayList; import java.io.FileNotFoundException; import java.io.IOException; import gadgets.Debug; public class CSVParser { public static String[][] parse ( String filename ) { String[][] ret = null; ArrayList<String[]> tmp = new ArrayList<String[]>(); String line; String[] row; int i = 0; Pattern pattern = Pattern.compile( "^\".*\"$" ); try { BufferedReader br = new BufferedReader( new FileReader( new File( filename ) ) ); while ( ( line = br.readLine() ) != null ) { row = line.split( ",(?=(([^\"]*\"){2})*[^\"]*$)", -1 ); for ( i = 0; i < row.length; i ++ ) { if ( pattern.matcher( row[ i ] ).find() ) { row[ i ] = row[ i ].substring( 1, row[ i ].length() - 1 ).replace( "\"\"" , "\"" ); System.out.println( row[ i ] ); } } tmp.add( row ); } ret = new String[ tmp.size() ][]; for ( i = 0; i < tmp.size(); i ++ ) { ret[ i ] = tmp.get( i ); } br.close(); } catch ( FileNotFoundException e ) { Debug.exit( filename + " is not found." ); } catch ( IOException e ) { Debug.exit( "faild in read " + filename ); } return ret; } }
リクエストのフィルタリングクラス
リクエストをフィルタリングするクラスを作ってみました。
<?php class Request { const GET = 0; const POST = 1; const COOKIE = 2; const SERVER = 3; const INT = 0; const STR = 1; const REG = 2; const HEX = 3; const TXT = 4; const DATE = 5; const EMAIL = 6; const PASSWORD = 7; const ASCII = 8; const CHECK = 9; const NOTEMPTY = 10; public static $Bans = array( 'http', '.net', '.com', '.jp', '.co.jp' ); private static function AllocateMethod ( $method ) { switch ( $method ) { case self::GET : return $_GET; case self::POST : return $_POST; case self::COOKIE : return $_COOKIE; case self::SERVER : return $_SERVER; } } public static function Checked ( $method, $name ) { $ary = self::AllocateMethod( $method ); return isset( $ary[ $name ] ) && strtolower( $ary[ $name ] ) == 'on'; } public static function SpecialCharacterCheck ( $method ) { $ary = self::AllocateMethod( $method ); foreach ( $ary as $name => $value ) { if ( preg_match( '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', $value ) ) { $name = Log::Escape( $name ); $value = Log::Escape( $value ); Log::Record( "Request containing special characters. ({$name}={$value})" ); header( "HTTP/1.0 403 Forbidden" ); exit; } } } public static function SafeString ( $str ) { return !preg_match( '/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/', $str ); } public static function Filter ( $method, $name, $type, $option1 = null, $option2 = null ) { $ary = self::AllocateMethod( $method ); $ret = false; if ( isset( $ary[ $name ] ) && self::SafeString( (binary)$ary[ $name ] ) ) { $value = $ary[ $name ]; switch ( $type ) { case self::INT : if ( $ret = $value == (string)(int)$value ) { if ( $option1 ) $ret = $option1 <= $value; if ( $option2 ) $ret = $value <= $option2; } break; case self::STR : case self::PASSWORD : $ret = mb_check_encoding( $value ); break; case self::TXT : $ret = mb_check_encoding( $value ) && TextCheck::NotContainBan( $option1, $value, static::$Bans ); break; case self::REG : $ret = preg_match( $option1, $value ); break; case self::HEX : $ret = preg_match( '/^[0-9a-fA-F]*$/', $value ); break; case self::DATE: $ret = strtotime( $value ) !== false; break; case self::EMAIL: $ret = empty( $value ) || filter_var( $value, FILTER_VALIDATE_EMAIL ); break; case self::ASCII: $ret = preg_match( '/^[\x00-\x7F]*$/', $value ); case self::CHECK: $ret = empty( $value ) || ( strtolower( $value ) == 'on' || strtolower( $value ) == 'off' ); break; case self::NOTEMPTY: $ret = !empty( $value ); break; } } if ( $ret == false && !empty( $value ) ) { $value = Log::Escape( $value ); $name = Log::Escape( $name ); Log::Record( "{$name} is Illegal value.({$name}={$value})" ); } return $ret; } public static function FilterAll ( $methods ) { $bit = 1; $code = 0; foreach ( $methods as $method => $names ) { foreach ( $names as $name => $options ) { if ( self::Filter( $method ,$name ,$options[ 0 ] ,isset( $options[ 1 ] ) ? $options[ 1 ] : null ,isset( $options[ 2 ] ) ? $options[ 2 ] : null ) == false ) $code |= $bit; $bit <<= 1; } } return $code; } }
使い方はこんな感じ
<?php $code = Request::FilterAll ( array ( Request::POST => array ( 'ID' => array ( Request::INT, 1 ) // ID は 1 以上の整数値で ,'NAME' => array ( Request::TXT, $dbh ) // NAME は禁止語句を含まないバイナリセーフな文字列 ,'EMAIL' => array ( Request::EMAIL ) // EMAIL はメールアドレス ) ) );
例えば、ID と EMAIL が引っかかった場合 0b0101 が返ってきます。ですので、
<?php if ( $code & 0x01 ) echo 'IDが不正です'; if ( $code & 0x02 ) echo 'NAMEが不正です'; if ( $code & 0x04 ) echo 'EMAILが不正です';
のようなことができます。
PHP で制御文字をエスケープ
制御文字を削除する記事はよく見かけるけど、エスケープする記事がなかったので自作
<?php public static function Escape( $str ) { return preg_replace_callback( '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', function ( $m ) { return '0x' . strtoupper( implode( '', unpack( 'H*', $m[ 0 ] ) ) ); }, $str ); }
大文字に変換してるのは、単純に個人的にといいますか、16進数は大文字で書くようにしてます。
水平タブと、改行は残してあります。
これに引っかかるときって、考えると、ISO-2022-JP を使ってるとか、携帯の絵文字?よく調べてないけど
などなど考えられるけど
それ以外と言ったら、やはり悪意のあるコードになる。
ヌルバイトの後にコードを埋め込んだりとか。
PHP関数はバイナリセーフになっているものの、そんなものを読ませたくはない。
引っかかったらログ取って終わり。
検索すると、制御文字を消すコードが多数引っかかるけど、どこで使うのかわからない。
消して処理続行?
いい加減にもほどがある。
もし、特殊文字をユーザーが入力する可能性があるなら、受け入れられませんってアラート表示するだろうし。
消すメリットってなんだろ。
因みにこのコードは、処理続行させるためのものじゃなく、ログをとるだけのためのものです。
何を送ってきたかわかるから、対策もできるでしょ。
htmlentities が近しい動作をするので、それを使ってもいいかもしれません。
個人事業主申請してきました
今日、事情があって予定を少し繰り上げて個人事業主申請してきました。
屋号 Fruits basket
メンバーのご紹介をしたいと思います。
=ユウ=(私) : 屋主 兼 SE
ソウタ : デザイナ
クロスケ : デザイナ
お茶 : 秘書 もとい お茶くみ
冗談です。実質メンバーは私だけ。3方は私の愛する方々です。
事業内容
Banana Chat 新感覚チャットサイト(18禁)
の運営
上記チャットサイトを足掛かりに、事業を展開していく予定です。
PHP で CSV 文字列生成
str_getcsv はあるものの、str_putcsv なる関数がない
ネーミングがおかしい気がしますが str2csv ?
仕方なく自作することに
因みに implode( ',', $array); でいいじゃんって人は帰ってください。
いいんじゃないですか?それで
で、話を戻すと、fputcsv がすぐに思い浮かぶ
そして、バッファも確かあったなと
出力バッファがまず思い浮かんだけど、用途として間違っている
出力バッファ関数郡は魅力的だけど
出力バッファを使ってしまうと
この処理をする前に何か出力されていたら、相当面倒なことになる。
出力バッファなんて使ったら何してんのって話だ。
てことで、しょうがなく、バッファで遠回りして csv 生成
一から、csv 生成関数作ってもいいような気もするけど、その辺は時短ということで
<?php if ( $fh = fopen( 'php://memory', 'w+' ) ) { foreach ( $ary as $line ) { fputcsv( $fh, $line ); } fseek( $fh, 0 ); $str_csv = stream_get_contents( $fh ); fseek( $fh, 0 ); ftruncate( $fh, 0 ); fclose( $fh ); }
サイズが気になる方は
php://temp
を検討してもいいと思います。
今回は、サイズの小さいものに限られていたので、php://memory で
エイヤとやっています。
N-Gram
テキスト検索する必要性が出てきたので、N-Gram プログラム作成。
というか数行・・・
<?php function NGram( $str, $n ) { mb_internal_encoding( 'UTF-8' ); $l = mb_strlen( $str ); $ret = ''; for ( $i = 0; $i < $l; $i ++ ) { $ret .= mb_substr( $str, $i, $n ) . ' '; } retrun trim( $ret ); }
検索してたらこんなのが出てきた
突っ込んでいいですか?
何をしてるのか知らないけど、長いのはいいとして、N-Gram 使うときって基本マルチバイトじゃないんでしょうか・・・
さて、N-Gram ができたら MySQL の FULLTEXT で全文検索どん。
おわり