Improves SIP and XMPP configuration interface to choose the priority between ZRTP and SDes.

cusax-fix
Vincent Lucas 13 years ago
parent 3363acadf0
commit 2836fe726f

@ -1053,12 +1053,15 @@ plugin.sipaccregwizz.SAVP_OPTION_0=Off (indicate RTP/AVP only)
plugin.sipaccregwizz.SAVP_OPTION_1=Mandatory (offer and accept only RTP/SAVP)
plugin.sipaccregwizz.SAVP_OPTION_2=Optional (offer RTP/SAVP first, then RTP/AVP)
plugin.sipaccregwizz.ENABLE_SDES_ATTRIBUTE=Enable S-Descriptor (also known as SDES or SRTP)
plugin.sipaccregwizz.ENCRYPTION_PROTOCOL_PREFERENCES=Choose enabled encryption protocols and their priority (top protocol first):
plugin.sipaccregwizz.CIPHER_SUITES=Enabled cipher suites:
plugin.sipaccregwizz.SECURITY_WARNING=<html><div width=450>{0} will automatically try to secure all \
your calls with ZRTP and you will both hear and see a notification once a secure \
connection is established. You should only change the advanced settings below \
if you are well aware what you are doing.</div></html>
plugin.sipaccregwizz.SHOW_ADVANCED=Advanced encryption settings
plugin.sipaccregwizz.ZRTP_OPTION=ZRTP option
plugin.sipaccregwizz.SDES_OPTION=SDes option
# skin manager
plugin.skinmanager.SKINS=Skins

@ -10,6 +10,8 @@
import javax.swing.table.*;
import net.java.sip.communicator.util.swing.*;
import org.jitsi.impl.neomedia.*;
import org.jitsi.impl.neomedia.codec.*;
import org.jitsi.impl.neomedia.format.*;
@ -22,7 +24,7 @@
* @author Lyubomir Marinov
*/
public class EncodingConfigurationTableModel
extends AbstractTableModel
extends MoveableTableModel
{
/**
* Serial version UID.

@ -600,7 +600,11 @@ private static Component createControls(int type)
container.insertTab(
res.getI18NString("impl.media.configform.ENCODINGS"),
null,
createEncodingControls(type),
new PriorityTable(
new EncodingConfigurationTableModel(
mediaService.getEncodingConfiguration(),
type),
100),
null,
1);
}
@ -620,102 +624,6 @@ private static Component createControls(int type)
return container;
}
/**
* Creates Component for the encodings of type(AUDIO or VIDEO).
* @param type the type
* @return the component.
*/
private static Component createEncodingControls(int type)
{
ResourceManagementService resources = NeomediaActivator.getResources();
String key;
final JTable table = new JTable();
table.setShowGrid(false);
table.setTableHeader(null);
key = "impl.media.configform.UP";
final JButton upButton = new JButton(resources.getI18NString(key));
upButton.setMnemonic(resources.getI18nMnemonic(key));
upButton.setOpaque(false);
key = "impl.media.configform.DOWN";
final JButton downButton = new JButton(resources.getI18NString(key));
downButton.setMnemonic(resources.getI18nMnemonic(key));
downButton.setOpaque(false);
Container buttonBar = new TransparentPanel(new GridLayout(0, 1));
buttonBar.add(upButton);
buttonBar.add(downButton);
Container parentButtonBar = new TransparentPanel(new BorderLayout());
parentButtonBar.add(buttonBar, BorderLayout.NORTH);
Container container = new TransparentPanel(new BorderLayout());
container.setPreferredSize(new Dimension(WIDTH, 100));
container.setMaximumSize(new Dimension(WIDTH, 100));
container.add(new JScrollPane(table), BorderLayout.CENTER);
container.add(parentButtonBar, BorderLayout.EAST);
table.setModel(new EncodingConfigurationTableModel(mediaService
.getEncodingConfiguration(), type));
/*
* The first column contains the check boxes which enable/disable their
* associated encodings and it doesn't make sense to make it wider than
* the check boxes.
*/
TableColumnModel tableColumnModel = table.getColumnModel();
TableColumn tableColumn = tableColumnModel.getColumn(0);
tableColumn.setMaxWidth(tableColumn.getMinWidth());
ListSelectionListener tableSelectionListener =
new ListSelectionListener()
{
public void valueChanged(ListSelectionEvent event)
{
if (table.getSelectedRowCount() == 1)
{
int selectedRow = table.getSelectedRow();
if (selectedRow > -1)
{
upButton.setEnabled(selectedRow > 0);
downButton.setEnabled(selectedRow < (table
.getRowCount() - 1));
return;
}
}
upButton.setEnabled(false);
downButton.setEnabled(false);
}
};
table.getSelectionModel().addListSelectionListener(
tableSelectionListener);
tableSelectionListener.valueChanged(null);
ActionListener buttonListener = new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
Object source = event.getSource();
boolean up;
if (source == upButton)
up = true;
else if (source == downButton)
up = false;
else
return;
move(table, up);
}
};
upButton.addActionListener(buttonListener);
downButton.addActionListener(buttonListener);
return container;
}
/**
* Creates preview for the (video) device in the video container.
*
@ -895,19 +803,6 @@ private static String getLabelText(int type)
}
}
/**
* Used to move encoding options.
* @param table the table with encodings
* @param up move direction.
*/
private static void move(JTable table, boolean up)
{
int index =
((EncodingConfigurationTableModel) table.getModel()).move(table
.getSelectedRow(), up);
table.getSelectionModel().setSelectionInterval(index, index);
}
/**
* Creates the video advanced settings.
*

@ -110,6 +110,7 @@ protected void addZRTPAdvertisedEncryptions(
&& accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true)
&& accountID.isEncryptionProtocolEnabled("ZRTP")
&& getPeer().getCall().isSipZrtpAttribute())
{
addAdvertisedEncryptionMethod(SrtpControlType.ZRTP);
@ -143,9 +144,7 @@ protected void addSDESAdvertisedEncryptions(
= getPeer().getProtocolProvider().getAccountID();
// SDES
if(accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.SDES_ENABLED,
false)
if(accountID.isEncryptionProtocolEnabled("SDES")
&& accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true))
@ -205,9 +204,7 @@ else if(isInitiator)
= getPeer().getProtocolProvider().getAccountID();
// SDES
if(accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.SDES_ENABLED,
false)
if(accountID.isEncryptionProtocolEnabled("SDES")
&& accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true))
@ -328,6 +325,8 @@ && isRemoteZrtpCapable(remoteEncryption))
.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true)
&& getPeer().getProtocolProvider().getAccountID()
.isEncryptionProtocolEnabled("ZRTP")
&& getPeer().getCall().isSipZrtpAttribute()
&& isRemoteZrtpCapable)
{
@ -373,8 +372,8 @@ && getPeer().getCall().isSipZrtpAttribute()
*
* @param mediaType The type of media we are modifying the DESCRIPTION to
* integrate the ENCRYPTION element.
* @param description The element containing the media DESCRIPTION and its
* encryption.
* @param localDescription The element containing the media DESCRIPTION and
* its encryption.
* @param remoteDescription The element containing the media DESCRIPTION and
* its encryption for the remote peer. Null, if the local peer is the
* initiator of the call.
@ -390,9 +389,7 @@ protected boolean setSDesEncryptionToDescription(
AccountID accountID = getPeer().getProtocolProvider().getAccountID();
// check if SDES and encryption is enabled at all
if(accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.SDES_ENABLED,
false)
if(accountID.isEncryptionProtocolEnabled("SDES")
&& accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true))
@ -499,4 +496,67 @@ protected boolean setSDesEncryptionToDescription(
return false;
}
/**
* Selects the preferred encryption protocol (only used by the callee).
*
* @param mediaType The type of media (AUDIO or VIDEO).
* @param localDescription The element containing the media DESCRIPTION and
* its encryption.
* @param remoteDescription The element containing the media DESCRIPTION and
* its encryption for the remote peer. Null, if the local peer is the
* initiator of the call.
*/
protected void setAndAddPreferredEncryptionProtocol(
MediaType mediaType,
RtpDescriptionPacketExtension localDescription,
RtpDescriptionPacketExtension remoteDescription)
{
// Sets ZRTP or SDES, depending on the preferences for this account.
ArrayList<String> encryptionProtocolList = new ArrayList<String>(2);
encryptionProtocolList.add("ZRTP");
encryptionProtocolList.add("SDES");
List<String> preferredEncryptionProtocols = getPeer()
.getProtocolProvider()
.getAccountID()
.getSortedEnabledEncryptionProtocolList(encryptionProtocolList);
for(int i = 0; i < preferredEncryptionProtocols.size(); ++i)
{
// ZRTP
if(preferredEncryptionProtocols.get(i).equals("ZRTP"))
{
boolean isZRTPAddedToDescription
= setZrtpEncryptionToDescription(
mediaType,
localDescription,
remoteDescription);
if(isZRTPAddedToDescription)
{
addZRTPAdvertisedEncryptions(
false,
remoteDescription,
mediaType);
// Stops once an encryption advertisement has been choosen.
return;
}
}
// SDES
else if(preferredEncryptionProtocols.get(i).equals("SDES"))
{
addSDESAdvertisedEncryptions(
false,
remoteDescription,
mediaType);
if(setSDesEncryptionToDescription(
mediaType,
localDescription,
remoteDescription))
{
// Stops once an encryption advertisement has been choosen.
return;
}
}
}
}
}

