# $Dwarf: yenc.rb,v 1.13 2005/01/31 14:08:33 ward Exp $
# $Source$

#
# Copyright (c) 2002, 2003 Ward Wouts <ward@wouts.nl>
#
# 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

Debuglevel = 0

def ydecode(data, outfile=nil)
	unless @ymap
		@ymap = {}
		(-106..255).each do |b|
			@ymap[b]=((b-42)%256)
		end
	end

	case data.class.to_s
		when "Array"
			print "Calling _ydecode_array\n" if Debuglevel>0
			mode, filename, body = _ydecode_array(data)
		when "File", "Tempfile"
			unless outfile
				print "ydecode: need outfile\n"
				exit
			end
			print "Calling _ydecode_file\n" if Debuglevel>0
			mode, filename, body = _ydecode_file(data, outfile)
		else
			print "Funny stuff in ydecode. Data of class \"#{data.class.to_s}\"\n"
	end
	return mode, filename, body
end

def _ydecode_line(line)
	i = 0
	ll = line.length
	ostr = ''
	while i < ll
		if line[i] == 0x3d
			if i == (ll - 1)
				raise YencError, "Escape char found as last char of line. This is not allowed by the yEnc standard"
			else
				i += 1
				line[i] -= 64
			end
		end
#		begin
			ostr << @ymap[line[i].to_i]
#		rescue TypeError
#			puts "this should not happen!!!"
#			puts "line[i] contents: '#{line[i]}'\n"
#		end
		i += 1
	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

	while (! file.eof)
		line = file.gets
		print "line: #{line}" if Debuglevel > 0
		if  line =~ /^\=ybegin\s+(.*line\=.*)/
			m = $1
			print "ybegin match; rest: #{m}\n"  if Debuglevel > 0
			if m =~ /^\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
					print ", linesize = #{linesize}, size = #{totalsize}, filename = #{filename}\n"
				end
				break
			else
				print "not a valid yenc begin line\n"
			end
		end
	end

	if file.eof
		print "Not yencoded!\n"
		return false
	end

	while (! file.eof)
		print "at #{file.pos} need to go to #{lines}\n" if Debuglevel > 1
		line = file.gets
		line.chop!

		if line =~ /^=yend\s+(.*)\Z/
			m = $1
			m =~ /(\s*size=(\d+)\s+)(\s*part=(\d+))?(\s+crc32=(\S+))?/
			size = $2.to_i
			part = $4.to_i
			crc = $6
			if size != bytes
				print "part size mismatch, is #{bytes}, should be #{size}\n"
			end
			if part == nil
				return mode, filename
			end
			total += bytes
			if total >= totalsize
				if total != totalsize
					print "total size mismatch, is #{total}, should be #{totalsize}\n"
				end
				return mode, filename
			end
			search_begin = true
			bytes = 0
			next
		end
		if search_begin && line =~ /^\=ybegin\s+(.*)\Z/
			m = $1
			search_begin = false
			if m =~ /^\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
				print "found beginning of part #{part}, linesize = #{linesize}, size = #{totalsize}, filename = #{filename}\n" if Debuglevel > 0
			end
			next
		end
		if search_begin == true
			next
		end
		if line =~ /^=ypart\s+(\s*begin=(\d+))(\s+end=(\d+))/
			skip = false
			b = $2
			e = $4
			print "	next part begin #{b}, end #{e}\n"
			if b.to_i == oldpartbegin && e.to_i == oldpartend
				print "Skipping duplicate part\n"
				skip = true
				next
			end
			if b.to_i == oldpartend + 1
				oldpartend = e.to_i
				oldpartbegin = b.to_i
			else
				raise PermError, "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
#			print "linesize mismatch, was #{line.length}, should be #{linesize}...\n"
#		end

		if !skip
			print "line: #{line}" if Debuglevel > 0
			ostr = _ydecode_line(line)
			outfile << ostr
			bytes += ostr.length
		end
	end

	print "No \"=yend\" found!!!\n"
	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] =~ /^\=ybegin\s+(.*line\=.*)/
			m = $1
			print "ybegin match; rest: #{m}\n"  if Debuglevel > 0
			if m =~ /^\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
				print "found beginning, linesize = #{linesize}, size = #{size}, filename = #{filename}\n" if Debuglevel > 0
				i += 1
				break
			else
				print "not a valid yenc begin line\n"
			end
		end
		i += 1
	end

	unless (i < data.length)
		print "Not yencoded!\n"
		return false
	end

	while (i < data.length)
		line = data[i]
		line.chomp!("\n")
		line.chomp!("\r")
		print "at #{i} need to go to #{data.length}\n" if Debuglevel > 1
		print "line: #{line}" if Debuglevel > 0
		i += 1
		if line =~ /^\=yend(\s+size=(\d+))(\s+crc32=(\S+))?/
			size = $2.to_i
			crc = $4
			if size != decode.length
				print "size mismatch, was #{decode.length}, should be #{size}\n"
			end
			dec = [ decode ]
			return mode, filename, dec
		end
		if line =~ /^=ypart\s+(\s*begin=(\d+))(\s+end=(\d+))/
			skip = false
			b = $2
			e = $4
			print "	next part begin #{b}, end #{e}\n"
			if b.to_i == oldpartbegin && e.to_i == oldpartend
				print "Skipping duplicate part\n"
				skip = true
				next
			end
			if b.to_i == oldpartend + 1
				oldpartend = e.to_i
				oldpartbegin = b.to_i
			else
				raise PermError, "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
#			print "#{i}: linesize mismatch, was #{line.length}, should be #{linesize}...\n"
#		end

		if !skip
			print "line: #{line}" if Debuglevel > 0
			ostr = _ydecode_line(line)
			decode << ostr
			bytes += ostr.length
		end
	end

	print "${i}: no \"=yend\" found!!!\n"
	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 =~ /=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
