From: Andrew C. Oliver Date: Mon, 2 Jul 2007 08:10:28 +0000 (+0000) Subject: Now can protect sheets with a password. So everyone may cease whining about it X-Git-Tag: REL_3_0_2_BETA1~82 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=a68e4bd7fdd527141817a9e43b56b32edbfd81fd;p=poi.git Now can protect sheets with a password. So everyone may cease whining about it already :-) git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@552425 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/java/org/apache/poi/hssf/model/Sheet.java b/src/java/org/apache/poi/hssf/model/Sheet.java index 2899005ecb..1f8deec967 100644 --- a/src/java/org/apache/poi/hssf/model/Sheet.java +++ b/src/java/org/apache/poi/hssf/model/Sheet.java @@ -93,6 +93,9 @@ public class Sheet implements Model protected ProtectRecord protect = null; protected PageBreakRecord rowBreaks = null; protected PageBreakRecord colBreaks = null; + protected ObjectProtectRecord objprotect = null; + protected ScenarioProtectRecord scenprotect = null; + protected PasswordRecord password = null; public static final byte PANE_LOWER_RIGHT = (byte)0; @@ -285,6 +288,18 @@ public class Sheet implements Model { retval.protect = (ProtectRecord) rec; } + else if ( rec.getSid() == ObjectProtectRecord.sid ) + { + retval.objprotect = (ObjectProtectRecord) rec; + } + else if ( rec.getSid() == ScenarioProtectRecord.sid ) + { + retval.scenprotect = (ScenarioProtectRecord) rec; + } + else if ( rec.getSid() == PasswordRecord.sid ) + { + retval.password = (PasswordRecord) rec; + } else if (rec.getSid() == PageBreakRecord.HORIZONTAL_SID) { retval.rowBreaks = (PageBreakRecord)rec; @@ -2500,7 +2515,38 @@ public class Sheet implements Model ProtectRecord retval = new ProtectRecord(); retval.setProtect(false); - // by default even when we support encryption we won't + return retval; + } + + /** + * creates an ObjectProtect record with protect set to false. + * @see org.apache.poi.hssf.record.ObjectProtectRecord + * @see org.apache.poi.hssf.record.Record + * @return an ObjectProtectRecord + */ + protected ObjectProtectRecord createObjectProtect() + { + if (log.check( POILogger.DEBUG )) + log.log(POILogger.DEBUG, "create protect record with protection disabled"); + ObjectProtectRecord retval = new ObjectProtectRecord(); + + retval.setProtect(false); + return retval; + } + + /** + * creates a ScenarioProtect record with protect set to false. + * @see org.apache.poi.hssf.record.ScenarioProtectRecord + * @see org.apache.poi.hssf.record.Record + * @return a ScenarioProtectRecord + */ + protected ScenarioProtectRecord createScenarioProtect() + { + if (log.check( POILogger.DEBUG )) + log.log(POILogger.DEBUG, "create protect record with protection disabled"); + ScenarioProtectRecord retval = new ScenarioProtectRecord(); + + retval.setProtect(false); return retval; } @@ -2518,6 +2564,38 @@ public class Sheet implements Model return protect; } + /** Returns the PasswordRecord. + * If one is not contained in the sheet, then one is created. + */ + public PasswordRecord getPassword() + { + if (password == null) { + password = createPassword(); + //Insert the newly created password record at the end of the record (just before the EOF) + int loc = findFirstRecordLocBySid(EOFRecord.sid); + records.add(loc, password); + } + return password; + } + + /** + * creates a Password record with password set to 00. + * @see org.apache.poi.hssf.record.PasswordRecord + * @see org.apache.poi.hssf.record.Record + * @return a PasswordRecord + */ + protected PasswordRecord createPassword() + { + if (log.check( POILogger.DEBUG )) + log.log(POILogger.DEBUG, "create password record with 00 password"); + PasswordRecord retval = new PasswordRecord(); + + retval.setPassword((short)00); + return retval; + } + + /** + /** * Sets whether the gridlines are shown in a viewer. * @param show whether to show gridlines or not @@ -2791,6 +2869,68 @@ public class Sheet implements Model } } + /** + * protect a spreadsheet with a password (not encypted, just sets protect + * flags and the password. + * @param password to set + * @param objects are protected + * @param scenarios are protected + */ + public void protectSheet( String password, boolean objects, boolean scenarios ) { + int protIdx = -1; + ProtectRecord prec = getProtect(); + PasswordRecord pass = getPassword(); + prec.setProtect(true); + pass.setPassword(PasswordRecord.hashPassword(password)); + if((objprotect == null && objects) || (scenprotect != null && scenarios)) { + protIdx = records.indexOf( protect ); + } + if(objprotect == null && objects) { + ObjectProtectRecord rec = createObjectProtect(); + rec.setProtect(true); + records.add(protIdx+1,rec); + objprotect = rec; + } + if(scenprotect == null && scenarios) { + ScenarioProtectRecord srec = createScenarioProtect(); + srec.setProtect(true); + records.add(protIdx+2,srec); + scenprotect = srec; + } + } + + /** + * unprotect objects in the sheet (will not protect them, but any set to false are + * unprotected. + * @param sheet is unprotected (false = unprotect) + * @param objects are unprotected (false = unprotect) + * @param scenarios are unprotected (false = unprotect) + */ + public void unprotectSheet( boolean sheet, boolean objects, boolean scenarios ) { + int protIdx = -1; + if (!sheet) { + ProtectRecord prec = getProtect(); + prec.setProtect(sheet); + PasswordRecord pass = getPassword(); + pass.setPassword((short)00); + } + if(objprotect != null && !objects) { + objprotect.setProtect(false); + } + if(scenprotect != null && !scenarios) { + scenprotect.setProtect(false); + } + } + + /** + * @return {sheet is protected, objects are proteced, scenarios are protected} + */ + public boolean[] isProtected() { + return new boolean[] { (protect != null && protect.getProtect()), + (objprotect != null && objprotect.getProtect()), + (scenprotect != null && scenprotect.getProtect())}; + } + // private void collapseColumn( short columnNumber ) // { // int idx = findColumnIdx( columnNumber, 0 ); diff --git a/src/java/org/apache/poi/hssf/record/PasswordRecord.java b/src/java/org/apache/poi/hssf/record/PasswordRecord.java index 13e8a43893..67f210da5b 100644 --- a/src/java/org/apache/poi/hssf/record/PasswordRecord.java +++ b/src/java/org/apache/poi/hssf/record/PasswordRecord.java @@ -29,14 +29,11 @@ import org.apache.poi.util.LittleEndian; * @version 2.0-pre */ -public class PasswordRecord - extends Record -{ +public class PasswordRecord extends Record { public final static short sid = 0x13; private short field_1_password; // not sure why this is only 2 bytes, but it is... go figure - public PasswordRecord() - { + public PasswordRecord() { } /** @@ -44,32 +41,46 @@ public class PasswordRecord * @param in the RecordInputstream to read the record from */ - public PasswordRecord(RecordInputStream in) - { + public PasswordRecord(RecordInputStream in) { super(in); } - protected void validateSid(short id) - { - if (id != sid) - { + protected void validateSid(short id) { + if (id != sid) { throw new RecordFormatException("NOT A PASSWORD RECORD"); } } - protected void fillFields(RecordInputStream in) - { + protected void fillFields(RecordInputStream in) { field_1_password = in.readShort(); } + //this is the world's lamest "security". thanks to Wouter van Vugt for making me + //not have to try real hard. -ACO + public static short hashPassword(String password) { + byte[] passwordCharacters = password.getBytes(); + int hash = 0; + if (passwordCharacters.length > 0) { + int charIndex = passwordCharacters.length; + while (charIndex-- > 0) { + hash = ((hash >> 14) & 0x01) | ((hash << 1) & 0x7fff); + hash ^= passwordCharacters[charIndex]; + } + // also hash with charcount + hash = ((hash >> 14) & 0x01) | ((hash << 1) & 0x7fff); + hash ^= passwordCharacters.length; + hash ^= (0x8000 | ('N' << 8) | 'K'); + } + return (short)hash; + } + /** * set the password * * @param password representing the password */ - public void setPassword(short password) - { + public void setPassword(short password) { field_1_password = password; } @@ -78,14 +89,11 @@ public class PasswordRecord * * @return short representing the password */ - - public short getPassword() - { + public short getPassword() { return field_1_password; } - public String toString() - { + public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("[PASSWORD]\n"); @@ -95,8 +103,7 @@ public class PasswordRecord return buffer.toString(); } - public int serialize(int offset, byte [] data) - { + public int serialize(int offset, byte [] data) { LittleEndian.putShort(data, 0 + offset, sid); LittleEndian.putShort(data, 2 + offset, (( short ) 0x02)); // 2 bytes (6 total) @@ -104,13 +111,11 @@ public class PasswordRecord return getRecordSize(); } - public int getRecordSize() - { + public int getRecordSize() { return 6; } - public short getSid() - { + public short getSid() { return sid; } diff --git a/src/java/org/apache/poi/hssf/record/ProtectRecord.java b/src/java/org/apache/poi/hssf/record/ProtectRecord.java index 6d3ea06e69..94868e0a60 100644 --- a/src/java/org/apache/poi/hssf/record/ProtectRecord.java +++ b/src/java/org/apache/poi/hssf/record/ProtectRecord.java @@ -26,9 +26,10 @@ import org.apache.poi.util.LittleEndian; * Description: defines whether a sheet or workbook is protected (HSSF DOES NOT SUPPORT ENCRYPTION)

* (kindly ask the US government to stop having arcane stupid encryption laws and we'll support it)

* (after all terrorists will all use US-legal encrypton right??)

+ * HSSF now supports the simple "protected" sheets (where they are not encrypted and open office et al + * ignore the password record entirely). * REFERENCE: PG 373 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)

* @author Andrew C. Oliver (acoliver at apache dot org) - * @version 2.0-pre */ public class ProtectRecord diff --git a/src/java/org/apache/poi/hssf/record/RecordFactory.java b/src/java/org/apache/poi/hssf/record/RecordFactory.java index 0ddfb60408..48b0d3fd8a 100644 --- a/src/java/org/apache/poi/hssf/record/RecordFactory.java +++ b/src/java/org/apache/poi/hssf/record/RecordFactory.java @@ -74,7 +74,7 @@ public class RecordFactory PaletteRecord.class, StringRecord.class, RecalcIdRecord.class, SharedFormulaRecord.class, HorizontalPageBreakRecord.class, VerticalPageBreakRecord.class, WriteProtectRecord.class, FilePassRecord.class, PaneRecord.class, - NoteRecord.class + NoteRecord.class, ObjectProtectRecord.class, ScenarioProtectRecord.class }; } private static Map recordsMap = recordsToMap(records); diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index d5b7737e32..74bda60fbc 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -872,17 +872,49 @@ public class HSSFSheet * @return true => protection enabled; false => protection disabled */ public boolean getProtect() { - return getSheet().getProtect().getProtect(); + return getSheet().isProtected()[0]; + } + + /** + * @return hashed password + */ + public short getPassword() { + return getSheet().getPassword().getPassword(); + } + + /** + * Answer whether object protection is enabled or disabled + * @return true => protection enabled; false => protection disabled + */ + public boolean getObjectProtect() { + return getSheet().isProtected()[1]; + } + + /** + * Answer whether scenario protection is enabled or disabled + * @return true => protection enabled; false => protection disabled + */ + public boolean getScenarioProtect() { + return getSheet().isProtected()[2]; } /** * Sets the protection on enabled or disabled * @param protect true => protection enabled; false => protection disabled + * @deprecated use protectSheet(String, boolean, boolean) */ public void setProtect(boolean protect) { getSheet().getProtect().setProtect(protect); } + /** + * Sets the protection enabled as well as the password + * @param password to set for protection + */ + public void protectSheet(String password) { + getSheet().protectSheet(password, true, true); //protect objs&scenarios(normal) + } + /** * Sets the zoom magnication for the sheet. The zoom is expressed as a * fraction. For example to express a zoom of 75% use 3 for the numerator diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java index 2a8561ebd6..6d7feef3b3 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java @@ -26,6 +26,7 @@ import junit.framework.TestCase; import org.apache.poi.hssf.model.Sheet; import org.apache.poi.hssf.record.HCenterRecord; import org.apache.poi.hssf.record.ProtectRecord; +import org.apache.poi.hssf.record.PasswordRecord; import org.apache.poi.hssf.record.SCLRecord; import org.apache.poi.hssf.record.VCenterRecord; import org.apache.poi.hssf.record.WSBoolRecord; @@ -38,6 +39,7 @@ import org.apache.poi.util.TempFile; * * * @author Glen Stampoultzis (glens at apache.org) + * @author Andrew C. Oliver (acoliver apache org) */ public class TestHSSFSheet @@ -242,6 +244,20 @@ public class TestHSSFSheet assertTrue(hssfSheet.getProtect()); } + public void testProtectSheet() { + short expected = (short)0xfef1; + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet s = wb.createSheet(); + s.protectSheet("abcdefghij"); + Sheet sheet = s.getSheet(); + ProtectRecord protect = sheet.getProtect(); + PasswordRecord pass = sheet.getPassword(); + assertTrue("protection should be on",protect.getProtect()); + assertTrue("object protection should be on",sheet.isProtected()[1]); + assertTrue("scenario protection should be on",sheet.isProtected()[2]); + assertEquals("well known value for top secret hash should be "+Integer.toHexString(expected).substring(4),expected,pass.getPassword()); + } + public void testZoom() throws Exception