/*
* FreeMind - A Program for creating and viewing Mindmaps Copyright (C)
* 2000-2001 Joerg Mueller <joergmueller@bigfoot.com> See COPYING for Details
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package freemind.modes.mindmapmode;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ListIterator;
import java.util.Vector;
import javax.swing.ImageIcon;
import javax.swing.tree.MutableTreeNode;
import freemind.main.FreeMindMain;
import freemind.main.HtmlTools;
import freemind.main.Tools.SingleDesEncrypter;
import freemind.main.XMLElement;
import freemind.modes.MindIcon;
import freemind.modes.MindMap;
import freemind.modes.MindMapLinkRegistry;
import freemind.modes.MindMapNode;
import freemind.modes.ModeController;
public class EncryptedMindMapNode extends MindMapNodeModel {
/**
* Is used to hide all children (when false)
*/
private boolean isAccessible = true;
/**
* Is used to store the child nodes (when true).
*/
private boolean isStoringEncryptedContent = false;
/**
* is only set to false by the load mechanism. If the node is generated or
* it is decrypted once, this is always true.
*/
private boolean isDecrypted = true;
/**
* password have to be stored in a StringBuffer as Strings cannot be deleted
* or overwritten.
*/
private StringBuffer password = null;
private String encryptedContent;
private static ImageIcon encryptedIcon;
private static ImageIcon decryptedIcon;
private boolean isShuttingDown = false;
/**
*/
public EncryptedMindMapNode(Object userObject, FreeMindMain frame,
MindMap map) {
super(userObject, frame, map);
if (encryptedIcon == null) {
encryptedIcon = MindIcon.factory("encrypted").getIcon();
}
if (decryptedIcon == null) {
decryptedIcon = MindIcon.factory("decrypted").getIcon();
}
if (map != null) {
updateIcon();
}
}
public void setMap(MindMap map) {
super.setMap(map);
updateIcon();
}
/**
* @return true, if the password was correct.
*/
public boolean decrypt(StringBuffer givenPassword) {
if (!checkPassword(givenPassword)) {
return false;
}
setAccessible(true);
if (!isDecrypted) {
try {
MindMapNode node = null;
String childXml = decryptXml(encryptedContent, password);
// is it a map at all?
if (childXml.startsWith(MindMapMapModel.MAP_INITIAL_START)) {
node = getNodeFromXml(childXml);
} else {
// old handling up to version 0.9.0_rc8:
String[] childs = childXml
.split(ModeController.NODESEPARATOR);
// and now? paste it:
// make a 0.8.0 map out of it:
String mapContent = MindMapMapModel.MAP_INITIAL_START
+ "0.8.0\"><node TEXT=\"DUMMY\">";
for (int j = 0; j < childs.length; j++) {
String nodeContent = childs[j];
mapContent += nodeContent;
}
mapContent += "</node></map>";
node = getNodeFromXml(mapContent);
}
int index = 0;
for (ListIterator i = node.childrenUnfolded(); i.hasNext();) {
MindMapNodeModel importNode = (MindMapNodeModel) i.next();
((MindMapController) getModeController()).insertNodeInto(
importNode, this, index++);
}
isDecrypted = true;
} catch (Exception e) {
freemind.main.Resources.getInstance().logException(e);
setAccessible(false);
}
}
setFolded(false);
getMap().getRegistry().registrySubtree(this, false);
return true;
}
private void paste(MindMapNodeModel importNode) {
((MindMapController) getModeController()).paste(importNode, this);
}
private MindMapNodeModel getNodeFromXml(String childXml) throws IOException {
// the loadTree method performs an automatical version update.
MindMapNodeModel node = getMindMapMapModel().loadTree(
new MindMapMapModel.StringReaderCreator(childXml), false);
return node;
}
private MindMapMapModel getMindMapMapModel() {
return ((MindMapMapModel) getMap());
}
/**
*/
public boolean checkPassword(StringBuffer givenPassword) {
if (password != null) {
if (!equals(givenPassword, password)) {
logger.warning("Wrong password supplied (cached!=given).");
return false;
}
return true;
}
// new password:
String decryptedNode = decryptXml(encryptedContent, givenPassword);
// FIXME: Better test needed.
if (decryptedNode == null) {
logger.warning("Wrong password supplied (deciphered text is null).");
return false;
}
if (!decryptedNode.startsWith("<node ")) {
// not an encrpyted node in the old format
// (node,separator,node,...), we test for xml:
if (!HtmlTools.getInstance().isWellformedXml(decryptedNode)) {
logger.warning("Wrong password supplied (malformed deciphered text).");
return false;
}
}
this.password = givenPassword;
return true;
}
/**
*/
private boolean equals(StringBuffer givenPassword, StringBuffer password2) {
if (givenPassword.length() != password.length())
return false;
for (int i = 0; i < password2.length(); i++) {
char c1 = password2.charAt(i);
char c2 = givenPassword.charAt(i);
if (c1 != c2)
return false;
}
return true;
}
public void encrypt() {
// FIXME: Sync.
setFolded(true);
setAccessible(false);
}
public int getChildCount() {
if (isAccessible()) {
return super.getChildCount();
}
return 0;
}
public ListIterator childrenFolded() {
if (isAccessible()) {
return super.childrenFolded();
}
return new Vector().listIterator();
}
public ListIterator childrenUnfolded() {
if (isAccessible() || isShuttingDown) {
return super.childrenUnfolded();
}
return new Vector().listIterator();
}
public boolean hasChildren() {
if (isAccessible()) {
return super.hasChildren();
}
return false;
}
/*
* (non-Javadoc)
*
* @see freemind.modes.MindMapNode#getIcons()
*/
public void updateIcon() {
if (isAccessible()) {
setStateIcon("encrypted", null);
setStateIcon("decrypted", decryptedIcon);
} else {
setStateIcon("decrypted", null);
setStateIcon("encrypted", encryptedIcon);
}
}
public void setPassword(StringBuffer password) {
this.password = password;
}
/**
*
*/
public boolean isFolded() {
if (isAccessible()) {
return super.isFolded();
}
return true;
}
/**
*
*/
public void setFolded(boolean folded) {
if (isAccessible()) {
super.setFolded(folded);
} else {
super.setFolded(true);
}
}
/**
*
*/
public void setAdditionalInfo(String info) {
encryptedContent = info;
setAccessible(false);
isDecrypted = false;
}
public String getAdditionalInfo() {
if (isStoringEncryptedContent()) {
return null;
}
return encryptedContent;
}
/**
*
*/
public XMLElement save(Writer writer, MindMapLinkRegistry registry,
boolean saveHidden, boolean saveChildren) throws IOException {
if (isStoringEncryptedContent()) {
return super.save(writer, registry, saveHidden, saveChildren);
}
if (isDecrypted) {
if (!isAccessible()) {
throw new IOException(
"Should store contents of encrypted node "
+ this.getText()
+ ", but it is not accessible.");
}
setStoringEncryptedContent(true);
try {
generateEncryptedContent(registry);
} finally {
setStoringEncryptedContent(false);
}
}
boolean oldIsVisible = isAccessible();
setAccessible(false);
XMLElement ret = null;
try {
ret = super.save(writer, registry, saveHidden, saveChildren);
} finally {
setAccessible(oldIsVisible);
}
return ret;
}
/**
* @throws IOException
*/
private void generateEncryptedContent(MindMapLinkRegistry registry)
throws IOException {
StringWriter sWriter = new StringWriter();
// OSSXP.COM: save all attributes or partial?
getMindMapMapModel().getXml(sWriter, true, this, 0);
StringBuffer childXml = sWriter.getBuffer();
encryptedContent = encryptXml(childXml);
}
/**
*/
private String encryptXml(StringBuffer childXml) {
try {
// Create encrypter/decrypter class
// FIXME: Use char[] instead of toString.
SingleDesEncrypter encrypter = new SingleDesEncrypter(password);
// Encrypt
String encrypted = encrypter.encrypt(childXml.toString());
return encrypted;
} catch (Exception e) {
freemind.main.Resources.getInstance().logException(e);
}
throw new IllegalArgumentException("Can't encrypt the node.");
}
/**
* @return null if the password is wrong.
*/
private String decryptXml(String encryptedString, StringBuffer pwd) {
SingleDesEncrypter encrypter = new SingleDesEncrypter(pwd);
// // Decrypt
String decrypted = encrypter.decrypt(encryptedString);
// fc, only in case, it is needed, we can activate this code.
// if (decrypted == null || decrypted.isEmpty()) {
// logger.warning("Perhaps wrong algorithm used (due to a Java bug, in FreeMind 0.8.0 and Java4-5 DES whereas with Java6 Triple DES was used. Trying Triple DES...");
// decrypted = new
// Tools.TripleDesEncrypter(pwd).decrypt(encryptedString);
// }
return decrypted;
}
/**
* isShuttingDown is used to fold an encrypted node properly. If it is
* encrypted, it has no children. Thus, the formely existing children can't
* be removed. Thus, this flag postpones the childlessness of a node until it
* tree structure is updated.
*
* @param isShuttingDown
* The isShuttingDown to set.
*/
public void setShuttingDown(boolean isShuttingDown) {
this.isShuttingDown = isShuttingDown;
}
/**
* @param isAccessible
* The isAccessible to set.
*/
private void setAccessible(boolean isAccessible) {
this.isAccessible = isAccessible;
updateIcon();
}
/**
* @return Returns the isAccessible (ie. if the node is decrypted
* (isAccessible==true) or not).
*/
public boolean isAccessible() {
return isAccessible;
}
public void insert(MutableTreeNode pChild, int pIndex) {
if (isAccessible()) {
super.insert(pChild, pIndex);
} else {
throw new IllegalArgumentException(
"Trying to insert nodes into a ciphered node.");
}
}
public boolean isWriteable() {
return isAccessible() && super.isWriteable();
}
public boolean isStoringEncryptedContent() {
return isStoringEncryptedContent;
}
public void setStoringEncryptedContent(boolean pIsStoringEncryptedContent) {
isStoringEncryptedContent = pIsStoringEncryptedContent;
}
}