@ -303,30 +303,11 @@ public RtpDescriptionPacketExtension generateSessionAccept(
}
}
// ZRTP
boolean isZRTPAddedToDescription = setZrtpEncryptionToDescription(
// Sets ZRTP or SDES, depending on the preferences for this account.
setAndAddPreferredEncryptionProtocol(
mediaType,
description,
remoteDescription);
if(isZRTPAddedToDescription)
{
addZRTPAdvertisedEncryptions(
false,
remoteDescription,
mediaType);
}
else
{
// SDES
addSDESAdvertisedEncryptions(
false,
remoteDescription,
mediaType);
setSDesEncryptionToDescription(
mediaType,
description,
remoteDescription);
}
initStream(mediaName, connector, dev, format, target,
direction, rtpExtensions, masterStream);

@ -350,24 +350,11 @@ public void processOffer(List<ContentPacketExtension> offer)
RtpDescriptionPacketExtension localDescription =
JingleUtils.getRtpDescription(ourContent);
// ZRTP
boolean isZRTPAddedToDescription = setZrtpEncryptionToDescription(
// Sets ZRTP or SDES, depending on the preferences for this account.
setAndAddPreferredEncryptionProtocol(
mediaType,
localDescription,
description);
if(isZRTPAddedToDescription)
{
addZRTPAdvertisedEncryptions(false, description, mediaType);
}
else
{
// SDES
addSDESAdvertisedEncryptions(false, description, mediaType);
setSDesEncryptionToDescription(
mediaType,
localDescription,
description);
}
// Got a content which has inputevt. It means that the peer requests
// a desktop sharing session so tell it we support inputevt.

@ -588,10 +588,11 @@ private Vector<MediaDescription> createMediaDescriptionsForAnswer(
mutuallySupportedFormats, connector,
direction, rtpExtensions);
if(!updateMediaDescriptionForZrtp(mediaType, md))
{
updateMediaDescriptionForSDes(mediaType, md, mediaDescription);
}
// Sets ZRTP or SDES, depending on the preferences for this account.
this.setAndAddPreferredEncryptionProtocol(
mediaType,
md,
mediaDescription);
// create the corresponding stream...
MediaFormat fmt = findMediaFormat(remoteFormats,
@ -655,6 +656,8 @@ private boolean updateMediaDescriptionForZrtp(
if(peer.getProtocolProvider().getAccountID().getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true)
&& peer.getProtocolProvider().getAccountID()
.isEncryptionProtocolEnabled("ZRTP")
&& peer.getCall().isSipZrtpAttribute())
{
try
@ -706,9 +709,7 @@ private boolean updateMediaDescriptionForSDes(
AccountID accountID = getPeer().getProtocolProvider().getAccountID();
// check if SDES and encryption is enabled at all
if (!accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.SDES_ENABLED,
false)
if(!accountID.isEncryptionProtocolEnabled("SDES")
|| !accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true))
@ -1253,4 +1254,51 @@ protected SrtpCryptoAttribute selectSdesCryptoSuite(
return sDesControl.responderSelectAttribute(peerAttributes);
}
}
/**
* Selects the preferred encryption protocol (only used by the callee).
*
* @param mediaType The type of media (AUDIO or VIDEO).
* @param localMd the description of the local peer.
* @param remoteMd the description of the remote peer.
*/
protected void setAndAddPreferredEncryptionProtocol(
MediaType mediaType,
MediaDescription localMd,
MediaDescription remoteMd)
{
// Sets ZRTP or SDES, depending on the preferences for this account.
ArrayList<String> encryptionProtocolList = new ArrayList<String>(2);
encryptionProtocolList.add("ZRTP");
encryptionProtocolList.add("SDES");
List<String> preferredEncryptionProtocols = getPeer()
.getProtocolProvider()
.getAccountID()
.getSortedEnabledEncryptionProtocolList(encryptionProtocolList);
for(int i = 0; i < preferredEncryptionProtocols.size(); ++i)
{
// ZRTP
if(preferredEncryptionProtocols.get(i).equals("ZRTP"))
{
if(updateMediaDescriptionForZrtp(mediaType, localMd))
{
// Stops once an encryption advertisement has been choosen.
return;
}
}
// SDES
else if(preferredEncryptionProtocols.get(i).equals("SDES"))
{
if(updateMediaDescriptionForSDes(
mediaType,
localMd,
remoteMd))
{
// Stops once an encryption advertisement has been choosen.
return;
}
}
}
}
}

@ -206,6 +206,16 @@ public class JabberAccountRegistration
*/
private boolean defaultEncryption = true;
/**
* The list of enabled encryption protocols in the priority order.
*/
private List<String> enabledEncryptionProtocols;
/**
* The list of disabled encryption protocols in the priority order.
*/
private List<String> disabledEncryptionProtocols;
/**
* Enqbles ZRTP encryption.
*/
@ -221,6 +231,17 @@ public class JabberAccountRegistration
*/
private String sdesCipherSuites = null;
/**
* Initializes a new JabberAccountRegistration.
*/
public JabberAccountRegistration()
{
// Sets the default values.
this.enabledEncryptionProtocols = new ArrayList<String>(1);
this.enabledEncryptionProtocols.add("ZRTP");
this.disabledEncryptionProtocols = new ArrayList<String>(0);
}
/**
* Returns the password of the jabber registration account.
* @return the password of the jabber registration account.
@ -929,4 +950,43 @@ public void setSavpOption(int savpOption)
// SAVP option is not useful for XMPP account.
// Thereby, do nothing.
}
/**
* Returns the list of the enabled or disabled encryption protocols in the
* priority order.
*
* @param enabled If true this function will return the enabled encryption
* protocol list. Otherwise, it will return the disabled list.
*
* @return the list of the enabled or disabled encryption protocols in the
* priority order.
*/
public List<String> getEncryptionProtocols(boolean enabled)
{
if(enabled)
{
return enabledEncryptionProtocols;
}
else
{
return disabledEncryptionProtocols;
}
}
/**
* Sets the list of the enabled and disabled encryption protocols in the
* priority order.
*
* @param enabledEncrpytionProtools The list of the enabled encryption
* protocols in the priority order.
* @param disabledEncrpytionProtools The list of the disabled encryption
* protocols in the priority order.
*/
public void setEncryptionProtocols(
List<String> enabledEncryptionProtocols,
List<String> disabledEncryptionProtocols)
{
this.enabledEncryptionProtocols = enabledEncryptionProtocols;
this.disabledEncryptionProtocols = disabledEncryptionProtocols;
}
}

@ -443,12 +443,33 @@ protected ProtocolProviderService installAccount(
accountProperties.put(ProtocolProviderFactory.DEFAULT_ENCRYPTION,
Boolean.toString(registration.isDefaultEncryption()));
List<String> enabledEncryptionProtocols
= registration.getEncryptionProtocols(true);
String enabledEncryptionProtocolsString = "";
for(int i = 0; i < enabledEncryptionProtocols.size(); ++i)
{
enabledEncryptionProtocolsString
+= enabledEncryptionProtocols.get(i) + " ";
}
accountProperties.put(
ProtocolProviderFactory.ENABLED_ENCRYPTION_PROTOCOLS,
enabledEncryptionProtocolsString);
List<String> disabledEncryptionProtocols
= registration.getEncryptionProtocols(false);
String disabledEncryptionProtocolsString = "";
for(int i = 0; i < disabledEncryptionProtocols.size(); ++i)
{
disabledEncryptionProtocolsString
+= disabledEncryptionProtocols.get(i) + " ";
}
accountProperties.put(
ProtocolProviderFactory.DISABLED_ENCRYPTION_PROTOCOLS,
disabledEncryptionProtocolsString);
accountProperties.put(ProtocolProviderFactory.DEFAULT_SIPZRTP_ATTRIBUTE,
Boolean.toString(registration.isSipZrtpAttribute()));
accountProperties.put(ProtocolProviderFactory.SDES_ENABLED,
Boolean.toString(registration.isSDesEnabled()));
accountProperties.put(ProtocolProviderFactory.SDES_CIPHER_SUITES,
registration.getSDesCipherSuites());

@ -0,0 +1,195 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.plugin.sipaccregwizz;
import java.util.*;
import net.java.sip.communicator.util.swing.*;
/**
* Implements {@link TableModel} for encryption configuration (ZRTP, SDES and
* MIKEY).
*
* @author Lyubomir Marinov
* @author Vincent Lucas
*/
public class EncryptionConfigurationTableModel
extends MoveableTableModel
{
/**
* Serial version UID.
*/
private static final long serialVersionUID = 0L;
private boolean[] selectionList;
private String[] labelList;
/**
* Creates a new table model in order to manage the encryption protocols and
* the corresponding priority.
*
* @param selectionList A list of boolean which is used to know of the
* corresponding protocol (same index) from the labelList is enabled or
* disabled.
* @param labelList The list of encryption protocols in the priority
* order.
*/
public EncryptionConfigurationTableModel(
boolean[] selectionList,
String[] labelList)
{
this.init(selectionList, labelList);
}
@Override
public Class<?> getColumnClass(int columnIndex)
{
return
(columnIndex == 0)
? Boolean.class
: super.getColumnClass(columnIndex);
}
public int getColumnCount()
{
return 2;
}
public int getRowCount()
{
//return getEncodings().length;
return labelList.length;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return (columnIndex == 0);
}
public Object getValueAt(int rowIndex, int columnIndex)
{
switch (columnIndex)
{
case 0:
return selectionList[rowIndex];
case 1:
return labelList[rowIndex];
default:
return null;
}
}
@Override
public void setValueAt(Object value, int rowIndex, int columnIndex)
{
if ((columnIndex == 0) && (value instanceof Boolean))
{
this.selectionList[rowIndex] = ((Boolean) value).booleanValue();
// We fire the update event before setting the configuration
// property in order to have more reactive user interface.
fireTableCellUpdated(rowIndex, columnIndex);
//encodingConfiguration.setPriorityConfig(encoding, priority);
}
}
/**
* Move the row.
*
* @param rowIndex index of the row
* @param up true to move up, false to move down
* @return the next row index
*/
public int move(int rowIndex, boolean up)
{
int toRowIndex;
if (up)
{
toRowIndex = rowIndex - 1;
if (toRowIndex < 0)
throw new IllegalArgumentException("rowIndex");
}
else
{
toRowIndex = rowIndex + 1;
if (toRowIndex >= getRowCount())
throw new IllegalArgumentException("rowIndex");
}
// Swaps the selection list.
boolean tmpSelectionItem = this.selectionList[rowIndex];
this.selectionList[rowIndex] = this.selectionList[toRowIndex];
this.selectionList[toRowIndex] = tmpSelectionItem;
// Swaps the label list.
String tmpLabel = this.labelList[rowIndex];
this.labelList[rowIndex] = this.labelList[toRowIndex];
this.labelList[toRowIndex] = tmpLabel;
fireTableRowsUpdated(rowIndex, toRowIndex);
return toRowIndex;
}
/**
* Returns the list of the enabled or disabled label in the priority order.
*
* @param enabledLabels If true this function will return the enabled label
* list. Otherwise, it will return the disabled list.
*
* @return the list of the enabled or disabled label in the priority order.
*/
public List<String> getLabels(boolean enabledLabels)
{
ArrayList<String> labels = new ArrayList<String>(this.labelList.length);
for(int i = 0; i < this.labelList.length; ++i)
{
if(this.selectionList[i] == enabledLabels)
{
labels.add(this.labelList[i]);
}
}
return labels;
}
/**
* Returns if the label is enabled or disabled.
*
* @param label The label to be determined as enabled or disabled.
*
* @return True if the label given in parameter is enabled. False,
* otherwise.
*/
public boolean isEnabledLabel(String label)
{
for(int i = 0; i < this.labelList.length; ++i)
{
if(this.labelList[i].equals(label))
{
return this.selectionList[i];
}
}
return false;
}
/**
* Initiates this table model in order to manage the encryption protocols and
* the corresponding priority.
*
* @param selectionList A list of boolean which is used to know of the
* corresponding protocol (same index) from the labelList is enabled or
* disabled.
* @param labelList The list of encryption protocols in the priority
* order.
*/
public void init(boolean[] selectionList, String[] labelList)
{
this.selectionList = selectionList;
this.labelList = labelList;
}
}

@ -5,6 +5,8 @@
*/
package net.java.sip.communicator.plugin.sipaccregwizz;
import java.util.*;
/**
* The <tt>SIPAccountRegistration</tt> is used to store all user input data
* through the <tt>SIPAccountRegistrationWizard</tt>.
@ -74,6 +76,16 @@ public class SIPAccountRegistration
*/
private boolean defaultEncryption = true;
/**
* The list of enabled encryption protocols in the priority order.
*/
private List<String> enabledEncryptionProtocols;
/**
* The list of disabled encryption protocols in the priority order.
*/
private List<String> disabledEncryptionProtocols;
/**
* Enqbles ZRTP encryption.
*/
@ -130,6 +142,17 @@ public class SIPAccountRegistration
*/
private boolean messageWaitingIndications = true;
/**
* Initializes a new SIPAccountRegistration.
*/
public SIPAccountRegistration()
{
// Sets the default values.
this.enabledEncryptionProtocols = new ArrayList<String>(1);
this.enabledEncryptionProtocols.add("ZRTP");
this.disabledEncryptionProtocols = new ArrayList<String>(0);
}
public String getPreferredTransport()
{
return preferredTransport;
@ -949,4 +972,43 @@ public void setMessageWaitingIndications(boolean messageWaitingIndications)
{
this.messageWaitingIndications = messageWaitingIndications;
}
/**
* Returns the list of the enabled or disabled encryption protocols in the
* priority order.
*
* @param enabled If true this function will return the enabled encryption
* protocol list. Otherwise, it will return the disabled list.
*
* @return the list of the enabled or disabled encryption protocols in the
* priority order.
*/
public List<String> getEncryptionProtocols(boolean enabled)
{
if(enabled)
{
return enabledEncryptionProtocols;
}
else
{
return disabledEncryptionProtocols;
}
}
/**
* Sets the list of the enabled and disabled encryption protocols in the
* priority order.
*
* @param enabledEncrpytionProtools The list of the enabled encryption
* protocols in the priority order.
* @param disabledEncrpytionProtools The list of the disabled encryption
* protocols in the priority order.
*/
public void setEncryptionProtocols(
List<String> enabledEncryptionProtocols,
List<String> disabledEncryptionProtocols)
{
this.enabledEncryptionProtocols = enabledEncryptionProtocols;
this.disabledEncryptionProtocols = disabledEncryptionProtocols;
}
}

@ -467,15 +467,36 @@ else if(serverAddress == null &&
accountProperties.put(ProtocolProviderFactory.DEFAULT_ENCRYPTION,
Boolean.toString(registration.isDefaultEncryption()));
java.util.List<String> enabledEncryptionProtocols
= registration.getEncryptionProtocols(true);
String enabledEncryptionProtocolsString = "";
for(int i = 0; i < enabledEncryptionProtocols.size(); ++i)
{
enabledEncryptionProtocolsString
+= enabledEncryptionProtocols.get(i) + " ";
}
accountProperties.put(
ProtocolProviderFactory.ENABLED_ENCRYPTION_PROTOCOLS,
enabledEncryptionProtocolsString);
java.util.List<String> disabledEncryptionProtocols
= registration.getEncryptionProtocols(false);
String disabledEncryptionProtocolsString = "";
for(int i = 0; i < disabledEncryptionProtocols.size(); ++i)
{
disabledEncryptionProtocolsString
+= disabledEncryptionProtocols.get(i) + " ";
}
accountProperties.put(
ProtocolProviderFactory.DISABLED_ENCRYPTION_PROTOCOLS,
disabledEncryptionProtocolsString);
accountProperties.put(ProtocolProviderFactory.DEFAULT_SIPZRTP_ATTRIBUTE,
Boolean.toString(registration.isSipZrtpAttribute()));
accountProperties.put(ProtocolProviderFactory.SAVP_OPTION,
Integer.toString(registration.getSavpOption()));
accountProperties.put(ProtocolProviderFactory.SDES_ENABLED,
Boolean.toString(registration.isSDesEnabled()));
accountProperties.put(ProtocolProviderFactory.SDES_CIPHER_SUITES,
registration.getSDesCipherSuites());

@ -5,6 +5,8 @@
*/
package net.java.sip.communicator.plugin.sipaccregwizz;
import java.util.*;
/**
* The <tt>SecurityAccountRegistration</tt> is used to determine security
* options for different registration protocol (Jabber, SIP). Useful fot the
@ -76,4 +78,29 @@ public interface SecurityAccountRegistration
* Sets the method used for RTP/SAVP indication.
*/
public void setSavpOption(int savpOption);
/**
* Returns the list of the enabled or disabled encryption protocols in the
* priority order.
*
* @param enabled If true this function will return the enabled encryption
* protocol list. Otherwise, it will return the disabled list.
*
* @return the list of the enabled or disabled encryption protocols in the
* priority order.
*/
public List<String> getEncryptionProtocols(boolean enabled);
/**
* Sets the list of the enabled and disabled encryption protocols in the
* priority order.
*
* @param enabledEncrpytionProtools The list of the enabled encryption
* protocols in the priority order.
* @param disabledEncrpytionProtools The list of the disabled encryption
* protocols in the priority order.
*/
public void setEncryptionProtocols(
List<String> enabledEncryptionProtocols,
List<String> disabledEncryptionProtocols);
}

@ -1,3 +1,8 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license. See terms of license at gnu.org.
*/
package net.java.sip.communicator.plugin.sipaccregwizz;
import java.awt.*;
@ -8,7 +13,9 @@
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
import javax.swing.event.*;
import net.java.sip.communicator.impl.neomedia.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.util.swing.*;
@ -20,10 +27,12 @@
* Contains the security settings for SIP media encryption.
*
* @author Ingo Bauersachs
* @author Vincent Lucas
*/
public class SecurityPanel
extends TransparentPanel
implements ActionListener
implements ActionListener,
TableModelListener
{
/**
* Serial version UID.
@ -35,12 +44,27 @@ public class SecurityPanel
private JPanel pnlAdvancedSettings;
private JCheckBox enableDefaultEncryption;
private JCheckBox enableSipZrtpAttribute;
private JCheckBox enableSDesAttribute;
private JComboBox cboSavpOption;
private JTable tabCiphers;
private CipherTableModel cipherModel;
private JLabel cmdExpandAdvancedSettings;
/**
* The TableModel used to configure the encryption protocols preferences.
*/
private EncryptionConfigurationTableModel encryptionConfigurationTableModel;
/**
* JTable with 2 buttons (up and down) which able to enable encryption
* protocols and to choose their priority order.
*/
private PriorityTable encryptionProtocolPreferences;
/**
* The encryption protocols managed by this SecurityPanel.
*/
private static final String[] encryptionProtocols = {"ZRTP", "SDES"};
/**
* Boolean used to display or not the SAVP options (only useful for SIP, not
* for XMPP).
@ -275,82 +299,117 @@ public void mouseClicked(MouseEvent e)
c = new GridBagConstraints();
c.gridx = 0;
c.gridy = 0;
c.anchor = GridBagConstraints.LINE_START;
c.gridwidth = 2;
c.gridheight = 1;
c.anchor = GridBagConstraints.LINE_START;
c.fill = GridBagConstraints.HORIZONTAL;
pnlAdvancedSettings.add(new JSeparator(), c);
// Encryption protcol preferences.
JLabel lblEncryptionProtocolPreferences = new JLabel();
lblEncryptionProtocolPreferences.setText(Resources
.getString("plugin.sipaccregwizz.ENCRYPTION_PROTOCOL_PREFERENCES"));
c.gridy++;
pnlAdvancedSettings.add(lblEncryptionProtocolPreferences, c);
int nbEncryptionProtocols = this.encryptionProtocols.length;
String[] encryptions = new String[nbEncryptionProtocols];
boolean[] selectedEncryptions = new boolean[nbEncryptionProtocols];
this.encryptionConfigurationTableModel
= new EncryptionConfigurationTableModel(
selectedEncryptions,
encryptions);
this.encryptionProtocolPreferences = new PriorityTable(
this.encryptionConfigurationTableModel,
60);
this.encryptionConfigurationTableModel.addTableModelListener(this);
c.gridy++;
pnlAdvancedSettings.add(this.encryptionProtocolPreferences, c);
//ZRTP
JLabel lblZrtpOption = new JLabel();
lblZrtpOption.setBorder(new EmptyBorder(5, 5, 5, 0));
lblZrtpOption.setText(
Resources.getString("plugin.sipaccregwizz.ZRTP_OPTION"));
c.gridx = 0;
c.gridy++;
c.gridwidth = 1;
pnlAdvancedSettings.add(lblZrtpOption, c);
c.gridx = 1;
pnlAdvancedSettings.add(new JSeparator(), c);
enableSipZrtpAttribute = new SIPCommCheckBox(Resources
.getString("plugin.sipaccregwizz.ENABLE_SIPZRTP_ATTRIBUTE"),
regform.isSipZrtpAttribute());
c.gridx = 0;
c.gridy++;
c.gridwidth = 2;
pnlAdvancedSettings.add(enableSipZrtpAttribute, c);
//SDES
JLabel lblSDesOption = new JLabel();
lblSDesOption.setBorder(new EmptyBorder(5, 5, 5, 0));
lblSDesOption.setText(
Resources.getString("plugin.sipaccregwizz.SDES_OPTION"));
c.gridx = 0;
c.gridy++;
c.gridwidth = 1;
pnlAdvancedSettings.add(lblSDesOption, c);
c.gridx = 1;
pnlAdvancedSettings.add(new JSeparator(), c);
JLabel lblCipherInfo = new JLabel();
lblCipherInfo.setText(Resources
.getString("plugin.sipaccregwizz.CIPHER_SUITES"));
c.gridx = 0;
c.gridy++;
c.gridwidth = 2;
pnlAdvancedSettings.add(lblCipherInfo, c);
cipherModel = new CipherTableModel(regform.getSDesCipherSuites());
tabCiphers = new JTable(cipherModel);
tabCiphers.setShowGrid(false);
tabCiphers.setTableHeader(null);
TableColumnModel tableColumnModel = tabCiphers.getColumnModel();
TableColumn tableColumn = tableColumnModel.getColumn(0);
tableColumn.setMaxWidth(tableColumn.getMinWidth());
JScrollPane scrollPane = new JScrollPane(tabCiphers);
scrollPane.setPreferredSize(new Dimension(tabCiphers.getWidth(), 100));
c.gridy++;
pnlAdvancedSettings.add(scrollPane, c);
//SAVP selection
c.gridx = 0;
c.gridwidth = 1;
JLabel lblSavpOption = new JLabel();
lblSavpOption.setBorder(new EmptyBorder(5, 5, 5, 0));
lblSavpOption.setText(
Resources.getString("plugin.sipaccregwizz.SAVP_OPTION"));
c.gridy++;
if(this.displaySavpOtions)
{
c.gridy++;
pnlAdvancedSettings.add(lblSavpOption, c);
}
c.gridx = 2;
c.weightx = 1;
c.gridx = 1;
if(this.displaySavpOtions)
{
pnlAdvancedSettings.add(new JSeparator(), c);
}
cboSavpOption = new JComboBox(new SavpOption[]{
new SavpOption(0),
new SavpOption(1),
new SavpOption(2)
});
c.gridx = 1;
c.gridy++;
c.gridx = 0;
c.gridwidth = 2;
c.insets = new Insets(0, 20, 0, 0);
c.weightx = 0;
if(this.displaySavpOtions)
{
c.gridy++;
pnlAdvancedSettings.add(cboSavpOption, c);
}
//SDES
enableSDesAttribute = new SIPCommCheckBox(Resources
.getString("plugin.sipaccregwizz.ENABLE_SDES_ATTRIBUTE"),
regform.isSDesEnabled());
enableSDesAttribute.addActionListener(this);
c.gridy++;
c.gridwidth = 1;
c.insets = new Insets(15, 0, 0, 0);
pnlAdvancedSettings.add(enableSDesAttribute, c);
c.gridx = 2;
c.weightx = 1;
pnlAdvancedSettings.add(new JSeparator(), c);
c.gridy++;
c.gridx = 1;
c.gridwidth = 2;
c.insets = new Insets(0, 20, 0, 0);
JLabel lblCipherInfo = new JLabel();
lblCipherInfo.setText(Resources
.getString("plugin.sipaccregwizz.CIPHER_SUITES"));
pnlAdvancedSettings.add(lblCipherInfo, c);
cipherModel = new CipherTableModel(regform.getSDesCipherSuites());
tabCiphers = new JTable(cipherModel);
tabCiphers.setShowGrid(false);
tabCiphers.setTableHeader(null);
TableColumnModel tableColumnModel = tabCiphers.getColumnModel();
TableColumn tableColumn = tableColumnModel.getColumn(0);
tableColumn.setMaxWidth(tableColumn.getMinWidth());
c.gridy++;
c.insets = new Insets(0, 20, 0, 0);
JScrollPane scrollPane = new JScrollPane(tabCiphers);
scrollPane.setPreferredSize(new Dimension(tabCiphers.getWidth(), 100));
pnlAdvancedSettings.add(scrollPane, c);
}
/**
@ -362,11 +421,14 @@ public void mouseClicked(MouseEvent e)
public boolean commitPanel(SecurityAccountRegistration registration)
{
registration.setDefaultEncryption(enableDefaultEncryption.isSelected());
registration.setEncryptionProtocols(
encryptionConfigurationTableModel.getLabels(true),
encryptionConfigurationTableModel.getLabels(false));
registration.setSipZrtpAttribute(enableSipZrtpAttribute.isSelected());
registration.setSavpOption(((SavpOption) cboSavpOption
.getSelectedItem()).option);
registration.setSDesEnabled(enableSDesAttribute.isSelected());
registration.setSDesCipherSuites(cipherModel.getEnabledCiphers());
return true;
}
@ -380,6 +442,9 @@ public void loadAccount(AccountID accountID)
accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true));
this.loadEncryptionProtocols(
accountID.getEncryptionProtocols(true),
accountID.getEncryptionProtocols(false));
enableSipZrtpAttribute.setSelected(
accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_SIPZRTP_ATTRIBUTE,
@ -388,10 +453,6 @@ public void loadAccount(AccountID accountID)
accountID.getAccountPropertyInt(
ProtocolProviderFactory.SAVP_OPTION,
ProtocolProviderFactory.SAVP_OFF));
enableSDesAttribute.setSelected(
accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.SDES_ENABLED,
false));
cipherModel.loadData(
accountID.getAccountPropertyString(
ProtocolProviderFactory.SDES_CIPHER_SUITES));
@ -400,8 +461,7 @@ public void loadAccount(AccountID accountID)
public void actionPerformed(ActionEvent e)
{
if((e.getSource() == enableDefaultEncryption)
|| (e.getSource() == enableSDesAttribute))
if(e.getSource() == enableDefaultEncryption)
{
loadStates();
}
@ -411,12 +471,81 @@ else if(e.getSource() == cmdExpandAdvancedSettings)
}
}
public void tableChanged(TableModelEvent e)
{
if(e.getSource() == this.encryptionConfigurationTableModel)
{
loadStates();
}
}
private void loadStates()
{
boolean b = enableDefaultEncryption.isSelected();
enableSipZrtpAttribute.setEnabled(b);
cboSavpOption.setEnabled(b);
enableSDesAttribute.setEnabled(b);
tabCiphers.setEnabled(b && enableSDesAttribute.isSelected());
this.encryptionProtocolPreferences.setEnabled(b);
enableSipZrtpAttribute.setEnabled(
b
&& this.encryptionConfigurationTableModel.isEnabledLabel("ZRTP"));
tabCiphers.setEnabled(
b
&& this.encryptionConfigurationTableModel.isEnabledLabel("SDES"));
}
/**
* Loads the list of enabled and disabled encryption protocols with their
* priority.
*
* @param enabledEncryptionProtocols The list of enabled encryption protocol
* available for this account.
* @param disabledEncryptionProtocols The list of disabled encryption protocol
* available for this account.
*/
private void loadEncryptionProtocols(
List<String> enabledEncryptionProtocols,
List<String> disabledEncryptionProtocols)
{
int nbEncryptionProtocols = this.encryptionProtocols.length;
String[] encryptions = new String[nbEncryptionProtocols];
boolean[] selectedEncryptions = new boolean[nbEncryptionProtocols];
List<String> availableEncryptionProtocols =
Arrays.asList(this.encryptionProtocols);
int index = 0;
for(int i = 0; i < enabledEncryptionProtocols.size(); ++i)
{
if(availableEncryptionProtocols.contains(
enabledEncryptionProtocols.get(i)))
{
encryptions[index] = enabledEncryptionProtocols.get(i);
selectedEncryptions[index] = true;
++index;
}
}
for(int i = 0; i < disabledEncryptionProtocols.size(); ++i)
{
if(availableEncryptionProtocols.contains(
disabledEncryptionProtocols.get(i)))
{
encryptions[index] = disabledEncryptionProtocols.get(i);
selectedEncryptions[index] = false;
++index;
}
}
List<String> alreadyLoadedEncryptionProtocols =
Arrays.asList(encryptions);
for(int i = 0; i < this.encryptionProtocols.length; ++i)
{
if(!alreadyLoadedEncryptionProtocols.contains(encryptionProtocols[i]))
{
encryptions[index] = encryptionProtocols[i];
selectedEncryptions[index] = false;
++index;
}
}
this.encryptionConfigurationTableModel.init(
selectedEncryptions,
encryptions);
}
}

@ -435,4 +435,99 @@ public void setAccountProperties(Map<String, String> accountProperties)
{
this.accountProperties = accountProperties;
}
/**
* Returns if the encryption protocol given in parameter is enabled.
*
* @param encryptionProtocolName The name of the encryption protocol
* ("ZRTP", "SDES" or "MIKEY").
*/
public boolean isEncryptionProtocolEnabled(String encryptionProtocolName)
{
List<String> encryptionProtocolList = this.getEncryptionProtocols(true);
for(int i = 0; i < encryptionProtocolList.size(); ++i)
{
if(encryptionProtocolName.equals(encryptionProtocolList.get(i)))
{
return true;
}
}
return false;
}
/**
* Returns the enabled or disabled (depending on the enabled parameter)
* encryption protocol list.
*
* @param enabled Set this parameter to true in order to get the enabled
* protocol list. Otherwise, when this parameter is set to false, returns
* the disabled list.
* @return The enabled or disabled encryption protocol list.
*/
public List<String> getEncryptionProtocols(boolean enabled)
{
String encryptionProtocols;
if(enabled)
{
encryptionProtocols = getAccountPropertyString(
ProtocolProviderFactory.ENABLED_ENCRYPTION_PROTOCOLS);
// If this property is not set yet, activate ZRTP only by default
if(encryptionProtocols == null)
{
ArrayList<String> result = new ArrayList<String>(1);
result.add("ZRTP");
return result;
}
}
else
{
encryptionProtocols = getAccountPropertyString(
ProtocolProviderFactory.DISABLED_ENCRYPTION_PROTOCOLS);
if(encryptionProtocols == null)
{
return new ArrayList<String>(0);
}
}
String[] tmp = encryptionProtocols.split(" ");
ArrayList<String> encryptionProtocolList
= new ArrayList<String>(tmp.length);
for(int i = 0; i < tmp.length; ++i)
{
encryptionProtocolList.add(tmp[i]);
}
return encryptionProtocolList;
}
/**
* Sorts the enabled encryption protocol list given in parameter to match
* the preferences set for this account.
*
* @param encryptionProtocolList The list of the encryption protocol to
* check.
*
* @return Sorts the enabled encryption protocol list given in parameter to
* match the preferences set for this account.
*/
public List<String> getSortedEnabledEncryptionProtocolList(
List<String> encryptionProtocolList)
{
List<String> enabledEncryptionProtocolList
= this.getEncryptionProtocols(true);
ArrayList<String> result
= new ArrayList<String>(enabledEncryptionProtocolList.size());
for(int i = 0; i < enabledEncryptionProtocolList.size(); ++i)
{
if(encryptionProtocolList.contains(
enabledEncryptionProtocolList.get(i)))
{
result.add(enabledEncryptionProtocolList.get(i));
}
}
return result;
}
}

@ -202,6 +202,20 @@ public abstract class ProtocolProviderFactory
*/
public static final String DEFAULT_ENCRYPTION = "DEFAULT_ENCRYPTION";
/**
* The name of the property that indicates the enabled encryption protocols
* for this account.
*/
public static final String ENABLED_ENCRYPTION_PROTOCOLS
= "ENABLED_ENCRYPTION_PROTOCOL";
/**
* The name of the property that indicates the disabled encryption protocols
* for this account.
*/
public static final String DISABLED_ENCRYPTION_PROTOCOLS
= "DISABLED_ENCRYPTION_PROTOCOL";
/**
* The name of the property which defines if to include the ZRTP attribute
* to SIP/SDP
@ -488,12 +502,6 @@ public abstract class ProtocolProviderFactory
*/
public static final String SDES_CIPHER_SUITES = "SDES_CIPHER_SUITES";
/**
* The name of the property that indicates if SDES is enabled for this
* account.
*/
public static final String SDES_ENABLED = "SDES_ENABLED";
/**
* Creates a new <tt>ProtocolProviderFactory</tt>.
*

@ -408,7 +408,9 @@ protected MediaStream configureStream(
break;
}
if (call.isDefaultEncrypted())
if (call.isDefaultEncrypted()
&& call.getProtocolProvider().getAccountID()
.isEncryptionProtocolEnabled("ZRTP"))
{
/*
* We'll use the audio stream as the master stream when using SRTP

@ -0,0 +1,25 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license. See terms of license at gnu.org.
*/
package net.java.sip.communicator.util.swing;
import javax.swing.table.*;
/**
* @author Vincent Lucas
*/
public abstract class MoveableTableModel
extends AbstractTableModel
{
/**
* Move the row.
*
* @param rowIndex index of the row
* @param up true to move up, false to move down
8
* @return the next row index
*/
public abstract int move(int rowIndex, boolean up);
}

@ -0,0 +1,170 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license. See terms of license at gnu.org.
*/
package net.java.sip.communicator.util.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import net.java.sip.communicator.util.*;
import org.jitsi.service.resources.*;
/**
* Creates a component containing a table and two buttons for the encodings of
* type(AUDIO or VIDEO) or to sort the priority of the encryption protocols.
*
* @author Vincent Lucas
*/
public class PriorityTable
extends TransparentPanel
{
/**
* The table containing the different elements to sort by priority.
*/
private JTable table;
/**
* The button to increase the priority of one item by moving it up in the
* table.
*/
private JButton upButton;
/**
* The button to decrease the priority of one item by moving it down in the
* table.
*/
private JButton downButton;
/**
* The preferred width of all panels.
*/
private final static int WIDTH = 350;
/**
* Creates a component for the encodings of type(AUDIO or VIDEO) or to sort
* the priority of the encryption protocols.
* @param tableModel The table model to display encodings (AUDIO or VIDEO),
* or to sort the priority of the encryption protocols.
* @param height The height (preferred and maximum height) of the component.
* @return the component.
*/
public PriorityTable(
MoveableTableModel tableModel,
int height)
{
super(new BorderLayout());
ResourceManagementService resources = UtilActivator.getResources();
String key;
String i18NresourcesKey;
table = new JTable();
table.setShowGrid(false);
table.setTableHeader(null);
key = "impl.media.configform.UP";
upButton = new JButton(resources.getI18NString(key));
upButton.setMnemonic(resources.getI18nMnemonic(key));
upButton.setOpaque(false);
key = "impl.media.configform.DOWN";
downButton = new JButton(resources.getI18NString(key));
downButton.setMnemonic(resources.getI18nMnemonic(key));
downButton.setOpaque(false);
Container buttonBar = new TransparentPanel(new GridLayout(0, 1));
buttonBar.add(upButton);
buttonBar.add(downButton);
Container parentButtonBar = new TransparentPanel(new BorderLayout());
parentButtonBar.add(buttonBar, BorderLayout.NORTH);
//Container container = new TransparentPanel(new BorderLayout());
this.setPreferredSize(new Dimension(WIDTH, height));
this.setMaximumSize(new Dimension(WIDTH, height));
this.add(new JScrollPane(table), BorderLayout.CENTER);
this.add(parentButtonBar, BorderLayout.EAST);
table.setModel(tableModel);
/*
* The first column contains the check boxes which enable/disable their
* associated encodings and it doesn't make sense to make it wider than
* the check boxes.
*/
TableColumnModel tableColumnModel = table.getColumnModel();
TableColumn tableColumn = tableColumnModel.getColumn(0);
tableColumn.setMaxWidth(tableColumn.getMinWidth());
ListSelectionListener tableSelectionListener =
new ListSelectionListener()
{
public void valueChanged(ListSelectionEvent event)
{
if (table.getSelectedRowCount() == 1)
{
int selectedRow = table.getSelectedRow();
if (selectedRow > -1)
{
upButton.setEnabled(selectedRow > 0);
downButton.setEnabled(selectedRow < (table
.getRowCount() - 1));
return;
}
}
upButton.setEnabled(false);
downButton.setEnabled(false);
}
};
table.getSelectionModel().addListSelectionListener(
tableSelectionListener);
tableSelectionListener.valueChanged(null);
ActionListener buttonListener = new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
Object source = event.getSource();
boolean up;
if (source == upButton)
up = true;
else if (source == downButton)
up = false;
else
return;
move(up);
}
};
upButton.addActionListener(buttonListener);
downButton.addActionListener(buttonListener);
}
/**
* Used to move encoding options.
* @param table the table with encodings
* @param up move direction.
*/
private void move(boolean up)
{
int index =
((MoveableTableModel) table.getModel()).move(table
.getSelectedRow(), up);
table.getSelectionModel().setSelectionInterval(index, index);
}
@Override
public void setEnabled(boolean enabled)
{
this.table.setEnabled(enabled);
this.upButton.setEnabled(enabled);
this.downButton.setEnabled(enabled);
}
}

@ -17,6 +17,7 @@ Import-Package: com.sun.awt,
javax.swing.plaf,
javax.swing.plaf.basic,
javax.swing.plaf.metal,
javax.swing.table,
javax.swing.text,
javax.swing.text.html,
javax.swing.text.html.parser,

Loading…
Cancel
Save