# # Copyright (c) 2002, 2003, 2014 Ward Wouts # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # require 'tempfile' class YencError < RuntimeError; end class YEnc class << self Encoding.default_external="ISO-8859-1" Encoding.default_internal="ISO-8859-1" #Encoding.default_external="ASCII-8BIT" #Encoding.default_internal="ASCII-8BIT" Debuglevel = 1 @@ymap = {} def ydecode(data, outfile=nil) if @@ymap.empty? (-106..255).each do |b| @@ymap[b]=((b-42)%256) end end case data.class.to_s when "Array" puts "Calling _ydecode_array" if Debuglevel>0 mode, filename, body = _ydecode_array(data) when "File", "Tempfile" unless outfile puts "ydecode: need outfile" exit end puts "Calling _ydecode_file" if Debuglevel>0 mode, filename, body = _ydecode_file(data, outfile) else puts "Funny stuff in ydecode. Data of class \"#{data.class.to_s}\"" end return mode, filename, body end def _ydecode_line(line) i = 0 ostr = '' esc = false line.each_byte do |c| next if c == 13 or c == 10 if c == 61 and not esc #escape character hit goto the next one esc = true next else if esc esc = false c = c - 64 end if c.between?(0,41) decoded = c + 214 else decoded = c - 42 end end ostr << decoded end return ostr end def _ydecode_file(file, outfile) mode = 0600 # mode is a bit stupid with yencoding... it don't get it filename = "unknown" lines = file.pos file.pos = 0 bytes = 0 total = 0 oldpartbegin = 0 oldpartend = 0 search_begin = false skip = false closure = true while (! file.eof) line = file.gets print "line: #{line}" if Debuglevel > 1 if line.match(/^\=ybegin\s+(.*line\=.*)/) m = $1 puts " #{Thread.current.inspect} ybegin match; rest: #{m}" if Debuglevel > 0 if m.match(/^\s*(part\=(\d+)\s+)?(total\=(\d+)\s+)?(line\=(\d+))(\s*size\=(\d+))(\s*name=(.*?\S))\s*$/) part = $2.to_i total = $4.to_i linesize = $6.to_i totalsize = $8.to_i filename = $10 if Debuglevel > 0 print "found beginning" if part != nil print " of part #{part}" end if total != nil print " of #{total}" end puts ", linesize = #{linesize}, size = #{totalsize}, filename = #{filename}" end break else puts "not a valid yenc begin line" end end end if file.eof puts "Not yencoded!" return false end while (! file.eof) puts "at #{file.pos} need to go to #{lines}" if Debuglevel > 1 line = file.gets line.chop! # if ! line.valid_encoding? # line = line.encode("UTF-16be", :invalid=>:replace, :replace=>"?").encode('UTF-8') # end if line.match(/^=yend\s+(.*)\Z/) closure = true m = $1 m.match(/(\s*size=(\d+)\s+)(\s*part=(\d+))?(\s+crc32=(\S+))?/) size = $2.to_i part = $4.to_i crc = $6 if size != bytes puts "#{Thread.current.inspect} part size mismatch, is #{bytes}, should be #{size}" end if part == nil return mode, filename end total += bytes if total >= totalsize if total != totalsize puts "#{Thread.current.inspect} total size mismatch, is #{total}, should be #{totalsize}" end return mode, filename end search_begin = true bytes = 0 puts " #{Thread.current.inspect} yended" if Debuglevel > 0 next end if search_begin && line.match(/^\=ybegin\s+(.*)\Z/) closure = false m = $1 search_begin = false if m.match(/^\s*(part\=(\d+)\s+)?(total\=(\d+)\s+)?(line\=(\d+))(\s*size\=(\d+))(\s*name=(.*?\S))\s*$/) part = $2.to_i total = $4.to_i linesize = $6.to_i totalsize = $8.to_i filename = $10 puts "found beginning of part #{part}, linesize = #{linesize}, size = #{totalsize}, filename = #{filename}" if Debuglevel > 0 end next end if search_begin == true next end if line.match(/^=ypart\s+(\s*begin=(\d+))(\s+end=(\d+))/) closure = false skip = false b = $2 e = $4 puts " #{Thread.current.inspect} next part begin #{b}, end #{e}" if b.to_i == oldpartbegin && e.to_i == oldpartend puts "Skipping duplicate part" skip = true next end if b.to_i == oldpartend + 1 oldpartend = e.to_i oldpartbegin = b.to_i else raise PermError, "#{Thread.current.inspect} Parts not continuous! last end #{oldpartend}, begin #{b}" end next end # This seems to be a common 'error' - maybe I misunderstand the spec or # something # if line.length != linesize # puts "linesize mismatch, was #{line.length}, should be #{linesize}..." # end if !skip puts "line: #{line}" if Debuglevel > 1 ostr = _ydecode_line(line) puts "ostr: #{ostr}" if Debuglevel > 1 outfile << ostr bytes += ostr.length end end puts "No \"=yend\" found!!!" if ! closure return mode, filename, outfile end def _ydecode_array(data) decode = "" mode = 0600 filename = "unknown" oldpartend = 0 oldpartbegin = 0 c = 0 lines = data.length bytes = 0 percent = 0 mark = lines/100 i = 0 while (i < data.length) if data[i].match(/^\=ybegin\s+(.*line\=.*)/) m = $1 puts " #{Thread.current.inspect} ybegin match; rest: #{m}" if Debuglevel > 0 if m.match(/^\s*(part\=(\d+)\s+)?(total\=(\d+)\s+)?(line\=(\d+))(\s*size\=(\d+))(\s*name=(.*?\S))\s*$/) part = $2.to_i total = $4.to_i linesize = $6.to_i size = $8.to_i filename = $10 puts " #{Thread.current.inspect} found beginning, linesize = #{linesize}, size = #{size}, filename = #{filename}" if Debuglevel > 0 i += 1 break else puts "not a valid yenc begin line" end end i += 1 end unless (i < data.length) puts "Not yencoded!" return false end while (i < data.length) line = data[i] line.chomp!("\n") line.chomp!("\r") puts "at #{i} need to go to #{data.length}" if Debuglevel > 1 print "line: #{line}" if Debuglevel > 1 i += 1 if line.match(/^\=yend(\s+size=(\d+))(\s+crc32=(\S+))?/) size = $2.to_i crc = $4 if size != decode.length puts " #{Thread.current.inspect} size mismatch, was #{decode.length}, should be #{size}" end dec = [ decode ] return mode, filename, dec end if line.match(/^=ypart\s+(\s*begin=(\d+))(\s+end=(\d+))/) skip = false b = $2 e = $4 puts " #{Thread.current.inspect} next part begin #{b}, end #{e}" if b.to_i == oldpartbegin && e.to_i == oldpartend puts "Skipping duplicate part" skip = true next end if b.to_i == oldpartend + 1 oldpartend = e.to_i oldpartbegin = b.to_i else raise PermError, "#{Thread.current.inspect} Parts not continuous! last end #{oldpartend}, begin #{b}" end next end # This seems to be a common 'error' - maybe I misunderstand the spec or # something # if line.length != linesize # puts "#{i}: linesize mismatch, was #{line.length}, should be #{linesize}..." # end if !skip print "line: #{line}" if Debuglevel > 1 ostr = _ydecode_line(line) decode << ostr bytes += ostr.length end end puts "${i}: no \"=yend\" found!!!" dec = [ decode ] return mode, filename, dec end def is_yencoded(data) if data.to_s =~ /=ybegin/m return true else return false end end def get_filename(data) i = 0 while i < data.length line = data[i] if line.match(/=ybegin\s*(part\=(\d+)\s+)?(total\=(\d+)\s+)?(line\=(\d+))(\s*size\=(\d+))(\s*name=(.*?\S))\s*$/m) return $10 end i += 1 end return false end end # class end