parse.rb (5871B)
1 #! /usr/bin/env ruby 2 3 require "open3" 4 require "ripl" 5 require "zlib" 6 require "base64" 7 8 class Parser 9 private 10 FLAGS = { 11 isbookmark: 1, maydefer: 2, write: 4, writeunknown: 8, isfixedlength: 0x10, 12 isnullable: 0x20, maybenull: 0x40, islong: 0x80, isrowid: 0x100, 13 isrowver: 0x200, cachedeferred: 0x1000 14 } 15 16 def self.bitmask_to_s enum, val 17 vals = [] 18 enum.each do |n,i| 19 if val & i != 0 20 vals << n 21 val = val & ~i 22 end 23 end 24 vals << val unless val == 0 25 vals.join "," 26 end 27 28 def parse_u8; @out.read(1).ord; end 29 30 def parse_i16; @out.read(2).unpack("s<")[0]; end 31 def parse_u16; @out.read(2).unpack("S<")[0]; end 32 33 def parse_i32; @out.read(4).unpack("l<")[0]; end 34 def parse_u32; @out.read(4).unpack("L<")[0]; end 35 def parse_f32; @out.read(4).unpack("e")[0]; end 36 37 def parse_f64; @out.read(8).unpack("E")[0]; end 38 39 def parse_crazy_bool; parse_u16 != 0; end 40 41 def parse_genstring encoding 42 @out.read(parse_u32).force_encoding encoding 43 end 44 45 def parse_wstring; parse_genstring Encoding::UTF_16LE; end 46 def parse_string; parse_genstring Encoding::UTF_8; end 47 def parse_bin; parse_genstring Encoding::BINARY; end 48 49 def parse_error code 50 case code 51 when 0xff 52 hr = parse_u32 53 msg = parse_wstring 54 printf "HRESULT 0x%x %s\n", hr, msg.encode(Encoding::UTF_8) 55 throw :failed 56 when 0xfe 57 msg = parse_string 58 printf "ERROR %s\n", msg 59 throw :failed 60 else 61 fail "Unknown code #{code}" 62 end 63 end 64 65 def parse_blob 66 buf = "".force_encoding Encoding::BINARY 67 loop do 68 code = parse_u8 69 case code 70 when 0 71 return buf 72 when 1 73 buf << parse_bin 74 else 75 parse_error code 76 end 77 end 78 end 79 80 def self.uniform_method name 81 fun = instance_method name 82 lambda do |inst, *args, &blk| 83 fun.bind_call inst, *args, &blk 84 end 85 end 86 87 def parse_nil; end 88 89 def self.with_len_check exp_len, fun 90 fun = instance_method fun 91 lambda do |inst| 92 len = inst.send :parse_u32 93 fail unless len == exp_len 94 fun.bind_call inst 95 end 96 end 97 98 TYPES = { 99 2 => with_len_check(2, :parse_i16), 100 3 => with_len_check(4, :parse_i32), 101 4 => with_len_check(4, :parse_f32), 102 5 => with_len_check(8, :parse_f64), 103 11 => with_len_check(2, :parse_crazy_bool), 104 # 72 => method(:parse_bin), # GUID 105 128 => uniform_method(:parse_bin), 106 130 => uniform_method(:parse_wstring), 107 } 108 109 # not Struct because braindead ap ignores custom inspect on Structs... 110 class Result 111 attr_accessor :header, :rows 112 def initialize header, rows 113 @header = header 114 @rows = rows 115 end 116 def inspect 117 "Result(cols=#{header.size}, rows=#{rows.size})" 118 end 119 alias :to_s :inspect 120 end 121 ColumnInfo = Struct.new :type, :flags, :name 122 Cell = Struct.new :status, :value 123 124 def parse_rows types 125 0.step do |r| 126 code = parse_u8 127 case code 128 when 0 129 puts "--END" 130 return 131 132 when 1 133 l = [] if @config.save_last 134 types.each_with_index do |t, i| 135 c2 = parse_u8 136 case c2 137 when 0 138 stat = parse_u32 139 parser = TYPES[t] 140 parser = Parser.with_len_check(0, :parse_nil) if stat == 3 141 unless parser 142 parser = Parser.uniform_method :parse_bin 143 puts "Unhandled type #{t}!" 144 end 145 val = parser[self] 146 l << Cell.new(stat, val) if l 147 puts "#{r}/#{i}: #{stat} #{val.inspect}" 148 when 1 149 stat = parse_u32 150 val = parse_blob 151 val.force_encoding Encoding::UTF_16LE if t == 130 # hack 152 l << Cell.new(stat, val) if l 153 154 max = @config.max_blob_display 155 if max && val.size > max 156 val = val[0,max] + "...".encode(val.encoding) 157 end 158 puts "#{r}/#{i}: #{stat} BLOB(#{val.inspect})" 159 else 160 parse_error c2 161 end 162 end 163 @last.rows << l if l 164 165 else 166 parse_error code 167 end 168 end 169 end 170 171 def send_cmd cmd 172 cmd = cmd.encode Encoding::UTF_16LE 173 @in.write [0, cmd.bytesize, cmd].pack "cla*" 174 end 175 176 def parse_cmd_res 177 code = parse_u8 178 case code 179 when 0 180 @last = nil if @config.save_last 181 puts "OK" 182 183 when 1 184 puts "RES" 185 n_cols = parse_u32 186 @last = Result.new([], []) if @config.save_last 187 types = n_cols.times.map do |i| 188 type = parse_u32 189 flags = parse_u32 190 name = parse_wstring 191 @last.header << ColumnInfo.new(type, flags, name) if @config.save_last 192 puts "#{i}: #{type}/#{Parser.bitmask_to_s FLAGS, flags} #{name.inspect}" 193 type 194 end 195 196 parse_rows types 197 else 198 parse_error code 199 end 200 end 201 202 class Config 203 def initialize 204 @max_blob_display = 32 205 @save_last = true 206 end 207 208 attr_accessor :max_blob_display 209 attr_accessor :save_last 210 end 211 212 public 213 def initialize stdin, stdout 214 @config = Config.new 215 @in = stdin 216 @out = stdout 217 end 218 attr_reader :config, :last 219 220 def sql cmd 221 send_cmd cmd 222 catch(:failed) { parse_cmd_res } 223 @last if @config.save_last 224 end 225 end 226 227 class Cmds 228 def initialize *args; @parser = Parser.new *args; end 229 (Parser.public_instance_methods - Object.methods).each do |m| 230 define_method m do |*args, &blk| 231 @parser.send m, *args, &blk 232 end 233 end 234 235 def gunzip_base64 data 236 Zlib.gunzip Base64.decode64(data)[4..-1] 237 end 238 239 def parse_xml data 240 require "nokogiri" 241 Nokogiri.XML Zlib.gunzip(Base64.decode64(data)[4..-1]), &:nocdata 242 end 243 end 244 245 fail "Usage: #$0 file" unless ARGV.size == 1 246 Open3.popen3 "./load.exe", ARGV[0] do |stdin, stdout, thr| 247 c = Cmds.new stdin, stdout 248 Ripl.start binding: c.instance_eval { binding } 249 stdin.close 250 end