diff --git a/build.gradle b/build.gradle index 85bf297a..6a7996a3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,6 @@ +import com.github.spotbugs.SpotBugsTask + + buildscript { repositories { google() @@ -27,14 +30,23 @@ subprojects { } dependencies { spotbugsPlugins group: 'com.h3xstream.findsecbugs', name: 'findsecbugs-plugin', version: '1.9.0' + //spotbugsPlugins group: 'com.mebigfatguy.sb-contrib', name: 'sb-contrib', version: '7.4.6' + } + spotbugs { + effort 'max' + showProgress true } tasks.withType( JavaCompile ) { options.encoding = 'UTF-8' sourceCompatibility = '1.8' targetCompatibility = '1.8' + options.compilerArgs << '-Xlint:unchecked' + if (it.name != JavaPlugin.COMPILE_JAVA_TASK_NAME) { + options.compilerArgs << '-Xlint:deprecation' + } } - tasks.withType( com.github.spotbugs.SpotBugsTask ) { + tasks.withType( SpotBugsTask ) { reports { xml.enabled = false html.enabled = true diff --git a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPIdenticon.java b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPIdenticon.java index 3bd4d726..311a2e34 100644 --- a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPIdenticon.java +++ b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPIdenticon.java @@ -24,6 +24,7 @@ import com.google.common.base.Charsets; import com.google.common.primitives.UnsignedBytes; import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests; import com.lyndir.lhunath.opal.system.logging.Logger; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.nio.*; import java.nio.charset.Charset; import java.util.Arrays; @@ -57,6 +58,7 @@ public class MPIdenticon { this( fullName, masterPassword.toCharArray() ); } + @SuppressFBWarnings("CLI_CONSTANT_LIST_INDEX") @SuppressWarnings("MethodCanBeVariableArityMethod") public MPIdenticon(final String fullName, final char[] masterPassword) { this.fullName = fullName; diff --git a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/impl/MPAlgorithmV0.java b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/impl/MPAlgorithmV0.java index f1afaf37..822b2f3c 100644 --- a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/impl/MPAlgorithmV0.java +++ b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/impl/MPAlgorithmV0.java @@ -60,17 +60,14 @@ public class MPAlgorithmV0 extends MPAlgorithm { ByteBuffer masterPasswordBuffer = ByteBuffer.wrap( masterPasswordBytes ); CoderResult result = encoder.encode( CharBuffer.wrap( masterPassword ), masterPasswordBuffer, true ); - if (!result.isUnderflow()) - result.throwException(); + if (result.isError()) + throw new IllegalStateException( result.toString() ); result = encoder.flush( masterPasswordBuffer ); - if (!result.isUnderflow()) - result.throwException(); + if (result.isError()) + throw new IllegalStateException( result.toString() ); return _masterKey( fullName, masterPasswordBytes, version().toInt() ); } - catch (final CharacterCodingException e) { - throw new IllegalStateException( e ); - } finally { Arrays.fill( masterPasswordBytes, (byte) 0 ); } diff --git a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/impl/Native.java b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/impl/Native.java index 9b2d93dd..fdc6ffc9 100644 --- a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/impl/Native.java +++ b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/impl/Native.java @@ -43,7 +43,7 @@ public final class Native { private static final char EXTENSION_SEPARATOR = '.'; private static final String NATIVES_PATH = "lib"; - @SuppressFBWarnings("PATH_TRAVERSAL_IN") + @SuppressFBWarnings({"PATH_TRAVERSAL_IN", "IOI_USE_OF_FILE_STREAM_CONSTRUCTORS", "EXS_EXCEPTION_SOFTENING_RETURN_FALSE"}) @SuppressWarnings({ "HardcodedFileSeparator", "LoadLibraryWithNonConstantString" }) public static boolean load(final Class context, final String name) { @@ -89,7 +89,7 @@ public final class Native { return true; } catch (@SuppressWarnings("ErrorNotRethrown") final IOException | UnsatisfiedLinkError e) { - logger.dbg( e, "Couldn't load library: %s", libraryResource ); + logger.wrn( e, "Couldn't load library: %s", libraryResource ); if (libraryFile != null) if (libraryFile.exists() && !libraryFile.delete()) diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/MPGuiConfig.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/MPGuiConfig.java index b58c5a86..86bbf02e 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/MPGuiConfig.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/MPGuiConfig.java @@ -19,6 +19,7 @@ package com.lyndir.masterpassword.gui; import com.lyndir.lhunath.opal.system.util.ConversionUtils; +import com.lyndir.masterpassword.gui.util.State; import com.lyndir.masterpassword.model.MPConfig; import com.lyndir.masterpassword.model.MPModelConstants; @@ -43,7 +44,7 @@ public class MPGuiConfig extends MPConfig { public void setCheckForUpdates(final boolean checkForUpdates) { this.checkForUpdates = checkForUpdates; - MasterPassword.get().updateCheck(); + State.get().updateCheck(); setChanged(); } diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/MasterPassword.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/MasterPassword.java index 8d31651c..1ca70a25 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/MasterPassword.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/MasterPassword.java @@ -50,40 +50,14 @@ public final class MasterPassword { private static final MasterPassword instance = new MasterPassword(); private final Provider keyMaster = Provider.getCurrentProvider( true ); - private final Collection listeners = new CopyOnWriteArraySet<>(); @Nullable private MasterPasswordFrame frame; - @Nullable - private MPUser activeUser; public static MasterPassword get() { return instance; } - public void addListener(final Listener listener) { - if (listeners.add( listener )) - listener.onUserSelected( activeUser ); - } - - public void removeListener(final Listener listener) { - listeners.remove( listener ); - } - - public void activateUser(final MPUser user) { - if (ObjectUtils.equals( activeUser, user )) - return; - - activeUser = user; - for (final Listener listener : listeners) - listener.onUserSelected( activeUser ); - } - - @Nullable - public String version() { - return MasterPassword.class.getPackage().getImplementationVersion(); - } - public void open() { Res.ui( () -> { if (frame == null) @@ -110,43 +84,10 @@ public final class MasterPassword { // Create and open the UI. get().open(); - - // UI features. get().updateResidence(); - get().updateCheck(); - } - public void updateCheck() { - if (!MPGuiConfig.get().checkForUpdates()) - return; - - try { - String implementationVersion = version(); - String latestVersion = new ByteSource() { - @Override - public InputStream openStream() - throws IOException { - URL url = URI.create( "https://masterpassword.app/masterpassword-gui.jar.rev" ).toURL(); - URLConnection conn = url.openConnection(); - conn.addRequestProperty( "User-Agent", "masterpassword-gui" ); - return conn.getInputStream(); - } - }.asCharSource( Charsets.UTF_8 ).readFirstLine(); - - if ((implementationVersion != null) && !implementationVersion.equalsIgnoreCase( latestVersion )) { - logger.inf( "Implementation: <%s>", implementationVersion ); - logger.inf( "Latest : <%s>", latestVersion ); - logger.wrn( "You are not running the current official version. Please update from:%n%s", - "https://masterpassword.app/masterpassword-gui.jar" ); - JOptionPane.showMessageDialog( null, Components.linkLabel( strf( - "A new version of Master Password is available." - + "

Please download the latest version from https://masterpassword.app." ) ), - "Update Available", JOptionPane.INFORMATION_MESSAGE ); - } - } - catch (final IOException e) { - logger.wrn( e, "Couldn't check for version update." ); - } + // Background. + State.get().updateCheck(); } public void updateResidence() { @@ -154,10 +95,4 @@ public final class MasterPassword { Platform.get().installAppReopenHandler( get()::open ); keyMaster.register( MPGuiConstants.ui_hotkey, hotKey -> get().open() ); } - - @SuppressWarnings("InterfaceMayBeAnnotatedFunctional") - public interface Listener { - - void onUserSelected(@Nullable MPUser user); - } } diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPIncognitoQuestion.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPIncognitoQuestion.java index 301c90ab..cc625ada 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPIncognitoQuestion.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPIncognitoQuestion.java @@ -22,7 +22,6 @@ import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*; import com.lyndir.masterpassword.MPResultType; import com.lyndir.masterpassword.model.impl.MPBasicQuestion; -import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java index 1b1d80f8..0547b22f 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java @@ -145,17 +145,15 @@ public abstract class Components { return ((selectedFiles != null) && (selectedFiles.length > 0))? selectedFiles[0]: null; } - public static JDialog showDialog(@Nullable final Component owner, @Nullable final String title, final Container content) { + public static void showDialog(@Nullable final Component owner, @Nullable final String title, final Container content) { JDialog dialog = new JDialog( (owner != null)? SwingUtilities.windowForComponent( owner ): null, title, Dialog.ModalityType.DOCUMENT_MODAL ); dialog.setMinimumSize( new Dimension( 320, 0 ) ); dialog.setLocationRelativeTo( owner ); dialog.setContentPane( content ); - - return showDialog( dialog ); } - private static JDialog showDialog(final JDialog dialog) { + private static void showDialog(final JDialog dialog) { // OpenJDK does not correctly implement this setting in native code. dialog.getRootPane().putClientProperty( "apple.awt.documentModalSheet", Boolean.TRUE ); dialog.getRootPane().putClientProperty( "Window.style", "small" ); @@ -163,8 +161,6 @@ public abstract class Components { dialog.setLocationByPlatform( true ); dialog.setVisible( true ); - - return dialog; } public static JTextField textField() { diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java index c977f658..6ab70bc8 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java @@ -226,7 +226,7 @@ public abstract class Res { return get( Font.PLAIN, size ); } - Font get(final int style, final int size) { + synchronized Font get(final int style, final int size) { if (!registered) register(); @@ -234,7 +234,7 @@ public abstract class Res { } @SuppressFBWarnings("URLCONNECTION_SSRF_FD") - private void register() { + private synchronized void register() { try { Font font = Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( resourceName ).openStream() ); GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont( font ); diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/State.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/State.java new file mode 100644 index 00000000..326529aa --- /dev/null +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/State.java @@ -0,0 +1,94 @@ +package com.lyndir.masterpassword.gui.util; + +import static com.lyndir.lhunath.opal.system.util.StringUtils.*; + +import com.google.common.base.Charsets; +import com.google.common.io.ByteSource; +import com.lyndir.lhunath.opal.system.logging.Logger; +import com.lyndir.lhunath.opal.system.util.ObjectUtils; +import com.lyndir.masterpassword.gui.MPGuiConfig; +import com.lyndir.masterpassword.model.MPUser; +import java.io.IOException; +import java.io.InputStream; +import java.net.*; +import java.util.Collection; +import java.util.concurrent.CopyOnWriteArraySet; +import javax.annotation.Nullable; +import javax.swing.*; + + +public class State { + + private static final Logger logger = Logger.get( State.class ); + private static final State instance = new State(); + + private final Collection listeners = new CopyOnWriteArraySet<>(); + @Nullable + private MPUser activeUser; + + public static State get() { + return instance; + } + + public void addListener(final Listener listener) { + if (listeners.add( listener )) + listener.onUserSelected( activeUser ); + } + + public void removeListener(final Listener listener) { + listeners.remove( listener ); + } + + public void activateUser(final MPUser user) { + if (ObjectUtils.equals( activeUser, user )) + return; + + activeUser = user; + for (final Listener listener : listeners) + listener.onUserSelected( activeUser ); + } + + @Nullable + public String version() { + return State.class.getPackage().getImplementationVersion(); + } + + public void updateCheck() { + if (!MPGuiConfig.get().checkForUpdates()) + return; + + try { + String implementationVersion = version(); + String latestVersion = new ByteSource() { + @Override + public InputStream openStream() + throws IOException { + URL url = URI.create( "https://masterpassword.app/masterpassword-gui.jar.rev" ).toURL(); + URLConnection conn = url.openConnection(); + conn.addRequestProperty( "User-Agent", "masterpassword-gui" ); + return conn.getInputStream(); + } + }.asCharSource( Charsets.UTF_8 ).readFirstLine(); + + if ((implementationVersion != null) && !implementationVersion.equalsIgnoreCase( latestVersion )) { + logger.inf( "Implementation: <%s>", implementationVersion ); + logger.inf( "Latest : <%s>", latestVersion ); + logger.wrn( "You are not running the current official version. Please update from:%n%s", + "https://masterpassword.app/masterpassword-gui.jar" ); + JOptionPane.showMessageDialog( null, Components.linkLabel( strf( + "A new version of Master Password is available." + + "

Please download the latest version from https://masterpassword.app." ) ), + "Update Available", JOptionPane.INFORMATION_MESSAGE ); + } + } + catch (final IOException e) { + logger.wrn( e, "Couldn't check for version update." ); + } + } + + @SuppressWarnings("InterfaceMayBeAnnotatedFunctional") + public interface Listener { + + void onUserSelected(@Nullable MPUser user); + } +} diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/UnsignedIntegerModel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/UnsignedIntegerModel.java index 761393e5..23ccf904 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/UnsignedIntegerModel.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/UnsignedIntegerModel.java @@ -124,15 +124,13 @@ public class UnsignedIntegerModel extends SpinnerNumberModel implements Selectab return new JFormattedTextField.AbstractFormatter() { @Override @Nullable - public Object stringToValue(@Nullable final String text) - throws ParseException { + public Object stringToValue(@Nullable final String text) { return (text != null)? UnsignedInteger.valueOf( text ): null; } @Override @Nullable - public String valueToString(final Object value) - throws ParseException { + public String valueToString(final Object value) { return (value != null)? value.toString(): null; } }; diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java index 9f1aaa64..e40e3161 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java @@ -17,13 +17,13 @@ import javax.swing.*; * @author lhunath, 2018-07-14 */ @SuppressWarnings("serial") -public class FilesPanel extends JPanel implements MPFileUserManager.Listener, MasterPassword.Listener { +public class FilesPanel extends JPanel implements MPFileUserManager.Listener, State.Listener { private final JButton avatarButton = Components.button( Res.icons().avatar( 0 ), event -> setAvatar(), "Click to change the user's avatar." ); private final CollectionListModel> usersModel = - new CollectionListModel>( MPFileUserManager.get().getFiles() ).selection( MasterPassword.get()::activateUser ); + new CollectionListModel>( MPFileUserManager.get().getFiles() ).selection( State.get()::activateUser ); protected FilesPanel() { setOpaque( false ); @@ -45,7 +45,7 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener, Ma add( Components.comboBox( usersModel, user -> ifNotNull( user, MPUser::getFullName ) ) ); MPFileUserManager.get().addListener( this ); - MasterPassword.get().addListener( this ); + State.get().addListener( this ); } private void setAvatar() { diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java index c2a2d34e..b573eb66 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java @@ -41,7 +41,7 @@ import javax.swing.text.PlainDocument; * @author lhunath, 2018-07-14 */ @SuppressWarnings("SerializableStoresNonSerializable") -public class UserContentPanel extends JPanel implements MasterPassword.Listener, MPUser.Listener { +public class UserContentPanel extends JPanel implements State.Listener, MPUser.Listener { private static final Random random = new SecureRandom(); private static final int SIZE_RESULT = 48; @@ -72,7 +72,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, setBorder( Components.marginBorder() ); showUser( null ); - MasterPassword.get().addListener( this ); + State.get().addListener( this ); } protected JComponent getUserToolbar() { @@ -155,9 +155,9 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, return; if (incognitoField.isSelected()) - MasterPassword.get().activateUser( new MPIncognitoUser( fullName ) ); + State.get().activateUser( new MPIncognitoUser( fullName ) ); else - MasterPassword.get().activateUser( MPFileUserManager.get().add( fullName ) ); + State.get().activateUser( MPFileUserManager.get().add( fullName ) ); } private void importUser() { @@ -200,7 +200,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, Res.format( importUser.getLastUsed() ) ) ))) return; - MasterPassword.get().activateUser( MPFileUserManager.get().add( importUser ) ); + State.get().activateUser( MPFileUserManager.get().add( importUser ) ); } catch (final MPIncorrectMasterPasswordException | MPAlgorithmException e) { JOptionPane.showMessageDialog( @@ -242,7 +242,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, + "Some people even configure this location to be synced between their different computers " + "using services such as those provided by SpiderOak or Dropbox.

" + "

https://masterpassword.app — by Maarten Billemont

", - MasterPassword.get().version(), + State.get().version(), InputEvent.getModifiersExText( MPGuiConstants.ui_hotkey.getModifiers() ), KeyEvent.getKeyText( MPGuiConstants.ui_hotkey.getKeyCode() ), MPFileUserManager.get().getPath().getAbsolutePath() ) ),