Viewing the contents of singlerar.py. Use the following link to view the file in raw, non-highlighted format: singlerar.py
#!/usr/bin/env python

""" singlerar.py - checks whether a RAR file is a single archive or the
start of a multi-volume archive.

Usage: singlerar.py <rar file>

The return code is zero if the file is a single archive or the first 
volume of a multi-volume archive, or non-zero if it contains at least one 
file that is continued from a previous volume.

Author: Per Jonsson

This code is in the public domain. You may use it for whatever you want,
as long as you do not hold the author responsible for the consequences of
its usage.

"""

import sys

__version__ = "0.1"

def rar_is_single_or_start_volume(fname):
	"""rar_is_single_or_start_volume(fname) -> boolean/none

	Determines if the file specified by fname is a single (standalone) RAR
	archive or if it is the first file of a multi-volume archive. It is 
	considered so if it does not contain any files that are continued from
	a previous volume.

	Returns one of None, True or False. None is returned if the RAR file
	has an invalid signature (and thus isn't a valid archive file). True or 
	False is returned otherwise, in accordance with the purpose of the 
	function. Exceptions raised when reading the file must be handled by the 
	caller.
	"""
	def data_to_num(data, idx, size):
		"""data_to_num(data, idx, size) -> numeric
		
		Converts a sequence of size bytes starting at index idx in the 
		string data to a long number. The data is assumed to contain the
		number in little-endian format.
		"""
		num = 0L
		shift = 0
		end = idx + size
		while idx < end:
			num += long(ord(data[idx])) << shift
			shift += 8
			idx += 1

		return num

	def get_data(fd, size):
		"""get_data(fd, size) -> string

		Reads size bytes from the file object fd (not a system file descriptor).
		The data is returned as a string. If not all data could be read (i.e.,
		in case of end-of-file), an empty string is returned.
		"""
		data = fd.read(size)
		if len(data) < size:
			return ""
		else:
			return data

	def read_rar_block_head(fd):
		"""read_rar_block_head(fd) -> tuple

		Returns information from the block at the current position in the file
		object fd. The return value is a tuple (x, y, z), where x is the
		HEAD_CRC field, y is the HEAD_TYPE field and z is the HEAD_FLAGS field.
		The block is skipped, so that the current file position is advanced
		past the block.

		Upon end-of-file, x, y and z in the tuple are all -1.
		"""
		data = get_data(fd, 7)
		if data == "":
			return (-1, -1, -1)
		head_crc = data_to_num(data, 0, 2)
		head_type = data_to_num(data, 2, 1)
		head_flags = data_to_num(data, 3, 2)
		head_size = data_to_num(data, 5, 2)
		if (head_flags & 0x8000) > 0:
			data = get_data(fd, 4)
			if data == "":
				return (-1, -1, -1)
			add_size = data_to_num(data, 0, 4)
			remain_size = head_size + add_size - 11
		else:
			remain_size = head_size - 7
		if remain_size > 0:
			fd.seek(remain_size, 1) # skip data
		return (head_crc, head_type, head_flags)

	try:
		# Open the file for reading in binary mode.
		fd = open(fname, "rb")
		first_block = True
		while 1:
			block_head = read_rar_block_head(fd)
			if first_block:
				# Check the RAR signature.
				# HEAD_CRC        Always 0x6152
				# HEAD_TYPE       Header type: 0x72
				# HEAD_FLAGS      Always 0x1a21 
				if block_head[0] != 0x6152 or block_head[1] != 0x72 or block_head[2] != 0x1a21:
					return None # not a valid RAR archive
				first_block = False
				
			if block_head[0] < 0:
				# End-of-file
				break
			if block_head[1] == 0x74:
				# File header. If the file is continued from a
				# previous volume, then we can return False.
				if (block_head[2] & 0x1) > 0:
					return False
	finally:
		fd.close()

	# No file was continued from a previous volume, so this is
	# either a start volume or a single volume.
	return True

if __name__ == "__main__":
	if len(sys.argv) < 2:
		print "Usage: %s <rar file>" % sys.argv[0]
		sys.exit(2)

	if rar_is_single_or_start_volume(sys.argv[1]):
		sys.exit(0)
	
	sys.exit(1)