summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRandy Morgan <[email protected]>2012-05-21 06:37:13 -0700
committerRandy Morgan <[email protected]>2012-05-21 06:37:13 -0700
commit5b9853a28f064ad827e5c8c65e7da023d559334e (patch)
tree29a985514c00c364a7a5730ebb5e3bb599e2f5a8
parent2e69e82bdff6024e48d5ae95e14a1ac908c5b3f8 (diff)
parent745a47bf2a7f90e8538d011481979e3e57761787 (diff)
downloadcaxlsx-5b9853a28f064ad827e5c8c65e7da023d559334e.tar.gz
caxlsx-5b9853a28f064ad827e5c8c65e7da023d559334e.zip
Merge pull request #95 from janhuehne/master
Solves password hash problem
-rw-r--r--lib/axlsx/workbook/worksheet/sheet_protection.rb74
-rw-r--r--test/workbook/worksheet/tc_sheet_protection.rb31
2 files changed, 61 insertions, 44 deletions
diff --git a/lib/axlsx/workbook/worksheet/sheet_protection.rb b/lib/axlsx/workbook/worksheet/sheet_protection.rb
index cc1eaf2b..a11ba49d 100644
--- a/lib/axlsx/workbook/worksheet/sheet_protection.rb
+++ b/lib/axlsx/workbook/worksheet/sheet_protection.rb
@@ -4,14 +4,6 @@ module Axlsx
# The SheetProtection object manages worksheet protection options per sheet.
class SheetProtection
- # Specifies the specific cryptographic hashing algorithm which shall be used along
- # with the salt attribute and input password in order to compute the hash value.
- # This value is automatically set to 'SHA-1' when password= is called.
- # @note only SHA-1 is supported.
- # @return [String]
- attr_reader :algorithm_name
-
-
# If 1 or true then AutoFilters should not be allowed to operate when the sheet is protected.
# If 0 or false then AutoFilters should be allowed to operate when the sheet is protected.
# @return [Boolean]
@@ -48,10 +40,6 @@ module Axlsx
# @default true
attr_reader :format_rows
- # Specifies the hash value for the password required to edit this worksheet.
- # @return [String]
- attr_reader :hash_value
-
# If 1 or true then inserting columns should not be allowed when the sheet is protected.
# If 0 or false then inserting columns should be allowed when the sheet is protected.
# @return [Boolean]
@@ -115,11 +103,12 @@ module Axlsx
# @return [Boolean]
# @default true
attr_reader :sort
-
- # Specifies the number of times the hashing function shall be iteratively run
- # @return [Integer]
- # @default 10000
- attr_reader :spin_count
+
+
+ # Password hash
+ # @return [String]
+ # @default nil
+ attr_reader :password
# Creates a new SheetProtection instance
# @option options [Boolean] sheet @see SheetProtection#sheet
@@ -143,6 +132,8 @@ module Axlsx
def initialize(options={})
@objects = @scenarios = @select_locked_cells = @select_unlocked_cells = false
@sheet = @format_cells = @format_rows = @format_columns = @insert_columns = @insert_rows = @insert_hyperlinks = @delete_columns = @delete_rows = @sort = @auto_filter = @pivot_tables = true
+ @password = nil
+
options.each do |o|
self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}="
end
@@ -159,22 +150,51 @@ module Axlsx
end
def password=(v)
- @algorithm_name = v == nil ? nil : 'SHA-1'
- @salt_value = @spin_count = @hash_value = v if v == nil
return if v == nil
- require 'digest/sha1'
- @spin_count = 10000
- @salt_value = Digest::SHA1.hexdigest(rand(36**8).to_s(36))
- @hash_value = nil
- @spin_count.times do |count|
- @hash_value = Digest::SHA1.hexdigest((@hash_value || (@salt_value + v.to_s)) + count.to_s.bytes.to_a.pack('l'))
- end
+ @password = create_password_hash(v)
end
def to_xml_string(str = '')
str << '<sheetProtection '
str << instance_values.map{ |k,v| k.gsub(/_(.)/){ $1.upcase } << %{="#{v.to_s}"} }.join(' ')
str << '/>'
- end
+ end
+
+ private
+ # Creates a password hash for a given password
+ # @return [String]
+ def create_password_hash(password)
+ encoded_password = encode_password(password)
+
+ password_as_hex = [encoded_password].pack("v")
+ password_as_string = password_as_hex.unpack("H*").first.upcase
+
+ password_as_string[2..3] + password_as_string[0..1]
+ end
+
+
+ # Encodes a given password
+ # Based on the algorithm provided by Daniel Rentz of OpenOffice.
+ # http://www.openoffice.org/sc/excelfileformat.pdf, Revision 1.42, page 115 (21.05.2012)
+ # @return [String]
+ def encode_password(password)
+ i = 0
+ chars = password.split(//)
+ count = chars.size
+
+ chars.collect! do |char|
+ i += 1
+ char = char.ord << i
+ low_15 = char & 0x7fff
+ high_15 = char & 0x7fff << 15
+ high_15 = high_15 >> 15
+ char = low_15 | high_15
+ end
+
+ encoded_password = 0x0000
+ chars.each { |c| encoded_password ^= c }
+ encoded_password ^= count
+ encoded_password ^= 0xCE4B
+ end
end
end
diff --git a/test/workbook/worksheet/tc_sheet_protection.rb b/test/workbook/worksheet/tc_sheet_protection.rb
index d6b16534..28185775 100644
--- a/test/workbook/worksheet/tc_sheet_protection.rb
+++ b/test/workbook/worksheet/tc_sheet_protection.rb
@@ -2,10 +2,6 @@
require 'tc_helper.rb'
# <xsd:complexType name="CT_SheetProtection">
-# <xsd:attribute name="algorithmName" type="s:ST_Xstring" use="optional"/>
-# <xsd:attribute name="hashValue" type="xsd:base64Binary" use="optional"/>
-# <xsd:attribute name="saltValue" type="xsd:base64Binary" use="optional"/>
-# <xsd:attribute name="spinCount" type="xsd:unsignedInt" use="optional"/>
# <xsd:attribute name="sheet" type="xsd:boolean" use="optional" default="false"/>
# <xsd:attribute name="objects" type="xsd:boolean" use="optional" default="false"/>
# <xsd:attribute name="scenarios" type="xsd:boolean" use="optional" default="false"/>
@@ -22,41 +18,42 @@ require 'tc_helper.rb'
# <xsd:attribute name="autoFilter" type="xsd:boolean" use="optional" default="true"/>
# <xsd:attribute name="pivotTables" type="xsd:boolean" use="optional" default="true"/>
# <xsd:attribute name="selectUnlockedCells" type="xsd:boolean" use="optional" default="false"/>
+# <xsd:attribute name="password" type="xsd:string" use="optional" default="nil"/>
# </xsd:complexType>
class TestSheetProtection < Test::Unit::TestCase
def setup
#inverse defaults
- @options = { :sheet => false, :objects => true, :scenarios => true, :format_cells => false,
- :format_columns => false, :format_rows => false, :insert_columns => false, :insert_rows => false,
- :insert_hyperlinks => false, :delete_columns => false, :delete_rows => false, :select_locked_cells => true,
- :sort => false, :auto_filter => false, :pivot_tables => false, :select_unlocked_cells => true }
+ @boolean_options = { :sheet => false, :objects => true, :scenarios => true, :format_cells => false,
+ :format_columns => false, :format_rows => false, :insert_columns => false, :insert_rows => false,
+ :insert_hyperlinks => false, :delete_columns => false, :delete_rows => false, :select_locked_cells => true,
+ :sort => false, :auto_filter => false, :pivot_tables => false, :select_unlocked_cells => true }
+
+ @string_options = { :password => nil }
+
+ @options = @boolean_options.merge(@string_options)
+
@sp = Axlsx::SheetProtection.new(@options)
end
def test_initialize
sp = Axlsx::SheetProtection.new
- @options.each do |key, value|
+ @boolean_options.each do |key, value|
assert_equal(!value, sp.send(key.to_sym), "initialized default #{key} should be #{!value}")
assert_equal(value, @sp.send(key.to_sym), "initialized options #{key} should be #{value}")
end
- { :algorithm_name => nil }.each do |key, value|
- assert_equal(value, @sp.send(key.to_sym), "initialized default #{key} should be #{value}")
- assert_equal(value, sp.send(key.to_sym), "initialized default #{key} should be #{value}")
- end
end
def test_boolean_attribute_validation
- @options.each do |key, value|
+ @boolean_options.each do |key, value|
assert_raise(ArgumentError, "#{key} must be boolean") { @sp.send("#{key}=".to_sym, 'A') }
assert_nothing_raised { @sp.send("#{key}=".to_sym, true) }
assert_nothing_raised { @sp.send("#{key}=".to_sym, true) }
end
end
-
-
+
def test_to_xml_string
- @sp.password = 'fish'
+ @sp.password = 'fish' # -> CA3F
doc = Nokogiri::XML(@sp.to_xml_string)
@options.each do |key, value|
assert(doc.xpath("//sheetProtection[@#{key.to_s.gsub(/_(.)/){ $1.upcase }}='#{value}']"))