├── .github
└── ISSUE_TEMPLATE.md
├── .gitignore
├── .gitmodules
├── .nb-gradle-properties
├── .travis.yml
├── LICENSE
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── misc
├── kon_snap1.png
└── kon_snap2.png
├── nbproject
├── licenseheader.txt
└── project.properties
├── settings.gradle
├── src
├── main
│ ├── java
│ │ └── org
│ │ │ └── kontalk
│ │ │ ├── Kontalk.java
│ │ │ ├── client
│ │ │ ├── AcknowledgedListener.java
│ │ │ ├── AvatarSendReceiver.java
│ │ │ ├── BlockListListener.java
│ │ │ ├── BlockSendReceiver.java
│ │ │ ├── Client.java
│ │ │ ├── EndpointServer.java
│ │ │ ├── FeatureDiscovery.java
│ │ │ ├── HKPClient.java
│ │ │ ├── HTTPFileClient.java
│ │ │ ├── HTTPFileSlotRequester.java
│ │ │ ├── KonConnection.java
│ │ │ ├── KonConnectionListener.java
│ │ │ ├── KonMessageListener.java
│ │ │ ├── KonMessageSender.java
│ │ │ ├── KonRosterListener.java
│ │ │ ├── LastActivityListener.java
│ │ │ ├── PresenceListener.java
│ │ │ ├── PrivateKeyReceiver.java
│ │ │ ├── PublicKeyListener.java
│ │ │ └── VCardListener.java
│ │ │ ├── crypto
│ │ │ ├── Coder.java
│ │ │ ├── Decryptor.java
│ │ │ ├── Encryptor.java
│ │ │ ├── PGPUtils.java
│ │ │ ├── PersonalKey.java
│ │ │ └── X509Bridge.java
│ │ │ ├── misc
│ │ │ ├── Callback.java
│ │ │ ├── JID.java
│ │ │ ├── KonException.java
│ │ │ ├── Searchable.java
│ │ │ └── ViewEvent.java
│ │ │ ├── model
│ │ │ ├── Account.java
│ │ │ ├── Avatar.java
│ │ │ ├── Contact.java
│ │ │ ├── ContactList.java
│ │ │ ├── Model.java
│ │ │ ├── chat
│ │ │ │ ├── Chat.java
│ │ │ │ ├── ChatList.java
│ │ │ │ ├── ChatMessages.java
│ │ │ │ ├── GroupChat.java
│ │ │ │ ├── GroupMetaData.java
│ │ │ │ ├── Member.java
│ │ │ │ ├── ProtoMember.java
│ │ │ │ └── SingleChat.java
│ │ │ └── message
│ │ │ │ ├── CoderStatus.java
│ │ │ │ ├── DecryptMessage.java
│ │ │ │ ├── InMessage.java
│ │ │ │ ├── KonMessage.java
│ │ │ │ ├── MessageContent.java
│ │ │ │ ├── OutMessage.java
│ │ │ │ ├── ProtoMessage.java
│ │ │ │ └── Transmission.java
│ │ │ ├── persistence
│ │ │ ├── Config.java
│ │ │ └── Database.java
│ │ │ ├── system
│ │ │ ├── AccountImporter.java
│ │ │ ├── AttachmentManager.java
│ │ │ ├── AvatarHandler.java
│ │ │ ├── ChatStateManager.java
│ │ │ ├── Control.java
│ │ │ ├── GroupControl.java
│ │ │ └── RosterHandler.java
│ │ │ ├── util
│ │ │ ├── ClientUtils.java
│ │ │ ├── CryptoUtils.java
│ │ │ ├── EncodingUtils.java
│ │ │ ├── MediaUtils.java
│ │ │ ├── MessageUtils.java
│ │ │ ├── OggClip.java
│ │ │ ├── Tr.java
│ │ │ ├── TrustUtils.java
│ │ │ └── XMPPUtils.java
│ │ │ └── view
│ │ │ ├── AvatarLoader.java
│ │ │ ├── ChatDetails.java
│ │ │ ├── ChatListView.java
│ │ │ ├── ChatView.java
│ │ │ ├── ComponentUtils.java
│ │ │ ├── ComposingArea.java
│ │ │ ├── ConfigurationDialog.java
│ │ │ ├── ContactDetails.java
│ │ │ ├── ContactListView.java
│ │ │ ├── Content.java
│ │ │ ├── ImageLoader.java
│ │ │ ├── ImportDialog.java
│ │ │ ├── LinkUtils.java
│ │ │ ├── ListView.java
│ │ │ ├── MainFrame.java
│ │ │ ├── MessageList.java
│ │ │ ├── Notifier.java
│ │ │ ├── ObserverTrait.java
│ │ │ ├── ProfileDialog.java
│ │ │ ├── SearchPanel.java
│ │ │ ├── TrayManager.java
│ │ │ ├── Utils.java
│ │ │ └── View.java
│ └── resources
│ │ ├── i18n
│ │ ├── strings.properties
│ │ ├── strings_ca.properties
│ │ ├── strings_cs.properties
│ │ ├── strings_de.properties
│ │ ├── strings_el.properties
│ │ ├── strings_es.properties
│ │ ├── strings_eu.properties
│ │ ├── strings_fa.properties
│ │ ├── strings_fi.properties
│ │ ├── strings_fr.properties
│ │ ├── strings_in.properties
│ │ ├── strings_it.properties
│ │ ├── strings_ja.properties
│ │ ├── strings_lt.properties
│ │ ├── strings_nb_no.properties
│ │ ├── strings_nl.properties
│ │ ├── strings_ru.properties
│ │ ├── strings_sr.properties
│ │ └── strings_zh.properties
│ │ ├── img
│ │ ├── chat_bg.png
│ │ ├── ic_msg_crypt.png
│ │ ├── ic_msg_crypt_warning.png
│ │ ├── ic_msg_delivered.png
│ │ ├── ic_msg_error.png
│ │ ├── ic_msg_pending.png
│ │ ├── ic_msg_sent.png
│ │ ├── ic_msg_unencrypt.png
│ │ ├── ic_msg_warning.png
│ │ ├── ic_ui_add_contact.png
│ │ ├── ic_ui_add_group.png
│ │ ├── ic_ui_attach.png
│ │ ├── ic_ui_clear.png
│ │ ├── ic_ui_edit.png
│ │ ├── ic_ui_insecure.png
│ │ ├── ic_ui_menu.png
│ │ ├── ic_ui_refresh.png
│ │ ├── ic_ui_reload.png
│ │ ├── ic_ui_search.png
│ │ ├── ic_ui_secure.png
│ │ ├── ic_ui_send.png
│ │ ├── ic_ui_warning.png
│ │ ├── kontalk-big.png
│ │ ├── kontalk.png
│ │ ├── kontalk_notification.png
│ │ └── kontalk_small.png
│ │ ├── notification.ogg
│ │ └── truststore.bks
└── test
│ └── java
│ └── org
│ └── kontalk
│ ├── KontalkTest.java
│ └── util
│ └── CryptoUtilsTest.java
├── update_tr.py
└── win_installer
├── create_installer.nsi
└── kontalk.ico
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 | (Template: Write your error report, question or wish here. Please be as precise as possible.)
3 |
4 | ### Additional information
5 | Kontalk Desktop version:
6 | Operating System:
7 | Desktop Environment: (if using Linux)
8 | Kontalk Android version: (if Android device is involved)
9 |
10 | (For errors please check for exceptions or warnings in the log file and
11 | include them here. More information: https://github.com/kontalk/desktopclient-java/wiki#i-found-a-bug-what-to-do )
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 |
3 | # EDIT:
4 | /nbproject/private/
5 | /kontalk.db
6 | /kontalk.properties
7 | *.pgp
8 | *.asc
9 | *.crt
10 | /win_installer/*.exe
11 |
12 | # Gradle
13 | /.gradle/
14 |
15 | # Gradle Netbeans plugin
16 | /.nb-gradle/
17 |
18 | # IntelliJ Idea
19 | .idea/
20 | *.iml
21 | out/
22 |
23 | # Package Files #
24 | #*.jar
25 | *.war
26 | *.ear
27 |
28 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
29 | hs_err_pid*
30 | build/
31 | dist/
32 | store/
33 | release/
34 |
35 | /.classpath
36 | /.project
37 | /.settings
38 | /bin/
39 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "client-common-java"]
2 | path = client-common-java
3 | url = https://github.com/kontalk/client-common-java.git
4 |
--------------------------------------------------------------------------------
/.nb-gradle-properties:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | GPL
6 | nbproject/licenseheader.txt
7 | Kontalk
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 |
3 | jdk:
4 | - oraclejdk8
5 |
6 | before_install:
7 | - git submodule update --init
8 |
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | kontalk-java-client
2 | ===================
3 |
4 | [](https://travis-ci.org/kontalk/desktopclient-java)
5 |
6 | A platform independent Java client for Kontalk (http://www.kontalk.org). Includes connectivity to the Jabber network!
7 |
8 | The desktop client uses your existing Kontalk account from the [Android client](https://github.com/kontalk/androidclient/blob/master/README.md#kontalk-official-android-client). Instructions for exporting the key [here](https://github.com/kontalk/androidclient/wiki/Export-personal-key-to-another-device).
9 |
10 | **FAQ:** Common questions are answered [here!](https://github.com/kontalk/desktopclient-java/wiki/FAQ)
11 |
12 | User Forum: https://forum.kontalk.org/
13 |
14 | ### Screenshots
15 |
16 | 
17 |
18 | [Contacts screen](/misc/kon_snap2.png?raw=true)
19 |
20 | ## Software Dependencies
21 |
22 | - Java 8 (Java Runtime Engine)
23 |
24 | ## Key Features
25 |
26 | Connect with Kontalk...
27 | - Use the existing Kontalk account from your phone
28 | - Synchronized contact list (=XMPP roster)
29 | - Add new Kontalk users by phone number
30 | - The client automatically requests public keys for safe communication
31 | - Your communication with Kontalk users is encrypted by default
32 |
33 | ... and beyond:
34 | - Exchange text messages with any Jabber/XMPP users!
35 | - Add new Jabber contacts by JID
36 | - Tested with clients like [Pidgin](https://pidgin.im/) or [Conversations](https://github.com/siacs/Conversations)
37 |
38 | **Note: private key and messages are saved unencrypted and can be read by other applications on your computer!**
39 |
40 | ## Implemented XEP features:
41 | - XEP-0184: Message receipts
42 | - XEP-0085: Chat state notifications
43 | - XEP-0191: User blocking
44 | - XEP-0066: File transfer over server
45 | - XEP-0231: Image thumbnails for attachments
46 | - XEP-0084: Avatar images
47 | - XEP-0363: HTTP File Upload
48 | - XEP-0012: Last activity timestamp
49 | - XEP-0245: The infamous and most essential "/me" command
50 |
51 | ## Support us
52 |
53 | * If you are missing a feature or found a bug [report it!](https://github.com/kontalk/desktopclient-java/issues)
54 |
55 | * Help us with [translations](https://translate.kontalk.org) to spread Kontalk around the world!
56 |
57 | * Code contributions / pull requests are welcome!
58 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | apply plugin: 'application'
3 | apply plugin: 'java'
4 |
5 | sourceCompatibility = '1.8'
6 | targetCompatibility = '1.8'
7 |
8 | mainClassName = 'org.kontalk.Kontalk'
9 | ext.clientCommonDir = 'client-common-java'
10 |
11 | applicationDefaultJvmArgs = ['''-Djava.util.logging.SimpleFormatter.format=%1$tH:%1$tM:%1$tS|%4$-6s|%2$s-%5$s%6$s%n''']
12 |
13 | if (!file('./'+clientCommonDir+'/build.gradle').exists()) {
14 | throw new GradleException('Submodule not found. Run "git submodule update --init".')
15 | }
16 |
17 | gradle.projectsEvaluated {
18 | tasks.withType(JavaCompile) {
19 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
20 | }
21 | }
22 |
23 | evaluationDependsOn ':'+clientCommonDir
24 |
25 | ext.bcVersion = '1.60'
26 | ext.smackVersion = project(':'+clientCommonDir).smackVersion
27 |
28 | //configurations.all { transitive = false }
29 |
30 | dependencies {
31 | compile project(':'+clientCommonDir)
32 |
33 | compile group: 'org.bouncycastle', name: 'bcpg-jdk15on', version: "$bcVersion"
34 | compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: "$bcVersion"
35 | compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: "$bcVersion"
36 | compile group: 'commons-cli', name: 'commons-cli', version: "1.3.1"
37 | compile group: 'commons-codec', name: 'commons-codec', version: "1.10"
38 | compile group: 'commons-configuration', name: 'commons-configuration', version: "1.10"
39 | compile group: 'commons-io', name: 'commons-io', version: "2.4"
40 | compile group: 'commons-lang', name: 'commons-lang', version: "2.6"
41 | compile group: 'commons-logging', name: 'commons-logging', version: "1.1.3"
42 | compile group: 'org.apache.httpcomponents', name: 'httpclient', version: "4.5.6"
43 | compile group: 'org.apache.httpcomponents', name: 'httpcore', version: "4.4.10"
44 | compile group: 'org.apache.tika', name: 'tika-core', version: "1.18"
45 | compile group: 'org.jcraft', name: 'jorbis', version: "0.0.17"
46 | //compile group: 'org.jxmpp', name: 'jxmpp-core', version: "0.4.2"
47 | //compile group: 'org.jxmpp', name: 'jxmpp-util-cache', version: "0.4.2"
48 | compile group: 'org.jxmpp', name: 'jxmpp-jid', version: "0.4.2"
49 | compile group: 'net.sf.kxml', name: 'kxml2', version: "2.3.0"
50 | // missing for junit in json-simple's pom
51 | compile group: 'com.googlecode.json-simple', name: 'json-simple', version: "1.1.1", transitive: false
52 | compile group: 'com.googlecode.libphonenumber', name: 'libphonenumber', version: "7.0.8"
53 | compile group: 'org.ocpsoft.prettytime', name: 'prettytime', version: "3.2.7.Final"
54 | compile group: 'org.igniterealtime.smack', name: 'smack-core', version: "$smackVersion"
55 | compile group: 'org.igniterealtime.smack', name: 'smack-extensions', version: "$smackVersion"
56 | compile group: 'org.igniterealtime.smack', name: 'smack-im', version: "$smackVersion"
57 | compile group: 'org.igniterealtime.smack', name: 'smack-java7', version: "$smackVersion"
58 | compile group: 'org.igniterealtime.smack', name: 'smack-tcp', version: "$smackVersion"
59 | compile group: 'org.xerial', name: 'sqlite-jdbc', version: "3.23.1"
60 | compile group: 'de.sciss', name: 'weblaf', version: "1.28"
61 |
62 | testCompile group: 'junit', name: 'junit', version: "4.12"
63 | }
64 |
65 | repositories {
66 | mavenCentral()
67 | }
68 |
69 | run {
70 | if (project.hasProperty('jvmargs')) {
71 | jvmArgs(jvmargs.split(','))
72 | }
73 | }
74 |
75 | task deleteDeps(type: Delete) {
76 | delete fileTree(project.file('dist/lib')) {
77 | include '*.jar'
78 | }
79 | }
80 |
81 | task copyDeps(type: Copy) {
82 | from(configurations.runtime)
83 | into project.file('dist/lib')
84 | dependsOn ':deleteDeps'
85 | }
86 |
87 | jar {
88 | baseName = 'KontalkDesktopApp'
89 | destinationDir project.file('dist')
90 |
91 | manifest {
92 | attributes(
93 | "Main-Class": mainClassName,
94 | "Class-Path": configurations.compile.collect { 'lib/'+it.getName() }.join(' ')
95 | )
96 | }
97 |
98 | dependsOn ':copyDeps'
99 | }
100 |
101 | buildscript {
102 | repositories {
103 | mavenCentral()
104 | }
105 | dependencies {
106 | classpath 'org.owasp:dependency-check-gradle:3.3.1'
107 | }
108 | }
109 |
110 | apply plugin: 'org.owasp.dependencycheck'
111 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kontalk/desktopclient-java/9a948bbf128f3fd30a9d3e26dbfe58b48f36500b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun May 20 17:46:17 CEST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/misc/kon_snap1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kontalk/desktopclient-java/9a948bbf128f3fd30a9d3e26dbfe58b48f36500b/misc/kon_snap1.png
--------------------------------------------------------------------------------
/misc/kon_snap2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kontalk/desktopclient-java/9a948bbf128f3fd30a9d3e26dbfe58b48f36500b/misc/kon_snap2.png
--------------------------------------------------------------------------------
/nbproject/licenseheader.txt:
--------------------------------------------------------------------------------
1 | <#if licenseFirst??>
2 | ${licenseFirst}
3 | #if>
4 | ${licensePrefix} Kontalk Java client
5 | ${licensePrefix} Copyright (C) 2016 Kontalk Devteam
6 | ${licensePrefix}
7 | ${licensePrefix} This program is free software: you can redistribute it and/or modify
8 | ${licensePrefix} it under the terms of the GNU General Public License as published by
9 | ${licensePrefix} the Free Software Foundation, either version 3 of the License, or
10 | ${licensePrefix} (at your option) any later version.
11 | ${licensePrefix}
12 | ${licensePrefix} This program is distributed in the hope that it will be useful,
13 | ${licensePrefix} but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | ${licensePrefix} MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | ${licensePrefix} GNU General Public License for more details.
16 | ${licensePrefix}
17 | ${licensePrefix} You should have received a copy of the GNU General Public License
18 | ${licensePrefix} along with this program. If not, see .
19 | <#if licenseLast??>
20 | ${licenseLast}
21 | #if>
--------------------------------------------------------------------------------
/nbproject/project.properties:
--------------------------------------------------------------------------------
1 | build.generated.sources.dir=${build.dir}/generated-sources
2 | excludes=
3 | includes=**
4 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'kontalk_java'
2 |
3 | include 'client-common-java'
4 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/client/AcknowledgedListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.client;
20 |
21 | import java.util.logging.Logger;
22 |
23 | import org.jivesoftware.smack.StanzaListener;
24 | import org.jivesoftware.smack.packet.Message;
25 | import org.jivesoftware.smack.packet.Stanza;
26 | import org.jivesoftware.smackx.chatstates.packet.ChatStateExtension;
27 | import org.jivesoftware.smackx.receipts.DeliveryReceipt;
28 | import org.kontalk.system.Control;
29 | import org.kontalk.util.ClientUtils.MessageIDs;
30 |
31 | /**
32 | * Listener for acknowledged packets (Stream Management, XEP-0198).
33 | * @author Alexander Bikadorov {@literal }
34 | */
35 | final class AcknowledgedListener implements StanzaListener {
36 | private static final Logger LOGGER = Logger.getLogger(AcknowledgedListener.class.getName());
37 |
38 | private final Control mControl;
39 |
40 | public AcknowledgedListener(Control control) {
41 | mControl = control;
42 | }
43 |
44 | @Override
45 | public void processStanza(Stanza p) {
46 | // NOTE: the packet is not the acknowledgement itself but the packet
47 | // that is acknowledged
48 | if (!(p instanceof Message)) {
49 | // we are only interested in acks for messages
50 | return;
51 | }
52 | Message m = (Message) p;
53 |
54 | LOGGER.config("for message: "+m);
55 |
56 | if (DeliveryReceipt.from(m) != null) {
57 | // this is an ack for a 'received' message (XEP-0184) send by
58 | // KonMessageListener, ignore
59 | return;
60 | }
61 |
62 | if (m.getBody() == null && m.getExtensions().size() == 1 &&
63 | m.getExtension(ChatStateExtension.NAMESPACE) != null) {
64 | // this is an ack for a chat state notification (XEP-0085), ignore
65 | return;
66 | }
67 |
68 | mControl.onMessageSent(MessageIDs.to(m));
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/client/BlockListListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.client;
20 |
21 | import java.util.ArrayList;
22 | import java.util.List;
23 | import java.util.logging.Logger;
24 | import org.jivesoftware.smack.StanzaListener;
25 | import org.jivesoftware.smack.packet.Stanza;
26 | import org.jivesoftware.smack.provider.ProviderManager;
27 | import org.kontalk.misc.JID;
28 | import org.kontalk.system.Control;
29 |
30 | /**
31 | *
32 | * @author Alexander Bikadorov {@literal }
33 | */
34 | final class BlockListListener implements StanzaListener {
35 | private static final Logger LOGGER = Logger.getLogger(BlockListListener.class.getName());
36 |
37 | private final Control mControl;
38 |
39 | static {
40 | ProviderManager.addIQProvider(BlockingCommand.BLOCKLIST,
41 | BlockingCommand.NAMESPACE,
42 | new BlockingCommand.Provider());
43 | }
44 |
45 | BlockListListener(Control control) {
46 | mControl = control;
47 | }
48 |
49 | @Override
50 | public void processStanza(Stanza packet) {
51 | BlockingCommand p = (BlockingCommand) packet;
52 | LOGGER.config("blocking command: "+p);
53 |
54 | List items = p.getItems();
55 | if (items != null) {
56 | List jids = new ArrayList<>(items.size());
57 | for (String s : items)
58 | jids.add(JID.full(s));
59 | mControl.onBlockList(jids.toArray(new JID[0]));
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/client/BlockSendReceiver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.client;
20 |
21 | import java.util.logging.Logger;
22 |
23 | import org.jivesoftware.smack.packet.IQ;
24 | import org.jivesoftware.smack.util.SuccessCallback;
25 | import org.kontalk.misc.JID;
26 | import org.kontalk.system.Control;
27 |
28 | /**
29 | * Send blocking command and listen to response.
30 | * @author Alexander Bikadorov {@literal }
31 | */
32 | final class BlockSendReceiver implements SuccessCallback {
33 | private static final Logger LOGGER = Logger.getLogger(BlockSendReceiver.class.getName());
34 |
35 | private final Control mControl;
36 | private final KonConnection mConn;
37 | private final boolean mBlocking;
38 | private final JID mJID;
39 |
40 | BlockSendReceiver(Control control,
41 | KonConnection conn,
42 | boolean blocking,
43 | JID jid){
44 | mControl = control;
45 | mConn = conn;
46 | mBlocking = blocking;
47 | mJID = jid;
48 | }
49 |
50 | void sendAndListen() {
51 | LOGGER.info("jid: "+mJID+" blocking="+mBlocking);
52 |
53 | String command = mBlocking ? BlockingCommand.BLOCK : BlockingCommand.UNBLOCK;
54 | BlockingCommand blockingCommand = new BlockingCommand(command, mJID.string());
55 |
56 | mConn.sendWithCallback(blockingCommand, this);
57 | }
58 |
59 |
60 | @Override
61 | public void onSuccess(IQ packet) {
62 | LOGGER.info("response: "+packet);
63 |
64 | IQ p = (IQ) packet;
65 |
66 | if (p.getType() != IQ.Type.result) {
67 | LOGGER.warning("ignoring response with IQ type: "+p.getType());
68 | return;
69 | }
70 |
71 | mControl.onContactBlocked(mJID, mBlocking);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/client/EndpointServer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 |
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 |
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 |
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.client;
20 |
21 | /**
22 | * Defines a server address and features.
23 | * @author Daniele Ricci
24 | * @version 1.0
25 | */
26 | public final class EndpointServer {
27 |
28 | private final String mHost;
29 | private final int mPort;
30 | private final String mNetwork;
31 |
32 | public EndpointServer(String host, int port) {
33 | // tigase: use hostname as network
34 | mNetwork = host;
35 | mHost = host;
36 | mPort = port;
37 | }
38 |
39 | // TODO unused
40 | public EndpointServer(String network, String host, int port) {
41 | mNetwork = network;
42 | mHost = host;
43 | mPort = port;
44 | }
45 |
46 | @Override
47 | public String toString() {
48 | return mNetwork + "|" + mHost + ":" + mPort;
49 | }
50 |
51 | public String getHost() {
52 | return mHost;
53 | }
54 |
55 | public int getPort() {
56 | return mPort;
57 | }
58 |
59 | public String getNetwork() {
60 | return mNetwork;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/client/HKPClient.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.client;
20 |
21 | import java.io.IOException;
22 | import java.util.logging.Level;
23 | import java.util.logging.Logger;
24 | import org.apache.commons.io.IOUtils;
25 | import org.apache.http.HttpEntity;
26 | import org.apache.http.HttpStatus;
27 | import org.apache.http.client.methods.CloseableHttpResponse;
28 | import org.apache.http.client.methods.HttpGet;
29 | import org.apache.http.client.methods.HttpRequestBase;
30 | import org.apache.http.impl.client.CloseableHttpClient;
31 | import org.apache.http.impl.client.HttpClients;
32 |
33 | /**
34 | * Client for OpenPGP HTTP Keyserver Protocol.
35 | *
36 | * See https://tools.ietf.org/html/draft-shaw-openpgp-hkp-00
37 | *
38 | * @author Alexander Bikadorov {@literal }
39 | */
40 | public final class HKPClient {
41 | private static final Logger LOGGER = Logger.getLogger(HKPClient.class.getName());
42 |
43 | //private static final int DEFAULT_PORT = 11371;
44 | //private static final int DEFAULT_SSL_PORT = 443;
45 |
46 | private static final int MAX_CONTENT_LENGTH = 9001;
47 |
48 | public String search(String server, String keyID) {
49 | CloseableHttpClient client = HttpClients.createDefault();
50 | HttpRequestBase get = new HttpGet(
51 | "https://"+server+"/pks/lookup?op=get&options=mr&exact=on&search=0x"+keyID);
52 |
53 | // execute request
54 | CloseableHttpResponse response;
55 | try {
56 | response = client.execute(get);
57 | } catch (IOException ex) {
58 | LOGGER.log(Level.WARNING, "can't execute request, server: "+server, ex);
59 | return "";
60 | }
61 | try {
62 | int code = response.getStatusLine().getStatusCode();
63 | if (code != HttpStatus.SC_OK) {
64 | if (code == HttpStatus.SC_NOT_FOUND) {
65 | LOGGER.config("key not found, server: "+server+"; keyID="+keyID);
66 | } else {
67 | LOGGER.warning("unexpected response, server: "+server+"; code=" + code);
68 | }
69 | return "";
70 | }
71 |
72 | HttpEntity entity = response.getEntity();
73 | if (entity == null) {
74 | LOGGER.warning("no download response entity");
75 | return "";
76 | }
77 |
78 | if (entity.getContentLength() > MAX_CONTENT_LENGTH) {
79 | LOGGER.warning("content too big");
80 | return "";
81 | }
82 |
83 | String contentStr;
84 | try {
85 | contentStr = IOUtils.toString(entity.getContent(), "UTF-8");
86 | } catch (IOException | IllegalStateException ex) {
87 | LOGGER.log(Level.WARNING, " can't read content", ex);
88 | return "";
89 | }
90 |
91 | // for (Header h: response.getAllHeaders()) {
92 | // System.out.println("header: "+h);
93 | // }
94 |
95 | return contentStr;
96 | } finally {
97 | try {
98 | response.close();
99 | } catch (IOException ex) {
100 | LOGGER.log(Level.WARNING, "can't close response", ex);
101 | }
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/client/HTTPFileSlotRequester.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.client;
20 |
21 | import java.util.logging.Logger;
22 |
23 | import org.jivesoftware.smack.provider.ProviderManager;
24 | import org.kontalk.misc.Callback;
25 | import org.kontalk.misc.JID;
26 | import org.kontalk.system.AttachmentManager;
27 | import org.kontalk.util.EncodingUtils;
28 |
29 | /**
30 | *
31 | * @author Alexander Bikadorov {@literal }
32 | */
33 | final class HTTPFileSlotRequester {
34 | private static final Logger LOGGER = Logger.getLogger(HTTPFileSlotRequester.class.getName());
35 |
36 | static {
37 | ProviderManager.addIQProvider(
38 | HTTPFileUpload.Slot.ELEMENT_NAME,
39 | HTTPFileUpload.NAMESPACE,
40 | new HTTPFileUpload.Slot.Provider());
41 | }
42 |
43 | private final KonConnection mConn;
44 | private final JID mService;
45 |
46 | public HTTPFileSlotRequester(KonConnection conn, JID service) {
47 | mConn = conn;
48 | mService = service;
49 | }
50 |
51 | private HTTPFileUpload.Slot mSlotPacket;
52 | synchronized AttachmentManager.Slot getSlot(String filename, long size, String mime) {
53 | HTTPFileUpload.Request request = new HTTPFileUpload.Request(filename, size, mime);
54 | request.setTo(mService.toBareSmack());
55 |
56 | final Callback.Synchronizer syncer = new Callback.Synchronizer();
57 | mSlotPacket = null;
58 | mConn.sendWithCallback(request, packet -> {
59 | LOGGER.config("response: " + packet);
60 |
61 | if (!(packet instanceof HTTPFileUpload.Slot)) {
62 | LOGGER.warning("response not a slot packet: " + packet);
63 | syncer.sync();
64 | return;
65 | }
66 | mSlotPacket = (HTTPFileUpload.Slot) packet;
67 | syncer.sync();
68 | });
69 |
70 | syncer.waitForSync();
71 |
72 | return mSlotPacket != null ?
73 | new AttachmentManager.Slot(
74 | EncodingUtils.toURI(mSlotPacket.getPutUrl()),
75 | EncodingUtils.toURI(mSlotPacket.getGetUrl())) :
76 | new AttachmentManager.Slot();
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/client/KonConnectionListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.client;
20 |
21 | import java.util.logging.Level;
22 | import java.util.logging.Logger;
23 | import org.jivesoftware.smack.ConnectionListener;
24 | import org.jivesoftware.smack.XMPPConnection;
25 | import org.kontalk.misc.JID;
26 | import org.kontalk.misc.KonException;
27 | import org.kontalk.system.Control;
28 |
29 | /**
30 | *
31 | * @author Alexander Bikadorov {@literal }
32 | */
33 | final class KonConnectionListener implements ConnectionListener {
34 | private static final Logger LOGGER = Logger.getLogger(KonConnectionListener.class.getName());
35 |
36 | private final Client mClient;
37 | private final Control mControl;
38 |
39 | private boolean mConnected = false;
40 |
41 | KonConnectionListener(Client client, Control control) {
42 | mClient = client;
43 | mControl = control;
44 | }
45 |
46 | @Override
47 | public void connected(XMPPConnection connection) {
48 | LOGGER.info("to "+connection.getHost());
49 | mConnected = true;
50 | }
51 |
52 | @Override
53 | public void authenticated(XMPPConnection connection, boolean resumed) {
54 | JID jid = JID.fromSmack(connection.getUser());
55 | LOGGER.info("as "+jid);
56 | mControl.onAuthenticated(jid);
57 | }
58 |
59 | @Override
60 | public void connectionClosed() {
61 | mConnected = false;
62 | LOGGER.info("connection closed");
63 | mClient.newStatus(Control.Status.DISCONNECTED);
64 | }
65 |
66 | @Override
67 | public void connectionClosedOnError(Exception ex) {
68 | // ignore if error occurs on connection attempt (handled by client)
69 | boolean connected = mConnected;
70 | mConnected = false;
71 |
72 | if (!connected)
73 | return;
74 |
75 | LOGGER.log(Level.WARNING, "connection closed on error", ex);
76 | mClient.newStatus(Control.Status.ERROR);
77 | mClient.newException(new KonException(KonException.Error.CLIENT_ERROR, ex));
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/client/KonRosterListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.client;
20 |
21 | import java.util.Collection;
22 | import java.util.Set;
23 | import java.util.logging.Level;
24 | import java.util.logging.Logger;
25 | import java.util.stream.Collectors;
26 |
27 | import org.apache.commons.lang.StringUtils;
28 | import org.jivesoftware.smack.packet.Presence;
29 | import org.jivesoftware.smack.roster.Roster;
30 | import org.jivesoftware.smack.roster.RosterEntry;
31 | import org.jivesoftware.smack.roster.RosterListener;
32 | import org.jivesoftware.smack.roster.RosterLoadedListener;
33 | import org.jxmpp.jid.Jid;
34 | import org.kontalk.misc.JID;
35 | import org.kontalk.system.RosterHandler;
36 | import org.kontalk.util.ClientUtils;
37 |
38 | /**
39 | * Listener for events in the roster (a server-side contact list in XMPP).
40 | * @author Alexander Bikadorov {@literal }
41 | */
42 | final class KonRosterListener implements RosterLoadedListener, RosterListener {
43 | private static final Logger LOGGER = Logger.getLogger(KonRosterListener.class.getName());
44 |
45 | private final Roster mRoster;
46 | private final RosterHandler mHandler;
47 | private boolean mLoaded = false;
48 |
49 | KonRosterListener(Roster roster, RosterHandler handler) {
50 | mRoster = roster;
51 | mHandler = handler;
52 | }
53 |
54 | @Override
55 | public void onRosterLoaded(Roster roster) {
56 | Set entries = roster.getEntries();
57 | LOGGER.info("loading "+entries.size()+" entries");
58 |
59 | mHandler.onLoaded(entries.stream()
60 | .map(KonRosterListener::clientToModel)
61 | .collect(Collectors.toList()));
62 | mLoaded = true;
63 | }
64 |
65 | @Override
66 | public void onRosterLoadingFailed(Exception exception) {
67 | LOGGER.log(Level.WARNING, "roster loading failed", exception);
68 | }
69 |
70 | /**
71 | * NOTE: on every (re-)connect all entries are added again (loaded),
72 | * one method call for all contacts.
73 | */
74 | @Override
75 | public void entriesAdded(Collection addresses) {
76 | if (mRoster == null || !mLoaded)
77 | return;
78 |
79 | for (Jid jid: addresses) {
80 | RosterEntry entry = mRoster.getEntry(jid.asBareJid());
81 | if (entry == null) {
82 | LOGGER.warning("jid not in roster: "+jid);
83 | return;
84 | }
85 |
86 | LOGGER.config("entry: "+entry.toString());
87 | mHandler.onEntryAdded(clientToModel(entry));
88 | }
89 | }
90 |
91 | @Override
92 | public void entriesUpdated(Collection addresses) {
93 | // note: we don't know what exactly changed here
94 | for (Jid jid: addresses) {
95 | RosterEntry entry = mRoster.getEntry(jid.asBareJid());
96 | if (entry == null) {
97 | LOGGER.warning("jid not in roster: "+jid);
98 | return;
99 | }
100 |
101 | LOGGER.info("entry: "+entry.toString());
102 | mHandler.onEntryUpdate(clientToModel(entry));
103 | }
104 | }
105 |
106 | @Override
107 | public void entriesDeleted(Collection addresses) {
108 | for (Jid jid: addresses) {
109 | LOGGER.info("address: "+jid);
110 |
111 | mHandler.onEntryDeleted(JID.fromSmack(jid));
112 | }
113 | }
114 |
115 | @Override
116 | public void presenceChanged(Presence presence) {
117 | // handled by PresenceListener
118 | }
119 |
120 | private static ClientUtils.KonRosterEntry clientToModel(RosterEntry entry) {
121 | return new ClientUtils.KonRosterEntry(JID.fromSmack(entry.getJid()),
122 | StringUtils.defaultString(entry.getName()),
123 | entry.getType(),
124 | entry.isSubscriptionPending());
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/client/LastActivityListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.client;
20 |
21 | import java.util.logging.Logger;
22 |
23 | import org.apache.commons.lang.StringUtils;
24 | import org.jivesoftware.smack.SmackException;
25 | import org.jivesoftware.smack.StanzaListener;
26 | import org.jivesoftware.smack.packet.Stanza;
27 | import org.jivesoftware.smackx.iqlast.packet.LastActivity;
28 | import org.kontalk.misc.JID;
29 | import org.kontalk.system.Control;
30 |
31 | /**
32 | * Listener for Last Activity Response packets (XEP-0012).
33 | * @author Alexander Bikadorov {@literal }
34 | */
35 | class LastActivityListener implements StanzaListener {
36 | private static final Logger LOGGER = Logger.getLogger(LastActivityListener.class.getName());
37 |
38 | private final Control mControl;
39 |
40 | public LastActivityListener(Control control) {
41 | mControl = control;
42 | }
43 |
44 | @Override
45 | public void processStanza(Stanza packet) throws SmackException.NotConnectedException {
46 | LOGGER.config("last activity: " + packet);
47 |
48 | LastActivity lastActivity = (LastActivity) packet;
49 |
50 | long lastActivityTime = lastActivity.getIdleTime();
51 | if (lastActivityTime < 0) {
52 | // error message or parsing error, not important here (logged by Client class)
53 | return;
54 | }
55 |
56 | mControl.onLastActivity(JID.fromSmack(lastActivity.getFrom()),
57 | lastActivity.getIdleTime(),
58 | StringUtils.defaultString(lastActivity.getStatusMessage()));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/client/PresenceListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.client;
20 |
21 | import java.util.Optional;
22 | import java.util.logging.Logger;
23 |
24 | import org.apache.commons.lang.StringUtils;
25 | import org.jivesoftware.smack.StanzaListener;
26 | import org.jivesoftware.smack.packet.ExtensionElement;
27 | import org.jivesoftware.smack.packet.Presence;
28 | import org.jivesoftware.smack.packet.Stanza;
29 | import org.jivesoftware.smack.packet.StanzaError;
30 | import org.jivesoftware.smack.provider.ProviderManager;
31 | import org.jivesoftware.smack.roster.Roster;
32 | import org.jivesoftware.smackx.muc.packet.MUCUser;
33 | import org.kontalk.misc.JID;
34 | import org.kontalk.system.RosterHandler;
35 |
36 | /**
37 | * Listen for presence packets.
38 | *
39 | * The presence stanza also may include a public key fingerprint
40 | * extension (custom Kontalk extension, based on XEP-0189) and/or a signature
41 | * extension for signing the status element (XEP-0027).
42 | *
43 | * @author Alexander Bikadorov {@literal }
44 | */
45 | class PresenceListener implements StanzaListener {
46 | private static final Logger LOGGER = Logger.getLogger(PresenceListener.class.getName());
47 |
48 | private final Roster mRoster;
49 | private final RosterHandler mHandler;
50 |
51 | public PresenceListener(Roster roster, RosterHandler handler) {
52 | mRoster = roster;
53 | mHandler = handler;
54 |
55 | ProviderManager.addExtensionProvider(
56 | PublicKeyPresence.ELEMENT_NAME,
57 | PublicKeyPresence.NAMESPACE,
58 | new PublicKeyPresence.Provider());
59 |
60 | ProviderManager.addExtensionProvider(
61 | PresenceSignature.ELEMENT_NAME,
62 | PresenceSignature.NAMESPACE,
63 | new PresenceSignature.Provider());
64 | }
65 |
66 | @Override
67 | public void processStanza(Stanza packet) {
68 | if (MUCUser.from(packet) != null) {
69 | // handled by MUC manager
70 | LOGGER.config("ignoring MUC presence, from: "+packet.getFrom());
71 | return;
72 | }
73 |
74 | LOGGER.config("packet: "+packet);
75 |
76 | Presence presence = (Presence) packet;
77 |
78 | JID jid = JID.fromSmack(presence.getFrom());
79 |
80 | ExtensionElement publicKeyExt = presence.getExtension(
81 | PublicKeyPresence.ELEMENT_NAME,
82 | PublicKeyPresence.NAMESPACE);
83 | PublicKeyPresence pubKey = publicKeyExt instanceof PublicKeyPresence ?
84 | (PublicKeyPresence) publicKeyExt :
85 | null;
86 |
87 | switch(presence.getType()) {
88 | case error:
89 | StanzaError error = presence.getError();
90 | if (error == null) {
91 | LOGGER.warning("error presence does not contain error");
92 | return;
93 | }
94 | mHandler.onPresenceError(jid, error.getType(),
95 | error.getCondition());
96 | return;
97 | // NOTE: only handled here if Roster.SubscriptionMode is set to 'manual'
98 | case subscribe:
99 | byte[] key = pubKey != null ? pubKey.getKey() : null;
100 | if (key == null)
101 | key = new byte[0];
102 | mHandler.onSubscriptionRequest(jid, key);
103 | return;
104 | case unsubscribe:
105 | // nothing to do(?)
106 | LOGGER.info(("ignoring unsubscribe, JID: "+jid));
107 | return;
108 | }
109 |
110 | // NOTE: a delay extension is sometimes included, don't know why;
111 | // ignoring mode, always null anyway
112 |
113 | // NOTE: using only the "best" presence to ignore unimportant updates
114 | // from multiple clients
115 | Presence bestPresence = mRoster.getPresence(jid.toBareSmack());
116 |
117 | mHandler.onPresenceUpdate(jid,
118 | bestPresence.getType(),
119 | Optional.ofNullable(bestPresence.getStatus()));
120 |
121 | if (pubKey != null) {
122 | String fp = StringUtils.defaultString(pubKey.getFingerprint()).toLowerCase();
123 | if (fp.isEmpty()) {
124 | LOGGER.warning("no fingerprint in public key presence extension");
125 | } else {
126 | mHandler.onFingerprintPresence(jid, fp);
127 | }
128 | }
129 |
130 | ExtensionElement signatureExt = bestPresence.getExtension(
131 | PresenceSignature.ELEMENT_NAME,
132 | PresenceSignature.NAMESPACE);
133 | if (signatureExt instanceof PresenceSignature) {
134 | PresenceSignature signing = (PresenceSignature) signatureExt;
135 | String signature = StringUtils.defaultString(signing.getSignature());
136 | if (!signature.isEmpty()) {
137 | mHandler.onSignaturePresence(jid, signature);
138 | } else {
139 | LOGGER.warning("no signature in signed presence extension");
140 | }
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/client/PrivateKeyReceiver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.client;
20 |
21 | import java.io.IOException;
22 | import java.util.List;
23 | import java.util.logging.Level;
24 | import java.util.logging.Logger;
25 |
26 | import org.jivesoftware.smack.SmackException;
27 | import org.jivesoftware.smack.XMPPException;
28 | import org.jivesoftware.smack.packet.IQ;
29 | import org.jivesoftware.smack.util.SuccessCallback;
30 | import org.jivesoftware.smackx.iqregister.packet.Registration;
31 | import org.jivesoftware.smackx.xdata.Form;
32 | import org.jivesoftware.smackx.xdata.FormField;
33 | import org.jivesoftware.smackx.xdata.packet.DataForm;
34 | import org.kontalk.misc.Callback;
35 |
36 | /**
37 | * Send request and listen to response for private data over
38 | * 'jabber:iq:register' namespace.
39 | *
40 | * Temporary server connection is established when requesting key.
41 | *
42 | * TODO not used
43 | *
44 | * @author Alexander Bikadorov {@literal }
45 | */
46 | public final class PrivateKeyReceiver implements SuccessCallback {
47 | private static final Logger LOGGER = Logger.getLogger(PrivateKeyReceiver.class.getName());
48 |
49 | private static final String FORM_TYPE_VALUE = "http://kontalk.org/protocol/register#privatekey";
50 | private static final String FORM_TOKEN_VAR = "token";
51 |
52 | private final Callback.Handler mHandler;
53 | private KonConnection mConn = null;
54 |
55 | public PrivateKeyReceiver(Callback.Handler handler) {
56 | mHandler = handler;
57 | }
58 |
59 | public void sendRequest(EndpointServer server, boolean validateCertificate,
60 | final String registrationToken) {
61 | // create connection
62 | mConn = new KonConnection(server, validateCertificate);
63 |
64 | Thread thread = new Thread("Private Key Request") {
65 | @Override
66 | public void run() {
67 | PrivateKeyReceiver.this.sendRequestAsync(registrationToken);
68 | }
69 | };
70 | thread.setDaemon(true);
71 | thread.start();
72 | }
73 |
74 | private void sendRequestAsync(String registrationToken) {
75 | // connect
76 | try {
77 | mConn.connect();
78 | } catch (XMPPException | SmackException | IOException | InterruptedException ex) {
79 | LOGGER.log(Level.WARNING, "can't connect to "+mConn.getServer(), ex);
80 | mHandler.handle(new Callback<>(ex));
81 | return;
82 | }
83 |
84 | Registration iq = new Registration();
85 | iq.setType(IQ.Type.set);
86 | iq.setTo(mConn.getXMPPServiceDomain());
87 | Form form = new Form(DataForm.Type.submit);
88 |
89 | // form type field
90 | FormField type = new FormField(FormField.FORM_TYPE);
91 | type.setType(FormField.Type.hidden);
92 | type.addValue(FORM_TYPE_VALUE);
93 | form.addField(type);
94 |
95 | // token field
96 | FormField fieldKey = new FormField(FORM_TOKEN_VAR);
97 | fieldKey.setLabel("Registration token");
98 | fieldKey.setType(FormField.Type.text_single);
99 | fieldKey.addValue(registrationToken);
100 | form.addField(fieldKey);
101 |
102 | iq.addExtension(form.getDataFormToSend());
103 |
104 | mConn.sendIqRequestAsync(iq)
105 | .onSuccess(this)
106 | .onError(exception -> mHandler.handle(new Callback<>(exception)));
107 | }
108 |
109 | @Override
110 | public void onSuccess(IQ packet) {
111 | LOGGER.info("response: "+packet);
112 |
113 | mConn.disconnect();
114 |
115 | if (!(packet instanceof IQ)) {
116 | LOGGER.warning("response not an IQ packet");
117 | finish(null);
118 | return;
119 | }
120 | IQ iq = (IQ) packet;
121 |
122 | if (iq.getType() != IQ.Type.result) {
123 | LOGGER.warning("ignoring response with IQ type: "+iq.getType());
124 | this.finish(null);
125 | return;
126 | }
127 |
128 | DataForm response = iq.getExtension(DataForm.ELEMENT, DataForm.NAMESPACE);
129 | if (response == null) {
130 | this.finish(null);
131 | return;
132 | }
133 |
134 | String token = null;
135 | List fields = response.getFields();
136 | for (FormField field : fields) {
137 | if ("token".equals(field.getVariable())) {
138 | token = field.getValues().get(0).toString();
139 | break;
140 | }
141 | }
142 |
143 | this.finish(token);
144 | }
145 |
146 | private void finish(String token) {
147 | mHandler.handle(token == null ?
148 | new Callback<>() :
149 | new Callback<>(token));
150 | }
151 |
152 | }
153 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/client/PublicKeyListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.client;
20 |
21 | import java.util.logging.Logger;
22 | import org.jivesoftware.smack.StanzaListener;
23 | import org.apache.commons.lang.StringUtils;
24 | import org.jivesoftware.smack.packet.IQ;
25 | import org.jivesoftware.smack.packet.Stanza;
26 | import org.jivesoftware.smack.provider.ProviderManager;
27 | import org.kontalk.misc.JID;
28 | import org.kontalk.system.Control;
29 |
30 | /**
31 | *
32 | * @author Alexander Bikadorov {@literal }
33 | */
34 | class PublicKeyListener implements StanzaListener {
35 | private static final Logger LOGGER = Logger.getLogger(PublicKeyListener.class.getName());
36 |
37 | private final Control mControl;
38 |
39 | static {
40 | ProviderManager.addIQProvider(PublicKeyPublish.ELEMENT_NAME,
41 | PublicKeyPublish.NAMESPACE,
42 | new PublicKeyPublish.Provider());
43 | }
44 |
45 | public PublicKeyListener(Control control) {
46 | mControl = control;
47 | }
48 |
49 | @Override
50 | public void processStanza(Stanza packet) {
51 | LOGGER.info("got public key: "+StringUtils.abbreviate(packet.toString(), 300));
52 |
53 | PublicKeyPublish publicKeyPacket = (PublicKeyPublish) packet;
54 |
55 | if (publicKeyPacket.getType() == IQ.Type.set) {
56 | LOGGER.warning("ignoring public key packet with type 'set'");
57 | return;
58 | }
59 |
60 | if (publicKeyPacket.getType() == IQ.Type.result) {
61 | byte[] keyData = publicKeyPacket.getPublicKey();
62 | if (keyData == null) {
63 | LOGGER.warning("got public key packet without public key");
64 | return;
65 | }
66 | mControl.onPGPKey(JID.fromSmack(publicKeyPacket.getFrom()), keyData);
67 | }
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/client/VCardListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 |
7 | package org.kontalk.client;
8 |
9 | import java.util.logging.Logger;
10 | import org.jivesoftware.smack.StanzaListener;
11 | import org.jivesoftware.smack.packet.IQ;
12 | import org.jivesoftware.smack.packet.Stanza;
13 | import org.jivesoftware.smack.provider.ProviderManager;
14 | import org.kontalk.misc.JID;
15 | import org.kontalk.system.Control;
16 |
17 | /**
18 | * Listener for vCard4 iq stanzas, deprecated!
19 | *
20 | * @author Alexander Bikadorov {@literal }
21 | */
22 | final class VCardListener implements StanzaListener {
23 | private static final Logger LOGGER = Logger.getLogger(VCardListener.class.getName());
24 |
25 | private final Control mControl;
26 |
27 | static {
28 | ProviderManager.addIQProvider(
29 | VCard4.ELEMENT_NAME,
30 | VCard4.NAMESPACE,
31 | new VCard4.Provider());
32 | }
33 |
34 | VCardListener(Control control) {
35 | mControl = control;
36 | }
37 |
38 | @Override
39 | public void processStanza(Stanza packet) {
40 | VCard4 p = (VCard4) packet;
41 | LOGGER.info("vcard: "+p);
42 |
43 | byte[] publicKey = p.getPGPKey();
44 |
45 | // vcard coming from sync
46 | if (p.getType() == IQ.Type.set) {
47 | LOGGER.warning("ignoring vcard with type 'set'");
48 | return;
49 | }
50 |
51 | if (p.getType() == IQ.Type.result) {
52 | if (publicKey == null) {
53 | LOGGER.warning("got vcard without pgp key included");
54 | return;
55 | }
56 | mControl.onPGPKey(JID.fromSmack(p.getFrom()), publicKey);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/crypto/Coder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.crypto;
20 |
21 | import java.io.File;
22 | import java.util.HashMap;
23 | import java.util.Optional;
24 | import java.util.logging.Logger;
25 |
26 | import org.kontalk.crypto.PGPUtils.PGPCoderKey;
27 | import org.kontalk.model.Contact;
28 | import org.kontalk.model.message.DecryptMessage;
29 | import org.kontalk.model.message.MessageContent.InAttachment;
30 | import org.kontalk.model.message.OutMessage;
31 |
32 | /**
33 | * Static methods for decryption and encryption of a message.
34 | *
35 | * @author Alexander Bikadorov {@literal }
36 | */
37 | public final class Coder {
38 | private static final Logger LOGGER = Logger.getLogger(Coder.class.getName());
39 |
40 | private Coder() {
41 | }
42 |
43 | /**
44 | * Encryption status of a message.
45 | * Do not modify, only add! Ordinal used in database.
46 | */
47 | public enum Encryption {NOT, ENCRYPTED, DECRYPTED}
48 |
49 | /**
50 | * Signing status of a message.
51 | * Do not modify, only add! Ordinal used in database.
52 | */
53 | public enum Signing {NOT, SIGNED, VERIFIED, UNKNOWN}
54 |
55 | /**
56 | * Errors that can occur during de-/encryption and verification.
57 | * Do not modify, only add! Ordinal used in database.
58 | */
59 | public enum Error {
60 | /** Some unknown error. */
61 | UNKNOWN_ERROR,
62 | /** Own personal key not found. Unused. */
63 | MY_KEY_UNAVAILABLE,
64 | /** Public key of sender not found. */
65 | KEY_UNAVAILABLE,
66 | /** My private key does not match. */
67 | INVALID_PRIVATE_KEY,
68 | /** Invalid data / parsing failed. */
69 | INVALID_DATA,
70 | /** No integrity protection found. */
71 | NO_INTEGRITY,
72 | /** Integrity check failed. */
73 | INVALID_INTEGRITY,
74 | /** Invalid data / parsing failed of signature. */
75 | INVALID_SIGNATURE_DATA,
76 | /** Signature does not match sender. */
77 | INVALID_SIGNATURE,
78 | /** Recipient user id in decrypted data does not match id in my key. */
79 | INVALID_RECIPIENT,
80 | /** Sender user id in decrypted data does not match id in sender key. */
81 | INVALID_SENDER,
82 |
83 | //INVALID_MIME,
84 | //INVALID_TIMESTAMP,
85 | }
86 |
87 | private static final HashMap KEY_MAP = new HashMap<>();
88 |
89 | public static Optional contactkey(Contact contact) {
90 | if (KEY_MAP.containsKey(contact)) {
91 | PGPCoderKey key = KEY_MAP.get(contact);
92 | if (key.fingerprint.equals(contact.getFingerprint()))
93 | return Optional.of(key);
94 | }
95 |
96 | byte[] rawKey = contact.getKey();
97 | if (rawKey.length != 0) {
98 | PGPCoderKey key = PGPUtils.readPublicKey(rawKey).orElse(null);
99 | if (key != null) {
100 | KEY_MAP.put(contact, key);
101 | return Optional.of(key);
102 | }
103 | }
104 |
105 | LOGGER.warning("key not found for contact: "+contact);
106 | return Optional.empty();
107 | }
108 |
109 | /**
110 | * Decrypt and verify the body of a message. Sets the encryption and signing
111 | * status of the message and errors that may occur are saved to the message.
112 | */
113 | public static boolean decryptMessage(PersonalKey myKey, DecryptMessage message) {
114 | return Decryptor.decryptMessage(message, myKey);
115 | }
116 |
117 | /**
118 | * Decrypt and verify a downloaded attachment file. Sets the encryption and
119 | * signing status of the message attachment and errors that may occur are
120 | * saved to the attachment.
121 | */
122 | public static void decryptAttachment(PersonalKey myKey, InAttachment attachment, Contact sender) {
123 | Decryptor.decryptAttachment(attachment, myKey, sender);
124 | }
125 |
126 | /**
127 | * Creates encrypted and signed message body.
128 | * Errors that may occur are saved to the message.
129 | * @return the encrypted and signed text.
130 | */
131 | public static String encryptMessageRFC3923(PersonalKey myKey, OutMessage message) {
132 | return new Encryptor(myKey, message).encryptMessageRFC3923();
133 | }
134 |
135 | public static String encryptStanzaRFC3923(PersonalKey myKey, OutMessage message, String xml) {
136 | return new Encryptor(myKey, message).encryptStanzaRFC3923(xml);
137 | }
138 |
139 | /** Encrypt an arbitrary UTF-8 string as a Base64 encoded OpenPGP Message. */
140 | public static String encryptString(PersonalKey myKey, OutMessage message, String plainText) {
141 | return new Encryptor(myKey, message).encryptString(plainText);
142 | }
143 |
144 | public static Optional encryptAttachment(PersonalKey myKey, OutMessage message, File file) {
145 | return new Encryptor(myKey, message).encryptAttachment(file);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/misc/Callback.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.misc;
20 |
21 | import java.util.Optional;
22 | import java.util.concurrent.CountDownLatch;
23 | import java.util.concurrent.TimeUnit;
24 | import java.util.logging.Level;
25 | import java.util.logging.Logger;
26 |
27 | /**
28 | *
29 | * @author Alexander Bikadorov {@literal }
30 | */
31 | public final class Callback {
32 | private static final Logger LOGGER = Logger.getLogger(Callback.class.getName());
33 |
34 | public final V value;
35 | public final Optional exception;
36 |
37 | public Callback() {
38 | this.value = null;
39 | this.exception = Optional.empty();
40 | }
41 |
42 | public Callback(V response) {
43 | this.value = response;
44 | this.exception = Optional.empty();
45 | }
46 |
47 | public Callback(Exception ex) {
48 | this.value = null;
49 | this.exception = Optional.of(ex);
50 | }
51 |
52 | public interface Handler {
53 | void handle(Callback callback);
54 | }
55 |
56 | public static class Synchronizer {
57 | private final CountDownLatch mLatch = new CountDownLatch(1);
58 |
59 | public void sync() {
60 | mLatch.countDown();
61 | }
62 |
63 | public void waitForSync() {
64 | boolean succ = false;
65 | try {
66 | succ = mLatch.await(5, TimeUnit.SECONDS);
67 | } catch (InterruptedException ex) {
68 | LOGGER.log(Level.WARNING, "interrupted", ex);
69 | }
70 | if (!succ) {
71 | LOGGER.warning("await failed, timeout reached");
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/misc/JID.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.misc;
20 |
21 | import java.util.Objects;
22 | import java.util.logging.Level;
23 | import java.util.logging.Logger;
24 |
25 | import org.apache.commons.lang.StringUtils;
26 | import org.jxmpp.jid.BareJid;
27 | import org.jxmpp.jid.Jid;
28 | import org.jxmpp.jid.impl.JidCreate;
29 | import org.jxmpp.jid.parts.Localpart;
30 | import org.jxmpp.jid.util.JidUtil;
31 | import org.jxmpp.stringprep.XmppStringprepException;
32 | import org.jxmpp.stringprep.simple.SimpleXmppStringprep;
33 | import org.jxmpp.util.XmppStringUtils;
34 |
35 | /**
36 | * A Jabber ID (the address of an XMPP entity). Immutable.
37 | *
38 | * Escaping of local part (XEP-0106) supported.
39 | *
40 | * @author Alexander Bikadorov {@literal }
41 | */
42 | public final class JID {
43 | private static final Logger LOGGER = Logger.getLogger(JID.class.getName());
44 |
45 | static {
46 | // good to know. For working JID validation
47 | SimpleXmppStringprep.setup();
48 | }
49 |
50 | private final String mLocal; // escaped!
51 | private final String mDomain;
52 | private final String mResource;
53 | private final boolean mValid;
54 |
55 | private JID(String local, String domain, String resource) {
56 | mLocal = local;
57 | mDomain = domain;
58 | mResource = resource;
59 |
60 | mValid = !mLocal.isEmpty() && !mDomain.isEmpty()
61 | // NOTE: domain check could be stronger - compliant with RFC 6122, but
62 | // server does not accept most special characters
63 | // NOTE: resource not checked
64 | && JidUtil.isTypicalValidEntityBareJid(
65 | XmppStringUtils.completeJidFrom(mLocal, mDomain));
66 | }
67 |
68 | /** Return unescaped local part. */
69 | public String local() {
70 | return XmppStringUtils.unescapeLocalpart(mLocal);
71 | }
72 |
73 | public String domain() {
74 | return mDomain;
75 | }
76 |
77 | /** Return JID as escaped string. */
78 | public String string() {
79 | return XmppStringUtils.completeJidFrom(mLocal, mDomain, mResource);
80 | }
81 |
82 | public String asUnescapedString() {
83 | return XmppStringUtils.completeJidFrom(this.local(), mDomain, mResource);
84 | }
85 |
86 | public boolean isValid() {
87 | return mValid;
88 | }
89 |
90 | public boolean isHash() {
91 | return mLocal.matches("[0-9a-f]{40}");
92 | }
93 |
94 | public boolean isFull() {
95 | return !mResource.isEmpty();
96 | }
97 |
98 | public JID toBare() {
99 | return new JID(mLocal, mDomain, "");
100 | }
101 |
102 | /** To invalid(!) domain JID. */
103 | public JID toDomain() {
104 | return new JID("", mDomain, "");
105 | }
106 |
107 | public BareJid toBareSmack() {
108 | try {
109 | return JidCreate.bareFrom(this.string());
110 | } catch (XmppStringprepException ex) {
111 | LOGGER.log(Level.WARNING, "could not convert to smack", ex);
112 | throw new RuntimeException("You didn't check isValid(), idiot!");
113 | }
114 | }
115 |
116 | public Jid toSmack() {
117 | try {
118 | return JidCreate.from(this.string());
119 | } catch (XmppStringprepException ex) {
120 | LOGGER.log(Level.WARNING, "could not convert to smack", ex);
121 | throw new RuntimeException("Not even a simple Jid?");
122 | }
123 | }
124 |
125 | /**
126 | * Comparing only bare JIDs.
127 | * Case-insensitive.
128 | */
129 | @Override
130 | public final boolean equals(Object o) {
131 | if (o == this)
132 | return true;
133 |
134 | if (!(o instanceof JID))
135 | return false;
136 |
137 | JID oJID = (JID) o;
138 | return mLocal.equalsIgnoreCase(oJID.mLocal) &&
139 | mDomain.equalsIgnoreCase(oJID.mDomain);
140 | }
141 |
142 | @Override
143 | public int hashCode() {
144 | return Objects.hash(mLocal, mDomain);
145 | }
146 |
147 | /** Use this only for debugging and otherwise string() instead! */
148 | @Override
149 | public String toString() {
150 | return "'"+this.string()+"'";
151 | }
152 |
153 | public static JID full(String jid) {
154 | jid = StringUtils.defaultString(jid);
155 | return escape(XmppStringUtils.parseLocalpart(jid),
156 | XmppStringUtils.parseDomain(jid),
157 | XmppStringUtils.parseResource(jid));
158 | }
159 |
160 | public static JID bare(String jid) {
161 | jid = StringUtils.defaultString(jid);
162 | return escape(XmppStringUtils.parseLocalpart(jid), XmppStringUtils.parseDomain(jid), "");
163 | }
164 |
165 | public static JID fromSmack(Jid jid) {
166 | Localpart localpart = jid.getLocalpartOrNull();
167 | return new JID(localpart != null ? localpart.toString() : "",
168 | jid.getDomain().toString(),
169 | jid.getResourceOrEmpty().toString());
170 | }
171 |
172 | public static JID bare(String local, String domain) {
173 | return escape(local, domain, "");
174 | }
175 |
176 | private static JID escape(String local, String domain, String resource) {
177 | return new JID(XmppStringUtils.escapeLocalpart(local), domain, resource);
178 | }
179 |
180 | public static JID deleted(int id) {
181 | return new JID("", Integer.toString(id), "");
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/misc/KonException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.misc;
20 |
21 | /**
22 | * Application internal exceptions.
23 | * @author Alexander Bikadorov {@literal }
24 | */
25 | public final class KonException extends Exception {
26 |
27 | public enum Error {
28 | DB,
29 | IMPORT_ARCHIVE,
30 | IMPORT_READ_FILE,
31 | IMPORT_KEY,
32 | CHANGE_PASS,
33 | CHANGE_PASS_COPY,
34 | WRITE_FILE,
35 | READ_FILE,
36 | LOAD_KEY,
37 | LOAD_KEY_DECRYPT,
38 | CLIENT_CONNECT,
39 | CLIENT_LOGIN,
40 | CLIENT_ERROR,
41 | DOWNLOAD_CREATE,
42 | DOWNLOAD_RESPONSE,
43 | DOWNLOAD_EXECUTE,
44 | DOWNLOAD_WRITE,
45 | UPLOAD_CREATE,
46 | UPLOAD_RESPONSE,
47 | UPLOAD_EXECUTE,
48 | }
49 |
50 | private final Error mError;
51 |
52 | public KonException(Error error, Exception ex) {
53 | super(ex);
54 | mError = error;
55 | }
56 |
57 | public KonException(Error error) {
58 | super();
59 | mError = error;
60 | }
61 |
62 | public Class> getCauseClass() {
63 | Throwable cause = this.getCause();
64 | return cause == null ? this.getClass() : cause.getClass();
65 | }
66 |
67 | public Error getError() {
68 | return mError;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/misc/Searchable.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package org.kontalk.misc;
19 |
20 | /**
21 | *
22 | * @author Alexander Bikadorov {@literal }
23 | */
24 | public interface Searchable {
25 | boolean contains(String string);
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/misc/ViewEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.misc;
20 |
21 | import java.util.EnumSet;
22 | import org.kontalk.client.FeatureDiscovery;
23 | import org.kontalk.crypto.PGPUtils.PGPCoderKey;
24 | import org.kontalk.model.message.InMessage;
25 | import org.kontalk.model.message.KonMessage;
26 | import org.kontalk.model.Contact;
27 | import org.kontalk.system.Control;
28 | import org.kontalk.system.RosterHandler;
29 |
30 | /**
31 | * Events passed from controller to view.
32 | * @author Alexander Bikadorov {@literal }
33 | */
34 | public class ViewEvent {
35 |
36 | private ViewEvent() {}
37 |
38 | /** Application status changed. */
39 | public static class StatusChange extends ViewEvent {
40 | public final Control.Status status;
41 | public final EnumSet features;
42 |
43 | public StatusChange(Control.Status status, EnumSet features) {
44 | this.status = status;
45 | this.features = features;
46 | }
47 | }
48 |
49 | /** Key is password protected (ask for password). */
50 | public static class PasswordSet extends ViewEvent {
51 | }
52 |
53 | /** The personal account is missing (show import wizard). */
54 | public static class MissingAccount extends ViewEvent {
55 | public final boolean connect;
56 |
57 | public MissingAccount(boolean connect) {
58 | this.connect = connect;
59 | }
60 | }
61 |
62 | /** An exception was thrown somewhere. */
63 | public static class Exception extends ViewEvent {
64 | public final KonException exception;
65 |
66 | public Exception(KonException exception) {
67 | this.exception = exception;
68 | }
69 | }
70 |
71 | /** There was a security error while de-/encrypting a message. */
72 | public static class SecurityError extends ViewEvent {
73 | public final KonMessage message;
74 |
75 | public SecurityError(KonMessage message) {
76 | this.message = message;
77 | }
78 | }
79 |
80 | /** Got a new message. */
81 | public static class NewMessage extends ViewEvent {
82 | public final InMessage message;
83 |
84 | public NewMessage(InMessage message) {
85 | this.message = message;
86 | }
87 | }
88 |
89 | /** Got a new public key (ask whattodo). */
90 | public static class NewKey extends ViewEvent {
91 | public final Contact contact;
92 | public final PGPCoderKey key;
93 |
94 | public NewKey(Contact contact, PGPCoderKey key) {
95 | this.contact = contact;
96 | this.key = key;
97 | }
98 | }
99 |
100 | /** Contact was deleted in roster (ask whattodo). */
101 | public static class ContactDeleted extends ViewEvent {
102 | public final Contact contact;
103 |
104 | public ContactDeleted(Contact contact) {
105 | this.contact = contact;
106 | }
107 | }
108 |
109 | /** A presence error. */
110 | public static class PresenceError extends ViewEvent {
111 | public final Contact contact;
112 | public final RosterHandler.Error error;
113 |
114 | public PresenceError(Contact contact, RosterHandler.Error error) {
115 | this.contact = contact;
116 | this.error = error;
117 | }
118 | }
119 |
120 | /** A contact wants presence subscription (ask whattodo). */
121 | public static class SubscriptionRequest extends ViewEvent {
122 | public final Contact contact;
123 |
124 | public SubscriptionRequest(Contact contact) {
125 | this.contact = contact;
126 | }
127 | }
128 |
129 | /** Notify view about connection retry in X seconds. */
130 | public static class RetryTimerMessage extends ViewEvent {
131 | public final int countdown;
132 |
133 | public RetryTimerMessage(int countdown) {
134 | this.countdown = countdown;
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/model/ContactList.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package org.kontalk.model;
19 |
20 | import java.sql.ResultSet;
21 | import java.sql.SQLException;
22 | import java.util.Collections;
23 | import java.util.HashMap;
24 | import java.util.Map;
25 | import java.util.Observable;
26 | import java.util.Optional;
27 | import java.util.Set;
28 | import java.util.logging.Level;
29 | import java.util.logging.Logger;
30 | import java.util.stream.Collectors;
31 |
32 | import org.kontalk.misc.JID;
33 | import org.kontalk.persistence.Database;
34 |
35 | /**
36 | * Global list of all contacts.
37 | *
38 | * Does not contain deleted contacts.
39 | *
40 | * @author Alexander Bikadorov {@literal }
41 | */
42 | public final class ContactList extends Observable {
43 | private static final Logger LOGGER = Logger.getLogger(ContactList.class.getName());
44 |
45 | private enum ViewChange { MODIFIED }
46 |
47 | private final Map mJIDMap =
48 | Collections.synchronizedMap(new HashMap());
49 |
50 | ContactList() {}
51 |
52 | Map load() {
53 | assert mJIDMap.isEmpty();
54 |
55 | Map contactMap = new HashMap<>();
56 |
57 | Database db = Model.database();
58 | try (ResultSet resultSet = db.execSelectAll(Contact.TABLE)) {
59 | while (resultSet.next()) {
60 | Contact contact = Contact.load(resultSet);
61 |
62 | JID jid = contact.getJID();
63 | if (mJIDMap.containsKey(jid)) {
64 | LOGGER.warning("contacts with equal JIDs: " + jid);
65 | continue;
66 | }
67 | if (!contact.isDeleted())
68 | mJIDMap.put(jid, contact);
69 |
70 | contactMap.put(contact.getID(), contact);
71 | }
72 | } catch (SQLException ex) {
73 | LOGGER.log(Level.WARNING, "can't load contacts from db", ex);
74 | }
75 | this.changed(null);
76 |
77 | return contactMap;
78 | }
79 |
80 | /** Create and add a new contact. */
81 | public Optional create(JID jid, String name) {
82 | jid = jid.toBare();
83 |
84 | if (!this.isValid(jid))
85 | return Optional.empty();
86 |
87 | Contact newContact = new Contact(jid, name);
88 | if (newContact.getID() < 1)
89 | return Optional.empty();
90 |
91 | mJIDMap.put(newContact.getJID(), newContact);
92 |
93 | this.changed(ViewChange.MODIFIED);
94 | return Optional.of(newContact);
95 | }
96 |
97 | /**
98 | * Get the contact for a JID (if the JID is in the list).
99 | * Resource is removed for lookup.
100 | */
101 | public Optional get(JID jid) {
102 | return Optional.ofNullable(mJIDMap.get(jid));
103 | }
104 |
105 | /**
106 | * Get the contact that represents the user itself.
107 | */
108 | public Optional getMe() {
109 | JID myJID = Model.getUserJID();
110 | if (!myJID.isValid())
111 | return Optional.empty();
112 |
113 | return this.get(myJID);
114 | }
115 |
116 | public Set getAll(boolean withMe, boolean blocked) {
117 | synchronized(mJIDMap) {
118 | return Collections.unmodifiableSet(
119 | mJIDMap.values().stream()
120 | .filter(c ->
121 | (blocked || !c.isBlocked()) &&
122 | (withMe || !c.isMe()))
123 | .collect(Collectors.toSet()));
124 | }
125 | }
126 |
127 | public void delete(Contact contact) {
128 | boolean removed = mJIDMap.remove(contact.getJID(), contact);
129 | if (!removed) {
130 | LOGGER.warning("can't find contact "+contact);
131 | }
132 |
133 | contact.setDeleted();
134 |
135 | this.changed(ViewChange.MODIFIED);
136 | }
137 |
138 | void onShutDown() {
139 | mJIDMap.values().forEach(Contact::onShutDown);
140 | }
141 |
142 | /**
143 | * Return whether a contact with a specified JID exists.
144 | */
145 | public boolean contains(JID jid) {
146 | return mJIDMap.containsKey(jid);
147 | }
148 |
149 | public boolean changeJID(Contact contact, JID jid) {
150 | if (!this.isValid(jid))
151 | return false;
152 |
153 | mJIDMap.put(jid, contact);
154 | mJIDMap.remove(contact.getJID());
155 |
156 | contact.setJID(jid);
157 |
158 | return true;
159 | }
160 |
161 | private boolean isValid(JID jid) {
162 | if (!jid.isValid()) {
163 | LOGGER.warning("invalid jid: " + jid);
164 | return false;
165 | }
166 |
167 | if (mJIDMap.containsKey(jid)) {
168 | LOGGER.warning("jid already exists: "+jid);
169 | return false;
170 | }
171 |
172 | return true;
173 | }
174 |
175 | private void changed(ViewChange change) {
176 | this.setChanged();
177 | this.notifyObservers(change);
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/model/Model.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.model;
20 |
21 | import java.nio.file.Path;
22 | import java.util.Date;
23 | import java.util.List;
24 | import java.util.Map;
25 | import java.util.Optional;
26 | import java.util.logging.Logger;
27 | import org.kontalk.misc.JID;
28 | import org.kontalk.model.chat.Chat;
29 | import org.kontalk.model.chat.ChatList;
30 | import org.kontalk.model.message.InMessage;
31 | import org.kontalk.model.message.MessageContent;
32 | import org.kontalk.model.message.OutMessage;
33 | import org.kontalk.model.message.ProtoMessage;
34 | import org.kontalk.persistence.Config;
35 | import org.kontalk.persistence.Database;
36 | import org.kontalk.util.ClientUtils;
37 |
38 | /**
39 | *
40 | * @author Alexander Bikadorov {@literal }
41 | */
42 | public final class Model {
43 | private static final Logger LOGGER = Logger.getLogger(Model.class.getName());
44 |
45 | private static Model INSTANCE = null;
46 | private static Path APP_DIR;
47 | private static Database DATABASE;
48 |
49 | private final ContactList mContactList;
50 | private final ChatList mChatList;
51 | private final Account mAccount;
52 |
53 | private Model(Database db, Path appDir) {
54 | DATABASE = db;
55 | APP_DIR = appDir;
56 |
57 | mAccount = new Account(APP_DIR, Config.getInstance());
58 | mContactList = new ContactList();
59 | mChatList = new ChatList();
60 |
61 | Avatar.createStorageDir(appDir);
62 | }
63 |
64 | public static Model setup(Database db, Path appDir) {
65 | if (INSTANCE != null) {
66 | LOGGER.warning("already set up");
67 | return INSTANCE;
68 | }
69 |
70 | return INSTANCE = new Model(db, appDir);
71 | }
72 |
73 | public Account account() {
74 | return mAccount;
75 | }
76 |
77 | public ContactList contacts() {
78 | return mContactList;
79 | }
80 |
81 | public ChatList chats() {
82 | return mChatList;
83 | }
84 |
85 | public void load() {
86 | // order matters!
87 | Map contactMap = mContactList.load();
88 | mChatList.load(contactMap);
89 | }
90 |
91 | public void setUserJID(JID jid) {
92 | Config.getInstance().setProperty(Config.ACC_JID, jid.string());
93 |
94 | if (!mContactList.contains(jid)) {
95 | LOGGER.info("creating user contact, jid: "+jid);
96 | mContactList.create(jid, "");
97 | }
98 | }
99 |
100 | public Optional createInMessage(ProtoMessage protoMessage,
101 | Chat chat, ClientUtils.MessageIDs ids, Optional serverDate) {
102 | InMessage newMessage = new InMessage(protoMessage, chat, ids.jid,
103 | ids.xmppID, serverDate);
104 |
105 | if (newMessage.getID() <= 0)
106 | return Optional.empty();
107 |
108 | if (chat.getMessages().contains(newMessage)) {
109 | LOGGER.info("message already in chat, dropping this one");
110 | return Optional.empty();
111 | }
112 | boolean added = chat.addMessage(newMessage);
113 | if (!added) {
114 | LOGGER.warning("can't add message to chat");
115 | return Optional.empty();
116 | }
117 | return Optional.of(newMessage);
118 | }
119 |
120 | public Optional createOutMessage(Chat chat,
121 | List contacts, MessageContent content) {
122 | OutMessage newMessage = new OutMessage(chat, contacts, content,
123 | chat.isSendEncrypted());
124 |
125 | boolean added = chat.addMessage(newMessage);
126 | if (!added) {
127 | LOGGER.warning("could not add outgoing message to chat");
128 | return Optional.empty();
129 | }
130 | return Optional.of(newMessage);
131 | }
132 |
133 | public static Path appDir() {
134 | if (APP_DIR == null)
135 | throw new IllegalStateException("model not set up");
136 |
137 | return APP_DIR;
138 | }
139 |
140 | public static Database database(){
141 | if (DATABASE == null)
142 | throw new IllegalStateException("model not set up");
143 |
144 | return DATABASE;
145 | }
146 |
147 | public static JID getUserJID() {
148 | return JID.bare(Config.getInstance().getString(Config.ACC_JID));
149 | }
150 |
151 | public void onShutDown() {
152 | mContactList.onShutDown();
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/model/chat/ChatMessages.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.model.chat;
20 |
21 | import java.sql.ResultSet;
22 | import java.sql.SQLException;
23 | import java.util.Collections;
24 | import java.util.Comparator;
25 | import java.util.HashSet;
26 | import java.util.Map;
27 | import java.util.NavigableSet;
28 | import java.util.Optional;
29 | import java.util.Set;
30 | import java.util.SortedSet;
31 | import java.util.TreeSet;
32 | import java.util.logging.Level;
33 | import java.util.logging.Logger;
34 | import java.util.stream.Collectors;
35 |
36 | import org.kontalk.model.Contact;
37 | import org.kontalk.model.message.KonMessage;
38 | import org.kontalk.model.message.OutMessage;
39 | import org.kontalk.persistence.Database;
40 |
41 | /**
42 | * All messages of a chat.
43 | *
44 | * @author Alexander Bikadorov {@literal }
45 | */
46 | public final class ChatMessages {
47 | private static final Logger LOGGER = Logger.getLogger(ChatMessages.class.getName());
48 |
49 | private static final Comparator MESSAGE_COMPARATOR =
50 | (KonMessage o1, KonMessage o2) -> {
51 | int dateOrder = o1.getDate().compareTo(o2.getDate());
52 | return dateOrder != 0 ? dateOrder : Integer.compare(o1.getID(), o2.getID());
53 | };
54 |
55 | // comparator inconsistent with .equals(); using one set for ordering...
56 | private final NavigableSet mSortedSet =
57 | Collections.synchronizedNavigableSet(new TreeSet<>(MESSAGE_COMPARATOR));
58 | // ... and one set for .contains()
59 | private final Set mContainsSet =
60 | Collections.synchronizedSet(new HashSet<>());
61 |
62 | ChatMessages() {
63 | }
64 |
65 | void load(Database db, Chat chat, Map contactMap) {
66 | try (ResultSet messageRS = db.execSelectWhereInsecure(KonMessage.TABLE,
67 | KonMessage.COL_CHAT_ID + " == " + chat.getID())) {
68 | while (messageRS.next()) {
69 | KonMessage message = KonMessage.load(messageRS, chat, contactMap);
70 | if (message.getTransmissions().isEmpty())
71 | // ignore broken message
72 | continue;
73 | this.addSilent(message);
74 | }
75 | } catch (SQLException ex) {
76 | LOGGER.log(Level.WARNING, "can't load messages from db", ex);
77 | }
78 | }
79 |
80 | /**
81 | * Add message to chat without notifying other components.
82 | */
83 | boolean add(KonMessage message) {
84 | return this.addSilent(message);
85 | }
86 |
87 | private boolean addSilent(KonMessage message) {
88 | boolean added = mContainsSet.add(message);
89 | if (!added) {
90 | LOGGER.warning("message already in chat: " + message);
91 | return false;
92 | }
93 | mSortedSet.add(message);
94 | return true;
95 | }
96 |
97 | public Set getAll() {
98 | return Collections.unmodifiableSet(mSortedSet);
99 | }
100 |
101 | /** Get all outgoing messages with status "PENDING" for this chat. */
102 | public SortedSet getPending() {
103 | synchronized(mSortedSet) {
104 | return mSortedSet.stream()
105 | .filter(m -> m.getStatus() == KonMessage.Status.PENDING
106 | && m instanceof OutMessage)
107 | .map(m -> (OutMessage) m)
108 | .collect(Collectors.toCollection(() -> new TreeSet<>(MESSAGE_COMPARATOR)));
109 | }
110 | }
111 |
112 | /** Get the newest (i.e. last received) outgoing message. */
113 | public Optional getLast(String xmppID) {
114 | synchronized(mSortedSet) {
115 | return mSortedSet.descendingSet().stream()
116 | .filter(m -> m.getXMPPID().equals(xmppID) && m instanceof OutMessage)
117 | .map(m -> (OutMessage) m).findFirst();
118 | }
119 | }
120 |
121 | /** Get the last created message. */
122 | public Optional getLast() {
123 | return mSortedSet.isEmpty() ?
124 | Optional.empty() :
125 | Optional.of(mSortedSet.last());
126 | }
127 |
128 | public boolean contains(KonMessage message) {
129 | return mContainsSet.contains(message);
130 | }
131 |
132 | public int size() {
133 | return mSortedSet.size();
134 | }
135 |
136 | public boolean isEmpty() {
137 | return mSortedSet.isEmpty();
138 | }
139 |
140 | public Optional getPredecessor(KonMessage message) {
141 | SortedSet headSet = mSortedSet.headSet(message);
142 | return headSet.isEmpty() ? Optional.empty() : Optional.of(headSet.last());
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/model/chat/GroupMetaData.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.model.chat;
20 |
21 | import java.util.Map;
22 | import java.util.Objects;
23 | import java.util.logging.Level;
24 | import java.util.logging.Logger;
25 | import org.json.simple.JSONObject;
26 | import org.json.simple.JSONValue;
27 | import org.kontalk.misc.JID;
28 |
29 | /**
30 | * Immutable meta data fields for a specific group chat protocol implementation.
31 | *
32 | * @author Alexander Bikadorov {@literal }
33 | */
34 | public abstract class GroupMetaData {
35 | private static final Logger LOGGER = Logger.getLogger(GroupMetaData.class.getName());
36 |
37 | abstract String toJSON();
38 |
39 | /** Data fields specific to a Kontalk group chat (custom protocol). */
40 | public static class KonGroupData extends GroupMetaData {
41 | private static final String JSON_OWNER_JID = "jid";
42 | private static final String JSON_ID = "id";
43 |
44 | public final JID owner;
45 | public final String id;
46 |
47 | public KonGroupData(JID ownerJID, String id) {
48 | this.owner = ownerJID;
49 | this.id = id;
50 | }
51 |
52 | // using legacy lib, raw types extend Object
53 | @SuppressWarnings(value = "unchecked")
54 | @Override
55 | public String toJSON() {
56 | JSONObject json = new JSONObject();
57 | json.put(JSON_OWNER_JID, owner.string());
58 | json.put(JSON_ID, id);
59 | return json.toJSONString();
60 | }
61 |
62 | private static GroupMetaData fromJSON(Map, ?> map) {
63 | JID jid = JID.bare((String) map.get(JSON_OWNER_JID));
64 | String id = (String) map.get(JSON_ID);
65 | return new KonGroupData(jid, id);
66 | }
67 |
68 | @Override
69 | public final boolean equals(Object o) {
70 | if (this == o) {
71 | return true;
72 | }
73 | if (!(o instanceof KonGroupData)) {
74 | return false;
75 | }
76 | KonGroupData oGID = (KonGroupData) o;
77 | return owner.equals(oGID.owner) && id.equals(oGID.id);
78 | }
79 |
80 | @Override
81 | public int hashCode() {
82 | return Objects.hash(owner, id);
83 | }
84 |
85 | @Override
86 | public String toString() {
87 | return "KGD:{id="+id+",owner="+owner+"}";
88 | }
89 | }
90 |
91 | /** Data fields specific to a MUC (XEP-0045) chat. Not used/deprecated! */
92 | public static class MUCData extends GroupMetaData {
93 | private static final String JSON_ROOM = "room";
94 | private static final String JSON_PW = "pw";
95 |
96 | public final JID room;
97 | public final String password;
98 |
99 | public MUCData(JID room) {
100 | this(room, "");
101 | }
102 |
103 | public MUCData(JID room, String password) {
104 | this.room = room;
105 | this.password = password;
106 | }
107 |
108 | // using legacy lib, raw types extend Object
109 | @SuppressWarnings(value = "unchecked")
110 | @Override
111 | public String toJSON() {
112 | JSONObject json = new JSONObject();
113 | json.put(JSON_ROOM, room.string());
114 | json.put(JSON_PW, password);
115 | return json.toJSONString();
116 | }
117 |
118 | private static GroupMetaData fromJSON(Map, ?> map) {
119 | JID room = JID.bare((String) map.get(JSON_ROOM));
120 | String pw = (String) map.get(JSON_PW);
121 | return new MUCData(room, pw);
122 | }
123 |
124 | @Override
125 | public final boolean equals(Object o) {
126 | if (this == o) {
127 | return true;
128 | }
129 | if (!(o instanceof MUCData)) {
130 | return false;
131 | }
132 | MUCData oData = (MUCData) o;
133 | return room.equals(oData.room);
134 | }
135 |
136 | @Override
137 | public int hashCode() {
138 | return Objects.hash(room);
139 | }
140 |
141 | @Override
142 | public String toString() {
143 | return "MUCD:{room="+room+"}";
144 | }
145 | }
146 |
147 | static GroupMetaData fromJSONOrNull(String json) {
148 | Object obj = JSONValue.parse(json);
149 | try {
150 | Map, ?> map = (Map) obj;
151 | return map.containsKey(MUCData.JSON_ROOM) ?
152 | MUCData.fromJSON(map) :
153 | KonGroupData.fromJSON(map);
154 | } catch (NullPointerException | ClassCastException ex) {
155 | LOGGER.log(Level.WARNING, "can't parse JSON", ex);
156 | return null;
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/main/java/org/kontalk/model/chat/Member.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kontalk Java client
3 | * Copyright (C) 2016 Kontalk Devteam
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package org.kontalk.model.chat;
20 |
21 | import java.sql.ResultSet;
22 | import java.sql.SQLException;
23 | import java.util.ArrayList;
24 | import java.util.Arrays;
25 | import java.util.Collections;
26 | import java.util.Date;
27 | import java.util.List;
28 | import java.util.Map;
29 | import java.util.logging.Level;
30 | import java.util.logging.Logger;
31 | import org.jivesoftware.smackx.chatstates.ChatState;
32 | import org.kontalk.model.Contact;
33 | import org.kontalk.model.Model;
34 | import org.kontalk.persistence.Database;
35 |
36 | /**
37 | * An association between a contact and a chat.
38 | * Single chats have exactly one, group chats can have any number of members.
39 | *
40 | * @author Alexander Bikadorov {@literal }
41 | */
42 | public final class Member extends ProtoMember {
43 | private static final Logger LOGGER = Logger.getLogger(Member.class.getName());
44 |
45 | private final int mID;
46 | private final int mChatID;
47 |
48 | public static final String TABLE = "receiver";
49 | public static final String COL_CONTACT_ID = "user_id";
50 | public static final String COL_ROLE = "role";
51 | public static final String COL_CHAT_ID = "thread_id";
52 | public static final String SCHEMA = "(" +
53 | Database.SQL_ID +
54 | COL_CHAT_ID + " INTEGER NOT NULL, " +
55 | COL_CONTACT_ID + " INTEGER NOT NULL, " +
56 | COL_ROLE + " INTEGER NOT NULL, " +
57 | "UNIQUE (" + COL_CHAT_ID + ", " + COL_CONTACT_ID + "), " +
58 | "FOREIGN KEY (" + COL_CHAT_ID + ") REFERENCES " + Chat.TABLE + " (_id), " +
59 | "FOREIGN KEY (" + COL_CONTACT_ID + ") REFERENCES " + Contact.TABLE + " (_id) " +
60 | ")";
61 |
62 | private ChatState mState = ChatState.gone;
63 | // note: the Android client does not set active states when only viewing
64 | // the chat (not necessary according to XEP-0085), this makes the
65 | // extra date field a bit useless
66 | // TODO save last active date to DB
67 | private Date mLastActive = null;
68 |
69 | Member(Contact contact, int chatID) {
70 | this(contact, Role.DEFAULT, chatID);
71 | }
72 |
73 | Member(ProtoMember protoMember, int chatID) {
74 | this(protoMember.mContact, protoMember.mRole, chatID);
75 | }
76 |
77 | private Member(Contact contact, Role role, int chatID) {
78 | super(contact, role);
79 |
80 | mChatID = chatID;
81 |
82 | List