├── .github └── workflows │ ├── release.yml │ └── unitTest.yml ├── .gitignore ├── CHANGELOG.md ├── Jenkinsfile ├── LICENCE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── src ├── main │ ├── java │ │ └── fr │ │ │ └── enimaloc │ │ │ └── jircd │ │ │ ├── Constant.java │ │ │ ├── Main.java │ │ │ ├── channel │ │ │ ├── Channel.java │ │ │ └── ChannelModes.java │ │ │ ├── commands │ │ │ ├── Command.java │ │ │ ├── channel │ │ │ │ ├── JoinCommand.java │ │ │ │ ├── KickCommand.java │ │ │ │ ├── ListCommand.java │ │ │ │ ├── NamesCommand.java │ │ │ │ ├── PartCommand.java │ │ │ │ └── TopicCommand.java │ │ │ ├── connection │ │ │ │ ├── NickCommand.java │ │ │ │ ├── OperCommand.java │ │ │ │ ├── PassCommand.java │ │ │ │ ├── PingCommand.java │ │ │ │ ├── QuitCommand.java │ │ │ │ └── UserCommand.java │ │ │ ├── messages │ │ │ │ ├── Dup.java │ │ │ │ ├── NoticeCommand.java │ │ │ │ └── PrivmsgCommand.java │ │ │ ├── operator │ │ │ │ ├── KillCommand.java │ │ │ │ ├── RehashCommand.java │ │ │ │ ├── RestartCommand.java │ │ │ │ └── SQuitCommand.java │ │ │ ├── optional │ │ │ │ ├── AwayCommand.java │ │ │ │ ├── LinksCommand.java │ │ │ │ ├── UserhostCommand.java │ │ │ │ └── WallOpsCommand.java │ │ │ ├── server │ │ │ │ ├── AdminCommand.java │ │ │ │ ├── ConnectCommand.java │ │ │ │ ├── HelpCommand.java │ │ │ │ ├── InfoCommand.java │ │ │ │ ├── LUserCommand.java │ │ │ │ ├── ModeCommand.java │ │ │ │ ├── MotdCommand.java │ │ │ │ ├── StatsCommand.java │ │ │ │ ├── TimeCommand.java │ │ │ │ └── VersionCommand.java │ │ │ └── user │ │ │ │ ├── WhoCommand.java │ │ │ │ ├── WhoisCommand.java │ │ │ │ └── WhowasCommand.java │ │ │ ├── message │ │ │ ├── Mask.java │ │ │ ├── Message.java │ │ │ └── Regex.java │ │ │ ├── server │ │ │ ├── JIRCD.java │ │ │ ├── ServerSettings.java │ │ │ └── attributes │ │ │ │ ├── Attribute.java │ │ │ │ ├── ChannelAttribute.java │ │ │ │ ├── ServerAttribute.java │ │ │ │ ├── SupportAttribute.java │ │ │ │ └── UserAttribute.java │ │ │ ├── user │ │ │ ├── User.java │ │ │ ├── UserInfo.java │ │ │ ├── UserModes.java │ │ │ └── UserState.java │ │ │ └── utils │ │ │ └── function │ │ │ └── TriConsumer.java │ └── resources │ │ └── logback.xml └── test │ └── java │ ├── fr │ └── enimaloc │ │ └── jircd │ │ ├── ServerBase.java │ │ ├── SocketBase.java │ │ ├── channel │ │ └── ChannelModesTest.java │ │ ├── commands │ │ ├── channel │ │ │ ├── CommandChannelBase.java │ │ │ ├── JoinCommandTest.java │ │ │ ├── KickCommandTest.java │ │ │ ├── ListCommandTest.java │ │ │ ├── NamesCommandTest.java │ │ │ ├── PartCommandTest.java │ │ │ └── TopicCommandTest.java │ │ ├── connection │ │ │ ├── ConnectionCommandBase.java │ │ │ ├── NickCommandTest.java │ │ │ ├── OperCommandTest.java │ │ │ ├── PassCommandTest.java │ │ │ ├── PingCommandTest.java │ │ │ ├── QuitCommandTest.java │ │ │ └── UserCommandTest.java │ │ ├── messages │ │ │ ├── MessageCommandBase.java │ │ │ ├── NoticeCommandTest.java │ │ │ └── PrivmsgCommandTest.java │ │ ├── operator │ │ │ ├── KillCommandTest.java │ │ │ ├── OperatorCommandBase.java │ │ │ ├── RehashCommandTest.java │ │ │ ├── RestartCommandTest.java │ │ │ └── SQuitCommandTest.java │ │ ├── optional │ │ │ ├── AwayCommandTest.java │ │ │ ├── LinksCommandTest.java │ │ │ ├── OptionalCommandBase.java │ │ │ ├── UserhostCommandTest.java │ │ │ └── WallOpsCommandTest.java │ │ ├── server │ │ │ ├── AdminCommandTest.java │ │ │ ├── ConnectCommandTest.java │ │ │ ├── HelpCommandTest.java │ │ │ ├── InfoCommandTest.java │ │ │ ├── LUserCommandTest.java │ │ │ ├── ModeCommandTest.java │ │ │ ├── MotdCommandTest.java │ │ │ ├── ServerCommandBase.java │ │ │ ├── StatsCommandTest.java │ │ │ ├── TimeCommandTest.java │ │ │ └── VersionCommandTest.java │ │ └── user │ │ │ ├── UserCommandBase.java │ │ │ ├── WhoCommandTest.java │ │ │ ├── WhoisCommandTest.java │ │ │ └── WhowasCommandTest.java │ │ ├── message │ │ └── MaskTest.java │ │ └── user │ │ └── UserModesTest.java │ └── utils │ ├── CommandClazzTest.java │ ├── CommandNameGen.java │ ├── FullModuleTest.java │ └── ListUtils.java └── version.txt /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | name: release-please 6 | jobs: 7 | release-please: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: GoogleCloudPlatform/release-please-action@v2 11 | id: release 12 | with: 13 | release-type: simple 14 | - uses: actions/checkout@v2 15 | - name: tag major and minor versions 16 | if: ${{ steps.release.outputs.release_created }} 17 | run: | 18 | git config user.name github-actions[bot] 19 | git config user.email 41898282+github-actions[bot]@users.noreply.github.com 20 | git remote add gh-token "https://${{ secrets.GITHUB_TOKEN }}@github.com/google-github-actions/release-please-action.git" 21 | git tag -d ${{ steps.release.outputs.major }} || true 22 | git tag -d ${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} || true 23 | git push origin :${{ steps.release.outputs.major }} || true 24 | git push origin :${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} || true 25 | git tag -a ${{ steps.release.outputs.major }} -m "Release ${{ steps.release.outputs.major }}" 26 | git tag -a ${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} -m "Release ${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}" 27 | git push origin ${{ steps.release.outputs.major }} 28 | git push origin ${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} 29 | -------------------------------------------------------------------------------- /.github/workflows/unitTest.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | jobs: 5 | build-test: 6 | name: Build & Test 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout Code 10 | uses: actions/checkout@v3 11 | - name: Set up JDK 17 12 | uses: actions/setup-java@v3 13 | with: 14 | java-version: 17 15 | distribution: temurin 16 | - name: Build and Run Tests 17 | run: ./gradlew :test -i # execute your tests generating test results 18 | - name: Test Report 19 | uses: phoenix-actions/test-reporting@v8 20 | id: test-report # Set ID reference for step 21 | if: success() || failure() # run this step even if previous step failed 22 | with: 23 | name: JUnit Tests # Name of the check run which will be created 24 | reporter: java-junit # Format of test results 25 | path: '**/build/test-results/test/TEST-*.xml' 26 | max-annotations: 50 27 | 28 | - name: Read output variables 29 | run: | 30 | echo "url is ${{ steps.test-report.outputs.runHtmlUrl }}" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | Thumbs.db:encryptable 3 | ehthumbs.db 4 | ehthumbs_vista.db 5 | *.stackdump 6 | [Dd]esktop.ini 7 | $RECYCLE.BIN/ 8 | *.cab 9 | *.msi 10 | *.msix 11 | *.msm 12 | *.msp 13 | *.lnk 14 | *~ 15 | .fuse_hidden* 16 | .directory 17 | .Trash-* 18 | .nfs* 19 | .idea 20 | *.iml 21 | out 22 | gen 23 | *.class 24 | *.log 25 | *.ctxt 26 | .mtj.tmp/ 27 | *.jar 28 | *.war 29 | *.nar 30 | *.ear 31 | *.zip 32 | *.tar.gz 33 | *.rar 34 | hs_err_pid* 35 | .gradle 36 | **/build/ 37 | !src/**/build/ 38 | gradle-app.setting 39 | !gradle-wrapper.jar 40 | .gradletasknamecache 41 | .idea/**/workspace.xml 42 | .idea/**/tasks.xml 43 | .idea/**/usage.statistics.xml 44 | .idea/**/dictionaries 45 | .idea/**/shelf 46 | .idea/**/contentModel.xml 47 | .idea/**/dataSources/ 48 | .idea/**/dataSources.ids 49 | .idea/**/dataSources.local.xml 50 | .idea/**/sqlDataSources.xml 51 | .idea/**/dynamic.xml 52 | .idea/**/uiDesigner.xml 53 | .idea/**/dbnavigator.xml 54 | .idea/**/gradle.xml 55 | .idea/**/libraries 56 | cmake-build-*/ 57 | .idea/**/mongoSettings.xml 58 | *.iws 59 | out/ 60 | .idea_modules/ 61 | atlassian-ide-plugin.xml 62 | .idea/replstate.xml 63 | com_crashlytics_export_strings.xml 64 | crashlytics.properties 65 | crashlytics-build.properties 66 | fabric.properties 67 | .idea/httpRequests 68 | .idea/caches/build_file_checksums.ser 69 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | docker { 4 | image 'gradle:jdk17' 5 | label 'gradle' 6 | reuseNode true 7 | } 8 | } 9 | stages { 10 | stage('SCM') { 11 | steps { 12 | checkout scm 13 | } 14 | } 15 | stage('SonarQube Analysis') { 16 | steps { 17 | withSonarQubeEnv() { 18 | sh "./gradlew sonarqube" 19 | } 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Antoine (enimaloc) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 18 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JIRCD 2 | 3 | JIRCD is a Java [Internet Relay Chat](https://wikipedia.org/wiki/Internet_Relay_Chat) Deamon which can be used as Internet Relay Chat server, 4 | this project follows [Oracle Code Convention](https://www.oracle.com/java/technologies/javase/codeconventions-contents.html), 5 | and we follow the [Internet Relay Chat documentation](https://modern.ircdocs.horse/index.html) 6 | and for undocumented part needed we follow [RFC 2812](https://datatracker.ietf.org/doc/html/rfc2812). 7 | For commit we follow [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/). 8 | All pull requests which do not respect the conventions above will be considered as [invalid](https://github.com/enimaloc/jircd/issues?q=label%3Ainvalid). 9 | 10 | ## Supported commands 11 | 12 | - [Channel](https://modern.ircdocs.horse/index.html#channel-operations) ([ed1be41](https://github.com/enimaloc/jircd/commit/ed1be41f01481f007ba83e352e58f1af84a13642)) 13 | - [Join](https://modern.ircdocs.horse/index.html#join-message) 14 | - [List](https://modern.ircdocs.horse/index.html#list-message) 15 | - [Names](https://modern.ircdocs.horse/index.html#names-message) 16 | - [Part](https://modern.ircdocs.horse/index.html#part-message) 17 | - [Topic](https://modern.ircdocs.horse/index.html#topic-message) 18 | - [Connection](https://modern.ircdocs.horse/index.html#connection-messages) ([d37e513](https://github.com/enimaloc/jircd/commit/d37e51341e1e9193b7c7343c3c19ce9f44c023bb)) 19 | - [Nick](https://modern.ircdocs.horse/index.html#nick-message) 20 | - [Oper](https://modern.ircdocs.horse/index.html#oper-message) 21 | - [Quit](https://modern.ircdocs.horse/index.html#quit-message) 22 | - [User](https://modern.ircdocs.horse/index.html#user-message) 23 | - [Sending Messages](https://modern.ircdocs.horse/index.html#sending-messages) ([feafd0a](https://github.com/enimaloc/jircd/commit/feafd0a467cff85bfa04dd41342a6b66b5a666e6)) 24 | - [Notice](https://modern.ircdocs.horse/index.html#notice-message) 25 | - [Privmsg](https://modern.ircdocs.horse/index.html#privmsg-message) 26 | - [Operator](https://modern.ircdocs.horse/index.html#operator-messages) ([c58bb0c](https://github.com/enimaloc/jircd/commit/c58bb0c1fde1d16c240a81fb25cb27f27f50c8e9)) 27 | - [Kill](https://modern.ircdocs.horse/index.html#kill-message) 28 | - [Optional](https://modern.ircdocs.horse/index.html#optional-messages) ([25a0fc4](https://github.com/enimaloc/jircd/commit/25a0fc4de3c9cc08133e9d5943cdaf47de2be5b6)) 29 | - [Userhost](https://modern.ircdocs.horse/index.html#userhost-message) 30 | - [Server Queries and Commands](https://modern.ircdocs.horse/index.html#server-queries-and-commands) ([3636485](https://github.com/enimaloc/jircd/commit/363648516b2323af0b3b952f3f558612328deb2a)) 31 | - [Admin](https://modern.ircdocs.horse/index.html#admin-message) 32 | - [Connect](https://modern.ircdocs.horse/index.html#connect-message) 33 | - [Info](https://modern.ircdocs.horse/index.html#info-message) 34 | - [Mode](https://modern.ircdocs.horse/index.html#mode-message) 35 | - [Motd](https://modern.ircdocs.horse/index.html#motd-message) 36 | - [Stats](https://modern.ircdocs.horse/index.html#stats-message) 37 | - [Time](https://modern.ircdocs.horse/index.html#time-message) 38 | - [Version](https://modern.ircdocs.horse/index.html#version-message) 39 | - Undocumented ([RFC 2812](https://datatracker.ietf.org/doc/html/rfc2812)) 40 | - [Miscellaneous](https://datatracker.ietf.org/doc/html/rfc2812#section-3.7) ([c58bb0c](https://github.com/enimaloc/jircd/commit/c58bb0c1fde1d16c240a81fb25cb27f27f50c8e9)) 41 | - [Ping](https://datatracker.ietf.org/doc/html/rfc2812#section-3.7.2) 42 | 43 | ## License 44 | 45 | The source code for the site is licensed under the MIT license, which you can find in 46 | the [LICENSE](LICENCE) file. -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id "com.github.johnrengelman.shadow" version "7.1.0" 4 | id "org.sonarqube" version "3.3" 5 | } 6 | 7 | sonarqube { 8 | properties { 9 | property "sonar.projectKey", "enimaloc_jircd" 10 | } 11 | } 12 | 13 | group 'fr.enimaloc' 14 | version new File("version.txt").text 15 | 16 | repositories { 17 | mavenCentral() 18 | maven { url "https://m2.enimaloc.fr/releases/" } 19 | } 20 | 21 | dependencies { 22 | implementation 'fr.enimaloc.enutils:classes:0.5.0' 23 | 24 | implementation 'com.electronwill.night-config:toml:3.6.5' 25 | implementation 'com.electronwill.night-config:json:3.6.5' 26 | 27 | implementation 'org.slf4j:slf4j-api:1.7.36' 28 | 29 | implementation 'ch.qos.logback:logback-core:1.2.11' 30 | implementation 'ch.qos.logback:logback-classic:1.2.11' 31 | 32 | implementation 'io.prometheus:simpleclient:0.15.0' 33 | implementation 'io.prometheus:simpleclient_httpserver:0.15.0' 34 | implementation 'io.prometheus:simpleclient_hotspot:0.15.0' 35 | 36 | implementation 'com.github.davidmoten:geo:0.8.0' 37 | implementation 'org.jetbrains:annotations:20.1.0' 38 | 39 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2' 40 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' 41 | testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2' 42 | testImplementation 'org.mockito:mockito-core:4.2.0' 43 | } 44 | 45 | test { 46 | reports { 47 | junitXml.enabled = true 48 | } 49 | useJUnitPlatform() 50 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enimaloc/jircd/20e163581a2fdb68f33cb062d5347d2c3e009873/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'jircd' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/Constant.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd; 2 | 3 | public class Constant { 4 | 5 | Constant() { 6 | } 7 | 8 | public static final String NAME = "jircd"; 9 | public static final String VERSION = "1.0.0"; 10 | public static final String GITHUB = "https://github.com/enimaloc/jircd"; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/Main.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd; 2 | 3 | import fr.enimaloc.jircd.server.JIRCD; 4 | 5 | public class Main { 6 | 7 | public static void main(String[] args) { 8 | JIRCD.newInstance(); 9 | } 10 | 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/Command.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | import java.lang.reflect.Method; 8 | 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.TYPE, ElementType.METHOD}) 11 | public @interface Command { 12 | 13 | String DEFAULT_STRING = "\0"; 14 | 15 | String name() default DEFAULT_STRING; 16 | 17 | boolean trailing() default false; 18 | 19 | record CommandIdentifier(int parametersCount, boolean hasTrailing) {} 20 | 21 | record CommandIdentity(Object instance, Method method) {} 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/channel/JoinCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.channel; 2 | 3 | import fr.enimaloc.jircd.channel.Channel; 4 | import fr.enimaloc.jircd.commands.Command; 5 | import fr.enimaloc.jircd.message.Mask; 6 | import fr.enimaloc.jircd.message.Message; 7 | import fr.enimaloc.jircd.message.Regex; 8 | import fr.enimaloc.jircd.user.User; 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.util.Optional; 11 | import java.util.stream.Collectors; 12 | 13 | @Command(name = "join") 14 | public class JoinCommand { 15 | 16 | @Command 17 | public void execute(User user, String channelsRaw) throws InvocationTargetException, IllegalAccessException { 18 | execute(user, channelsRaw, ""); 19 | } 20 | 21 | @Command 22 | public void execute(User user, String channelsRaw, String passwdRaw) 23 | throws InvocationTargetException, IllegalAccessException { 24 | if (channelsRaw.equals("0")) { 25 | user.process( 26 | "PART " + user.channels().stream().map(Channel::name).collect(Collectors.joining(","))); 27 | return; 28 | } 29 | for (Couple couple : couples(channelsRaw, passwdRaw)) { 30 | actionPerChannel(user, couple); 31 | } 32 | } 33 | 34 | private static void actionPerChannel(User user, Couple couple) { 35 | if (!Regex.CHANNEL.matcher(couple.name()).matches()) { 36 | user.send(Message.ERR_NOSUCHCHANNEL.client(user.info()).channel(couple.name())); 37 | return; 38 | } 39 | 40 | Channel channelObj = user.server() 41 | .channels() 42 | .stream() 43 | .filter(c -> c.name().equals(couple.name())) 44 | .findFirst() 45 | .orElseGet(() -> { 46 | Channel temp = new Channel(user, couple.name()); 47 | user.server().originalChannels().add(temp); 48 | return temp; 49 | }); 50 | 51 | Optional error = invalid(user, couple, channelObj); 52 | if (error.isPresent()) { 53 | user.send(error.get().client(user.info()).channel(couple.name())); 54 | return; 55 | } 56 | 57 | channelObj.addUser(user); 58 | } 59 | 60 | private static Optional invalid(User user, Couple couple, Channel channelObj) { 61 | Message error = null; 62 | if (user.channels().size() >= user.server().supportAttribute().channelAttribute().channelLen()) { 63 | error = Message.ERR_TOOMANYCHANNELS; 64 | } 65 | if (channelObj.modes().password().isPresent() && !channelObj.modes().password().equals(couple.password())) { 66 | error = Message.ERR_BADCHANNELKEY; 67 | } 68 | if (channelObj.modes() 69 | .bans() 70 | .stream() 71 | .anyMatch(mask -> new Mask(mask).toPattern().matcher(user.info().full()).matches()) && 72 | channelObj.modes() 73 | .except() 74 | .stream() 75 | .noneMatch(mask -> new Mask(mask).toPattern().matcher(user.info().full()).matches())) { 76 | error = Message.ERR_BANNEDFROMCHAN; 77 | } 78 | if (channelObj.users().size() >= channelObj.modes().limit().orElse(Integer.MAX_VALUE)) { 79 | error = Message.ERR_CHANNELISFULL; 80 | } 81 | if (channelObj.modes().inviteOnly()) { 82 | error = Message.ERR_INVITEONLYCHAN; 83 | } 84 | return Optional.ofNullable(error); 85 | } 86 | 87 | Couple[] couples(String channelsRaw, String passwdRaw) { 88 | String[] channels = channelsRaw.split(","); 89 | String[] passwds = passwdRaw.split(","); 90 | 91 | Couple[] couples = new Couple[channels.length]; 92 | for (int i = 0; i < couples.length; i++) { 93 | couples[i] = new Couple(channels[i], i < passwds.length ? passwds[i] : null); 94 | } 95 | return couples; 96 | } 97 | 98 | record Couple(String name, String password0) { 99 | public Optional password() { 100 | return Optional.ofNullable(password0); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/channel/KickCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * KickCommand 3 | * 4 | * 0.0.1 5 | * 6 | * 19/07/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.channel; 9 | 10 | import fr.enimaloc.jircd.channel.Channel; 11 | import fr.enimaloc.jircd.commands.Command; 12 | import fr.enimaloc.jircd.message.Message; 13 | import fr.enimaloc.jircd.message.Regex; 14 | import fr.enimaloc.jircd.user.User; 15 | import java.util.Optional; 16 | 17 | /** 18 | * 19 | */ 20 | @Command(name = "kick") 21 | public class KickCommand { 22 | 23 | @Command 24 | public void execute(User user, String channel, String users) { 25 | execute(user, channel, users, "Kicked by " + user.info().nickname()); 26 | } 27 | 28 | @Command(trailing = true) 29 | public void execute(User user, String channelName, String users, String reason) { 30 | if (!Regex.CHANNEL.matcher(channelName).matches()) { 31 | user.send(Message.ERR_NOSUCHCHANNEL.client(user.info()).channel(channelName)); 32 | return; 33 | } 34 | Optional channelOpt = user.channels() 35 | .stream() 36 | .filter(channel -> channel.name().equals(channelName)) 37 | .findFirst(); 38 | if (channelOpt.isEmpty()) { 39 | user.send(Message.ERR_NOTONCHANNEL.client(user.info()).channel(channelName)); 40 | return; 41 | } 42 | 43 | Channel channelObj = channelOpt.get(); 44 | if (!channelObj.isRanked(user, Channel.Rank.HALF_OPERATOR)) { 45 | user.send(Message.ERR_CHANOPRIVSNEEDED.client(user.info()).channel(channelObj)); 46 | return; 47 | } 48 | 49 | String[] usersNames = new String[1]; 50 | if (users.contains(",")) { 51 | usersNames = users.split(","); 52 | } else { 53 | usersNames[0] = users; 54 | } 55 | for (String userName : usersNames) { 56 | Optional userOpt = channelObj.users() 57 | .stream() 58 | .filter(user1 -> user1.info().nickname().equals(userName)) 59 | .findFirst(); 60 | if (userOpt.isEmpty() || channelObj.isRanked(userOpt.get(), Channel.Rank.PROTECTED)) { 61 | if (userOpt.isEmpty()) { 62 | user.send(Message.ERR_USERNOTINCHANNEL.client(user.info()) 63 | .addFormat("nick", userName) 64 | .addFormat("channel", channelName)); 65 | } 66 | continue; 67 | } 68 | User userObj = userOpt.get(); 69 | channelObj.broadcast( 70 | ":%s KICK %s %s%s".formatted(user.info().format(), 71 | channelName, 72 | userObj.info().nickname(), 73 | " :" + reason) 74 | ); 75 | channelObj.removeUser(userObj); 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/channel/ListCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.channel; 2 | 3 | import fr.enimaloc.enutils.classes.NumberUtils; 4 | import fr.enimaloc.jircd.channel.Channel; 5 | import fr.enimaloc.jircd.commands.Command; 6 | import fr.enimaloc.jircd.message.Mask; 7 | import fr.enimaloc.jircd.message.Message; 8 | import fr.enimaloc.jircd.user.User; 9 | import java.util.function.Predicate; 10 | import java.util.regex.Pattern; 11 | import java.util.stream.Collectors; 12 | 13 | @Command(name = "list") 14 | public class ListCommand { 15 | 16 | @Command 17 | public void execute(User user) { 18 | executeWithChannel(user, user.server() 19 | .channels() 20 | .stream() 21 | .filter(channel -> !channel.modes().secret()) 22 | .map(Channel::name) 23 | .collect(Collectors.joining(","))); 24 | } 25 | 26 | @Command 27 | public void execute(User user, String raw) { 28 | if (raw.startsWith("C<") || raw.startsWith("C>") || 29 | raw.startsWith("T<") || raw.startsWith("T>") || 30 | raw.startsWith("<") || raw.startsWith(">") 31 | ) { 32 | executeWithEList(user, raw); 33 | } else { 34 | executeWithChannel(user, raw); 35 | } 36 | } 37 | 38 | private void executeWithChannel(User user, String raw) { 39 | execute(user, raw, ""); 40 | } 41 | 42 | private void executeWithEList(User user, String raw) { 43 | execute(user, user.server() 44 | .channels() 45 | .stream() 46 | .filter(channel -> !channel.modes().secret()) 47 | .map(Channel::name) 48 | .collect(Collectors.joining(",")), raw); 49 | } 50 | 51 | @Command 52 | public void execute(User user, String channelsRaw, String eListRaw) { 53 | Predicate predicate = channel -> true; 54 | String[] eList; 55 | if (eListRaw == null || eListRaw.isBlank() || eListRaw.isEmpty()) { 56 | eList = new String[0]; 57 | } else if (eListRaw.contains(",")) { 58 | eList = eListRaw.split(","); 59 | } else { 60 | eList = new String[]{eListRaw}; 61 | } 62 | for (String e : eList) { 63 | char[] chars = e.toCharArray(); 64 | int i = 0; 65 | char identifier = chars[i] != '<' && chars[i] != '>' 66 | ? Character.toUpperCase(chars[i++]) 67 | : '\u0000'; // ignored for now 68 | char comparaison = Character.toUpperCase(chars[i++]); 69 | int b = NumberUtils.getSafe(e.substring(i), Integer.class).orElse(Integer.MIN_VALUE); 70 | 71 | // TODO: 15/08/2022 Complete this part 72 | predicate = switch (comparaison) { 73 | case '>' -> switch (identifier) { 74 | // case 'C' -> predicate.and(channel -> channel.) 75 | // case 'T' -> predicate.and(channel -> channel.topic().isPresent() && 76 | // channel.topic().get().unixTimestamp() > b); 77 | case '\u0000' -> predicate.and(channel -> channel.users().size() > b); 78 | default -> throw new IllegalStateException("Unexpected value: " + identifier); 79 | }; 80 | case '<' -> switch (identifier) { 81 | // case 'C' -> predicate.and(channel -> channel.) 82 | // case 'T' -> predicate.and(channel -> channel.topic().isPresent() && 83 | // channel.topic().get().unixTimestamp() < b); 84 | case '\u0000' -> predicate.and(channel -> channel.users().size() < b); 85 | default -> throw new IllegalStateException("Unexpected value: " + identifier); 86 | }; 87 | default -> predicate.and(channel -> Pattern.compile(e).matcher(channel.name()).matches()); 88 | }; 89 | } 90 | 91 | String[] channels; 92 | if (channelsRaw.contains(",")) { 93 | channels = channelsRaw.split(","); 94 | } else { 95 | channels = new String[]{channelsRaw}; 96 | } 97 | user.send(Message.RPL_LISTSTART.client(user.info())); 98 | for (String channelName : channels) { 99 | user.server() 100 | .channels() 101 | .stream() 102 | .filter(predicate.and(channel -> new Mask("*" + channelName + "*").toPattern() 103 | .matcher(channel.name()) 104 | .matches())) 105 | .map(channel -> Message.RPL_LIST.client(user.info()) 106 | .channel(channel) 107 | .addFormat("client count", channel.users().size()) 108 | .addFormat("topic", channel.topic() 109 | .orElse(new Channel.Topic("", null)) 110 | .topic())) 111 | .forEach(user::send); 112 | } 113 | user.send(Message.RPL_LISTEND.client(user.info())); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/channel/NamesCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.channel; 2 | 3 | import fr.enimaloc.jircd.channel.Channel; 4 | import fr.enimaloc.jircd.commands.Command; 5 | import fr.enimaloc.jircd.message.Message; 6 | import fr.enimaloc.jircd.user.User; 7 | import java.util.Optional; 8 | import java.util.stream.Collectors; 9 | 10 | @Command(name = "names") 11 | public class NamesCommand { 12 | 13 | @Command 14 | public void execute(User user) { 15 | execute(user, user.server().channels().stream().map(Channel::name).collect(Collectors.joining(","))); 16 | } 17 | 18 | @Command 19 | public void execute(User user, String channelsRaw) { 20 | String[] channels; 21 | if (channelsRaw.contains(",")) { 22 | channels = channelsRaw.split(","); 23 | } else { 24 | channels = new String[]{channelsRaw}; 25 | } 26 | for (String channelName : channels) { 27 | Optional channelOpt = user.server() 28 | .channels() 29 | .stream() 30 | .filter(channel -> channel.name().equals(channelName)) 31 | .findFirst(); 32 | boolean userIn = user.channels().stream().anyMatch(channel -> channel.name().equals(channelName)); 33 | 34 | if (channelOpt.isPresent() && (!channelOpt.get().modes().secret() || userIn)) { 35 | Channel channel = channelOpt.get(); 36 | String nicknames = channel.users() 37 | .stream() 38 | .filter(u -> !u.modes().invisible() || userIn) 39 | .map(u -> channel.prefix(u) + u.modes().prefix() + u.info().format()) 40 | .collect(Collectors.joining(" ")); 41 | user.send(Message.RPL_NAMREPLY.client(user.info()) 42 | .channel(channelName) 43 | .addFormat("symbol", channel.modes().secret() ? "@" : 44 | channel.modes().password().map(s -> "*").orElse("=")) 45 | .addFormat("nicknames", nicknames)); 46 | } 47 | user.send(Message.RPL_ENDOFNAMES.client(user.info()).channel(channelName)); 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/channel/PartCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.channel; 2 | 3 | import fr.enimaloc.jircd.channel.Channel; 4 | import fr.enimaloc.jircd.message.Message; 5 | import fr.enimaloc.jircd.message.Regex; 6 | import fr.enimaloc.jircd.commands.Command; 7 | import fr.enimaloc.jircd.user.User; 8 | import java.util.Optional; 9 | 10 | @Command(name = "part") 11 | public class PartCommand { 12 | 13 | @Command() 14 | public void execute(User user, String channelsRaw) { 15 | execute(user, channelsRaw, null); 16 | } 17 | 18 | @Command(trailing = true) 19 | public void execute(User user, String channelsRaw, String reason) { 20 | String[] channelsNames = new String[1]; 21 | if (channelsRaw.contains(",")) { 22 | channelsNames = channelsRaw.split(","); 23 | } else { 24 | channelsNames[0] = channelsRaw; 25 | } 26 | for (String channelName : channelsNames) { 27 | Optional channelOpt = user.channels() 28 | .stream() 29 | .filter(channel -> channel.name().equals(channelName)) 30 | .findFirst(); 31 | if (!Regex.CHANNEL.matcher(channelName).matches() || channelOpt.isEmpty()) { 32 | if (!Regex.CHANNEL.matcher(channelName).matches()) { 33 | user.send(Message.ERR_NOSUCHCHANNEL.client(user.info()).channel(channelName)); 34 | } else { 35 | user.send(Message.ERR_NOTONCHANNEL.client(user.info()).channel(channelName)); 36 | } 37 | continue; 38 | } 39 | Channel channelObj = channelOpt.get(); 40 | 41 | channelObj.broadcast( 42 | ":%s PART %s%s".formatted(user.info().format(), 43 | channelName, 44 | reason == null || reason.isEmpty() || reason.isBlank() ? 45 | "" : 46 | " :" + reason) 47 | ); 48 | channelObj.removeUser(user); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/channel/TopicCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.channel; 2 | 3 | import fr.enimaloc.jircd.channel.Channel; 4 | import fr.enimaloc.jircd.commands.Command; 5 | import fr.enimaloc.jircd.message.Message; 6 | import fr.enimaloc.jircd.message.Regex; 7 | import fr.enimaloc.jircd.user.User; 8 | import java.util.Optional; 9 | 10 | @Command(name = "topic") 11 | public class TopicCommand { 12 | 13 | @Command 14 | public void execute(User user, String channelName) { 15 | execute(user, channelName, null); 16 | } 17 | 18 | @Command(trailing = true) 19 | public void execute(User user, String channelName, String topic) { 20 | if (!Regex.CHANNEL.matcher(channelName).matches()) { 21 | user.send(Message.ERR_NOSUCHCHANNEL.client(user.info()).channel(channelName)); 22 | return; 23 | } 24 | 25 | Optional channelOpt = user.channels() 26 | .stream() 27 | .filter(channel -> channel.name().contains(channelName)) 28 | .findFirst(); 29 | if (channelOpt.isEmpty()) { 30 | user.send(Message.ERR_NOTONCHANNEL.client(user.info()).channel(channelName)); 31 | return; 32 | } 33 | 34 | Channel channel = channelOpt.get(); 35 | if (topic == null) { 36 | Optional topicOpt = channel.topic(); 37 | if (topicOpt.isEmpty()) { 38 | user.send(Message.RPL_NOTOPIC.client(user.info()).channel(channel)); 39 | return; 40 | } 41 | Channel.Topic topicObj = topicOpt.get(); 42 | user.send(Message.RPL_TOPIC.client(user.info()) 43 | .channel(channel) 44 | .addFormat("topic", topicObj.topic())); 45 | user.send(Message.RPL_TOPICWHOTIME.client(user.info()) 46 | .channel(channel) 47 | .addFormat("nick", topicObj.user().info().nickname()) 48 | .addFormat("setat", topicObj.unixTimestamp())); 49 | return; 50 | } 51 | 52 | if (!(channel.prefix(user) + user.modes().prefix()).matches("[@%]") && channel.modes().protected0()) { 53 | user.send(Message.ERR_CHANOPRIVSNEEDED.client(user.info()).channel(channel)); 54 | return; 55 | } 56 | channel.topic(topic.isEmpty() || topic.isBlank() ? null : new Channel.Topic(topic, user)); 57 | 58 | Optional topicOpt = channel.topic(); 59 | if (topicOpt.isEmpty()) { 60 | channel.broadcast(user.server().settings().host(), 61 | Message.RPL_NOTOPIC.client(user.info()).channel(channel)); 62 | return; 63 | } 64 | Channel.Topic topicObj = topicOpt.get(); 65 | channel.broadcast(user.server().settings().host(), 66 | Message.RPL_TOPIC.client(user.info()) 67 | .channel(channel) 68 | .addFormat("topic", topicObj.topic())); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/connection/NickCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.connection; 2 | 3 | import fr.enimaloc.jircd.message.Message; 4 | import fr.enimaloc.jircd.message.Regex; 5 | import fr.enimaloc.jircd.commands.Command; 6 | import fr.enimaloc.jircd.server.ServerSettings; 7 | import fr.enimaloc.jircd.user.User; 8 | import fr.enimaloc.jircd.user.UserState; 9 | 10 | @Command(name = "nick") 11 | public class NickCommand { 12 | 13 | @Command 14 | public void execute(User user, String nickname) { 15 | if (!user.info().passwordValid()) { 16 | return; 17 | } 18 | ServerSettings settings = user.server().settings(); 19 | if (!nickname.matches(Regex.NICKNAME.pattern()) || 20 | (settings.unsafeNickname().contains(nickname) && !settings.safeNet().contains(user.info().host()))) { 21 | user.send(Message.ERR_ERRONEUSNICKNAME.client(user.info()).addFormat("nick", nickname)); 22 | return; 23 | } 24 | if (user.server().users().stream().anyMatch( 25 | u -> u.info().nickname() != null && u.info().nickname().equals(nickname))) { 26 | user.send(Message.ERR_NICKNAMEINUSE.client(user.info()).addFormat("nick", nickname)); 27 | return; 28 | } 29 | if (user.state() == UserState.LOGGED) { 30 | user.server().broadcast(user.info().format() + " NICK " + nickname); 31 | } 32 | user.info().setNickname(nickname); 33 | if (user.info().canRegistrationBeComplete()) { 34 | user.finishRegistration(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/connection/OperCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.connection; 2 | 3 | import fr.enimaloc.jircd.message.Message; 4 | import fr.enimaloc.jircd.server.ServerSettings; 5 | import fr.enimaloc.jircd.commands.Command; 6 | import fr.enimaloc.jircd.user.User; 7 | import java.util.Optional; 8 | 9 | @Command(name = "oper") 10 | public class OperCommand { 11 | 12 | 13 | @Command 14 | public void execute(User user, String name, String password) { 15 | Optional operOptional = user.server().settings().operators().stream().filter( 16 | op -> op.username().equalsIgnoreCase(name)).findFirst(); 17 | if (operOptional.isEmpty()) { 18 | return; 19 | } 20 | ServerSettings.Operator oper = operOptional.get(); 21 | if (!oper.password().equals(password)) { 22 | user.send(Message.ERR_PASSWDMISMATCH.client(user.info())); 23 | return; 24 | } 25 | if (!user.info().host().matches(oper.host().toRegex())) { 26 | user.send(Message.ERR_NOOPERHOST.client(user.info())); 27 | return; 28 | } 29 | user.info().setOper(oper); 30 | user.send(Message.RPL_YOUREOPER.client(user.info())); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/connection/PassCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.connection; 2 | 3 | import fr.enimaloc.jircd.message.Message; 4 | import fr.enimaloc.jircd.commands.Command; 5 | import fr.enimaloc.jircd.user.User; 6 | import fr.enimaloc.jircd.user.UserState; 7 | 8 | @Command(name = "Pass") 9 | public class PassCommand { 10 | 11 | @Command 12 | public void execute(User user, String password) { 13 | if (user.state() == UserState.LOGGED) { 14 | user.send(Message.ERR_ALREADYREGISTERED.client(user.info())); 15 | return; 16 | } 17 | if (!user.server().settings().pass().orElse("").equals(password)) { 18 | user.send(Message.ERR_PASSWDMISMATCH.client(user.info())); 19 | return; 20 | } 21 | user.state(UserState.CONNECTED); 22 | user.info().validPass(); 23 | if (user.info().canRegistrationBeComplete()) { 24 | user.finishRegistration(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/connection/PingCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.connection; 2 | 3 | import fr.enimaloc.jircd.message.Message; 4 | import fr.enimaloc.jircd.commands.Command; 5 | import fr.enimaloc.jircd.user.User; 6 | 7 | @Command(name = "ping") 8 | public class PingCommand { 9 | 10 | @Command 11 | public void execute(User user, String server1) { 12 | user.send("PONG "+server1); 13 | } 14 | 15 | @Command 16 | public void execute(User user, String server1, String server2) { 17 | if (!user.server().settings().host().equals(server2)) { 18 | user.send(Message.ERR_NOSUCHSERVER.client(user.info()).addFormat("", server2)); 19 | return; 20 | } 21 | execute(user, server1); 22 | } 23 | 24 | @Command(trailing = true) 25 | public void executeTrailing(User user, String trailing) { 26 | execute(user, trailing); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/connection/QuitCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.connection; 2 | 3 | import fr.enimaloc.jircd.commands.Command; 4 | import fr.enimaloc.jircd.user.User; 5 | 6 | @Command(name = "quit") 7 | public class QuitCommand { 8 | 9 | @Command 10 | public void execute(User user) { 11 | user.terminate(""); 12 | } 13 | 14 | @Command(trailing = true) 15 | public void execute(User user, String reason) { 16 | user.terminate(reason); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/connection/UserCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.connection; 2 | 3 | import fr.enimaloc.jircd.message.Message; 4 | import fr.enimaloc.jircd.commands.Command; 5 | import fr.enimaloc.jircd.user.User; 6 | import fr.enimaloc.jircd.user.UserState; 7 | 8 | @Command(name = "user") 9 | public class UserCommand { 10 | 11 | @Command 12 | public void executeA(User user, String username, String unused0, String unused1, String realName) { 13 | execute(user, username, unused0, unused1, realName); 14 | } 15 | 16 | @Command(trailing = true) 17 | public void execute(User user, String username, String unused0, String unused1, String realName) { 18 | if (!user.info().passwordValid()) { 19 | return; 20 | } 21 | if (user.state() == UserState.LOGGED) { 22 | user.send(Message.ERR_ALREADYREGISTERED.client(user.info())); 23 | return; 24 | } 25 | user.info().setUsername(username); 26 | user.info().setRealName(realName); 27 | if (user.info().canRegistrationBeComplete()) { 28 | user.finishRegistration(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/messages/Dup.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Dup 3 | * 4 | * 0.0.1 5 | * 6 | * 15/08/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.messages; 9 | 10 | import fr.enimaloc.jircd.channel.Channel; 11 | import fr.enimaloc.jircd.message.Mask; 12 | import fr.enimaloc.jircd.message.Regex; 13 | import fr.enimaloc.jircd.user.User; 14 | import fr.enimaloc.jircd.utils.function.TriConsumer; 15 | 16 | /** 17 | * 18 | */ 19 | public class Dup { 20 | 21 | Dup() { 22 | } 23 | 24 | public static void dup1( 25 | User user, 26 | String targets, 27 | String trailing, 28 | TriConsumer channelFun, 29 | TriConsumer userFun 30 | ) { 31 | for (String target : targets.split(",")) { 32 | if (target.isEmpty()) { 33 | continue; 34 | } 35 | if (Regex.CHANNEL.matcher(target.replaceFirst(Regex.CHANNEL_PREFIX.pattern(), "")).matches()) { 36 | channelFun.accept(user, target, trailing); 37 | } else if (Regex.NICKNAME.matcher(target).matches()) { 38 | userFun.accept(user, target, trailing); 39 | } 40 | } 41 | } 42 | 43 | public static boolean restrained(Channel channel, User user) { 44 | return (channel.modes().bans().stream().anyMatch(mask -> new Mask(mask).toPattern() 45 | .matcher(user.info().full()) 46 | .matches()) && 47 | channel.modes().except().stream().noneMatch(mask -> new Mask(mask).toPattern() 48 | .matcher(user.info().full()) 49 | .matches())) || 50 | (channel.modes().noExternalMessage() && !channel.users().contains(user)) || 51 | (channel.modes().moderate() && !Regex.CHANNEL_PREFIX.matcher( 52 | channel.prefix(user) + user.modes().prefix()).matches()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/messages/NoticeCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.messages; 2 | 3 | import fr.enimaloc.jircd.channel.Channel; 4 | import fr.enimaloc.jircd.message.Mask; 5 | import fr.enimaloc.jircd.message.Message; 6 | import fr.enimaloc.jircd.message.Regex; 7 | import fr.enimaloc.jircd.commands.Command; 8 | import fr.enimaloc.jircd.user.User; 9 | import java.util.Optional; 10 | import java.util.function.Predicate; 11 | 12 | @Command(name = "notice") 13 | public class NoticeCommand { 14 | 15 | @Command 16 | public void execute(User user, String targets, String message) { 17 | executeTrailing(user, targets, message); 18 | } 19 | 20 | @Command(trailing = true) 21 | public void executeTrailing(User user, String targets, String trailing) { 22 | Dup.dup1(user, targets, trailing, this::executeChannel, this::executeUser); 23 | } 24 | 25 | private void executeChannel(User user, String target, String trailing) { 26 | String targetWithoutPrefix = target.replaceFirst(Regex.CHANNEL_PREFIX.pattern(), ""); 27 | String prefix = target.replace(targetWithoutPrefix, ""); 28 | Optional channelOpt = user.server() 29 | .channels() 30 | .stream() 31 | .filter(c -> c.name().equals(targetWithoutPrefix)) 32 | .findFirst(); 33 | if (channelOpt.isEmpty()) { 34 | return; 35 | } 36 | Channel channel = channelOpt.get(); 37 | if (Dup.restrained(channel, user)) { 38 | return; 39 | } 40 | Predicate filter = u -> u != user; 41 | if (!prefix.isEmpty()) { 42 | StringBuilder builder = new StringBuilder("["); 43 | for (char c : prefix.toCharArray()) { 44 | // Switch here do not use break to pass through the labels, 45 | // because if a prefix is read, all the prefixes below will be added. 46 | switch (c) { 47 | case '+': 48 | builder.append("+"); 49 | // fallthrough 50 | case '%': 51 | builder.append("%"); 52 | // fallthrough 53 | case '@': 54 | builder.append("@"); 55 | // fallthrough 56 | case '&': 57 | builder.append("&"); 58 | // fallthrough 59 | case '~': 60 | builder.append("~"); 61 | // fallthrough 62 | default : 63 | break; 64 | } 65 | } 66 | builder.append("]"); 67 | filter = filter.and(u -> (channel.prefix(u) + u.modes().prefix()).matches(builder.toString())); 68 | } 69 | channel.broadcast(":" + user.info().format() + " NOTICE " + target + " :" + trailing, filter); 70 | } 71 | 72 | private void executeUser(User user, String target, String trailing) { 73 | Optional targetOpt = user.server() 74 | .users() 75 | .stream() 76 | .filter(u -> u.info().format().equals(target)) 77 | .findFirst(); 78 | if (targetOpt.isEmpty()) { 79 | return; 80 | } 81 | User targetObj = targetOpt.get(); 82 | if (targetObj.away().isPresent()) { 83 | user.send(Message.RPL_AWAY.client(user.info()) 84 | .addFormat("nick", target) 85 | .addFormat("message", targetObj.away().get())); 86 | } 87 | targetObj.send(":" + user.info().format() + " NOTICE " + target + " :" + trailing); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/messages/PrivmsgCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.messages; 2 | 3 | import fr.enimaloc.jircd.channel.Channel; 4 | import fr.enimaloc.jircd.message.Mask; 5 | import fr.enimaloc.jircd.message.Message; 6 | import fr.enimaloc.jircd.message.Regex; 7 | import fr.enimaloc.jircd.commands.Command; 8 | import fr.enimaloc.jircd.user.User; 9 | import java.util.Optional; 10 | import java.util.function.Predicate; 11 | 12 | @Command(name = "privmsg") 13 | public class PrivmsgCommand { 14 | 15 | @Command 16 | public void execute(User user, String targets, String message) { 17 | executeTrailing(user, targets, message); 18 | } 19 | 20 | @Command(trailing = true) 21 | public void executeTrailing(User user, String targets, String trailing) { 22 | Dup.dup1(user, targets, trailing, this::executeChannel, this::executeUser); 23 | } 24 | 25 | private void executeChannel(User user, String target, String trailing) { 26 | String targetWithoutPrefix = target.replaceFirst(Regex.CHANNEL_PREFIX.pattern(), ""); 27 | String prefix = target.replace(targetWithoutPrefix, ""); 28 | Optional channelOpt = user.server() 29 | .channels() 30 | .stream() 31 | .filter(c -> c.name().equals(targetWithoutPrefix)) 32 | .findFirst(); 33 | if (channelOpt.isEmpty()) { 34 | user.send(Message.ERR_CANNOTSENDTOCHAN.client(user.info()).channel(targetWithoutPrefix)); 35 | return; 36 | } 37 | Channel channel = channelOpt.get(); 38 | if (Dup.restrained(channel, user)) { 39 | user.send(Message.ERR_CANNOTSENDTOCHAN.client(user.info()).channel(targetWithoutPrefix)); 40 | return; 41 | } 42 | Predicate filter = u -> u != user; 43 | if (!prefix.isEmpty()) { 44 | StringBuilder builder = new StringBuilder("["); 45 | for (char c : prefix.toCharArray()) { 46 | // Switch here do not use break to pass through the labels, 47 | // because if a prefix is read all the prefixes below will be added. 48 | switch (c) { 49 | case '+': 50 | builder.append("+"); 51 | // fallthrough 52 | case '%': 53 | builder.append("%"); 54 | // fallthrough 55 | case '@': 56 | builder.append("@"); 57 | // fallthrough 58 | case '&': 59 | builder.append("&"); 60 | // fallthrough 61 | case '~': 62 | builder.append("~"); 63 | // fallthrough 64 | default: 65 | break; 66 | } 67 | } 68 | builder.append("]"); 69 | filter = filter.and(u -> (channel.prefix(u) + u.modes().prefix()).matches(builder.toString())); 70 | } 71 | channel.broadcast(":" + user.info().format() + " PRIVMSG " + target + " :" + trailing, filter); 72 | } 73 | 74 | private void executeUser(User user, String target, String trailing) { 75 | Optional targetOpt = user.server() 76 | .users() 77 | .stream() 78 | .filter(u -> u.info().format().equals(target)) 79 | .findFirst(); 80 | if (targetOpt.isEmpty()) { 81 | user.send(Message.ERR_NOSUCHNICK.client(user.info()).addFormat("nickname", target)); 82 | return; 83 | } 84 | User targetObj = targetOpt.get(); 85 | if (targetObj.away().isPresent()) { 86 | user.send(Message.RPL_AWAY.client(user.info()) 87 | .addFormat("nick", target) 88 | .addFormat("message", targetObj.away().get())); 89 | } 90 | targetObj.send(":" + user.info().format() + " PRIVMSG " + target + " :" + trailing); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/operator/KillCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.operator; 2 | 3 | import fr.enimaloc.jircd.message.Message; 4 | import fr.enimaloc.jircd.commands.Command; 5 | import fr.enimaloc.jircd.user.User; 6 | import java.util.Optional; 7 | 8 | @Command(name = "kill", trailing = true) 9 | public class KillCommand { 10 | 11 | @Command 12 | public void execute(User user, String nickname, String reason) { 13 | if (!user.modes().oper() && !user.modes().localOper()) { 14 | user.send(Message.ERR_NOPRIVILEGES.client(user.info())); 15 | return; 16 | } 17 | Optional userOpt = user.server() 18 | .users() 19 | .stream() 20 | .filter(u -> u.info().format().equals(nickname)) 21 | .findFirst(); 22 | if (userOpt.isEmpty()) { 23 | return; 24 | } 25 | User userObj = userOpt.get(); 26 | String quitReason = "Killed (%s (%s))".formatted(user.info().format(), reason); 27 | userObj.send("Closing Link: " + userObj.server().settings().host() + " (" + quitReason + ")"); 28 | userObj.terminate(quitReason); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/operator/RehashCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * RehashCommand 3 | * 4 | * 0.0.1 5 | * 6 | * 28/07/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.operator; 9 | 10 | import fr.enimaloc.jircd.commands.Command; 11 | import fr.enimaloc.jircd.message.Message; 12 | import fr.enimaloc.jircd.user.User; 13 | import java.nio.file.Path; 14 | 15 | /** 16 | * 17 | */ 18 | @Command(name = "rehash") 19 | public class RehashCommand { 20 | 21 | @Command 22 | public void execute(User user) { 23 | if (!user.modes().oper()) { 24 | user.send(Message.ERR_NOPRIVILEGES.client(user.info())); 25 | return; 26 | } 27 | user.send(Message.RPL_REHASHING.client(user.info()).addFormat("file", "settings.toml")); 28 | user.server().settings().reload(Path.of("settings.toml")); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/operator/RestartCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * RestartCommand 3 | * 4 | * 0.0.1 5 | * 6 | * 29/07/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.operator; 9 | 10 | import fr.enimaloc.jircd.commands.Command; 11 | import fr.enimaloc.jircd.message.Message; 12 | import fr.enimaloc.jircd.server.JIRCD; 13 | import fr.enimaloc.jircd.user.User; 14 | 15 | /** 16 | * 17 | */ 18 | @Command(name = "restart") 19 | public class RestartCommand { 20 | 21 | @Command 22 | public void execute(User user) { 23 | if (!user.modes().oper()) { 24 | user.send(Message.ERR_NOPRIVILEGES.client(user.info())); 25 | return; 26 | } 27 | user.server().shutdown(); 28 | JIRCD.newInstance(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/operator/SQuitCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SQuitCommand 3 | * 4 | * 0.0.1 5 | * 6 | * 29/07/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.operator; 9 | 10 | import fr.enimaloc.jircd.commands.Command; 11 | import fr.enimaloc.jircd.message.Message; 12 | import fr.enimaloc.jircd.user.User; 13 | 14 | /** 15 | * 16 | */ 17 | @Command(name = "squit") 18 | public class SQuitCommand { 19 | 20 | // TODO: 29/07/2022 - Implement connection to server before 21 | @Command(trailing = true) 22 | public void execute(User user, String server, String message) { 23 | if (!user.modes().oper()) { 24 | user.send(Message.ERR_NOPRIVILEGES.client(user.info())); 25 | return; 26 | } 27 | user.send(Message.ERR_NOSUCHSERVER.client(user.info()).addFormat("server name", server)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/optional/AwayCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * AwayCommand 3 | * 4 | * 0.0.1 5 | * 6 | * 29/07/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.optional; 9 | 10 | import fr.enimaloc.jircd.commands.Command; 11 | import fr.enimaloc.jircd.message.Message; 12 | import fr.enimaloc.jircd.user.User; 13 | import java.util.function.Predicate; 14 | 15 | /** 16 | * 17 | */ 18 | @Command(name = "away") 19 | public class AwayCommand { 20 | public static final Predicate AWAY_CAP = u -> u.info().capabilities().contains("away-notify"); 21 | 22 | @Command 23 | public void execute(User user) { 24 | user.channels() 25 | .forEach(channel -> channel.broadcast(user.info().format(), Message.CMD_AWAY, AWAY_CAP)); 26 | user.away(null); 27 | user.send(Message.RPL_UNAWAY.client(user.info())); 28 | } 29 | 30 | @Command(trailing = true) 31 | public void execute(User user, String message) { 32 | user.channels() 33 | .forEach(channel -> channel.broadcast(user.info().format(), Message.CMD_AWAY.trailing(message), AWAY_CAP)); 34 | user.away(message); 35 | user.send(Message.RPL_NOWAWAY.client(user.info())); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/optional/LinksCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * LinkCommand 3 | * 4 | * 0.0.1 5 | * 6 | * 29/07/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.optional; 9 | 10 | import fr.enimaloc.jircd.commands.Command; 11 | import fr.enimaloc.jircd.message.Message; 12 | import fr.enimaloc.jircd.user.User; 13 | 14 | /** 15 | * 16 | */ 17 | @Command(name = "links") 18 | public class LinksCommand { 19 | 20 | @Command 21 | public void execute(User user) { 22 | user.send(Message.RPL_LINKS.client(user.info()) 23 | .addFormat("mask", "*") 24 | .addFormat("server", user.server().settings().host()) 25 | .addFormat("hopcount", "0") 26 | .addFormat("serverinfo", user.server().settings().description())); 27 | user.send(Message.RPL_ENDOFLINKS.client(user.info()) 28 | .addFormat("mask", "*")); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/optional/UserhostCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.optional; 2 | 3 | import fr.enimaloc.jircd.message.Message; 4 | import fr.enimaloc.jircd.commands.Command; 5 | import fr.enimaloc.jircd.user.User; 6 | import java.util.Arrays; 7 | import java.util.Objects; 8 | import java.util.Optional; 9 | 10 | @Command(name = "userhost") 11 | public class UserhostCommand { 12 | 13 | @Command 14 | public void execute(User user, String a) { 15 | execute(user, a, null); 16 | } 17 | 18 | @Command 19 | public void execute(User user, String a, String b) { 20 | execute(user, a, b, null); 21 | } 22 | 23 | @Command 24 | public void execute(User user, String a, String b, String c) { 25 | execute(user, a, b, c, null); 26 | } 27 | 28 | @Command 29 | public void execute(User user, String a, String b, String c, String d) { 30 | execute(user, a, b, c, d, null); 31 | } 32 | 33 | @Command 34 | public void execute(User user, String a, String b, String c, String d, String e) { 35 | StringBuilder ret = new StringBuilder(); 36 | for (String nickname : Arrays.stream(new String[]{a, b, c, d, e}).filter(Objects::nonNull).toArray(String[]::new)) { 37 | Optional userOpt = user.server() 38 | .users() 39 | .stream() 40 | .filter(u -> u.info().format().equals(nickname)) 41 | .findFirst(); 42 | if (userOpt.isEmpty()) { 43 | continue; 44 | } 45 | User u = userOpt.get(); 46 | ret.append(u.info().nickname()) 47 | .append(u.modes().oper() ? "*" : "") 48 | .append(u.away().isPresent() ? "-" : "+") 49 | .append(u.info().host()) 50 | .append(" "); 51 | } 52 | user.send(Message.RPL_USERHOST.client(user.info()).addFormat("reply", ret.toString().trim())); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/optional/WallOpsCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * WallOpsCommand 3 | * 4 | * 0.0.1 5 | * 6 | * 29/07/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.optional; 9 | 10 | import fr.enimaloc.jircd.commands.Command; 11 | import fr.enimaloc.jircd.message.Message; 12 | import fr.enimaloc.jircd.user.User; 13 | 14 | /** 15 | * 16 | */ 17 | @Command(name = "wallops") 18 | public class WallOpsCommand { 19 | 20 | @Command(trailing = true) 21 | public void execute(User user, String message) { 22 | if (!user.modes().oper()) { 23 | user.send(Message.ERR_NOPRIVILEGES); 24 | return; 25 | } 26 | user.server().broadcast(user.info().format(), 27 | Message.CMD_WALLOPS.rawFormat(message), 28 | u -> u.modes().wallops() || u.equals(user)); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/server/AdminCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.server; 2 | 3 | import fr.enimaloc.jircd.server.JIRCD; 4 | import fr.enimaloc.jircd.message.Message; 5 | import fr.enimaloc.jircd.commands.Command; 6 | import fr.enimaloc.jircd.user.User; 7 | import java.util.Optional; 8 | import java.util.regex.Pattern; 9 | 10 | @Command(name = "admin") 11 | public class AdminCommand { 12 | 13 | @Command 14 | public void execute(User user) { 15 | execute(user, user.server().settings().host()); 16 | } 17 | 18 | @Command 19 | public void execute(User user, String target) { 20 | Pattern compile = Pattern.compile(target); 21 | JIRCD server; 22 | Optional subject = user.server() 23 | .users() 24 | .stream() 25 | .filter(u -> compile.matcher(u.info().format()).matches()) 26 | .findFirst(); 27 | if (compile.matcher(user.server().settings().host()).matches()) { 28 | server = user.server(); 29 | } else if (subject.isPresent()) { 30 | server = subject.get().server(); 31 | } else { 32 | user.send(Message.ERR_NOSUCHSERVER.client(user.info()).addFormat("server name", target)); 33 | return; 34 | } 35 | 36 | user.send(Message.RPL_ADMINME.client(user.info()).addFormat("server", server.settings().host())); 37 | user.send(Message.RPL_ADMINLOC1.client(user.info()).addFormat("info", server.settings().admin().loc1())); 38 | user.send(Message.RPL_ADMINLOC2.client(user.info()).addFormat("info", server.settings().admin().loc2())); 39 | user.send(Message.RPL_ADMINEMAIL.client(user.info()).addFormat("info", server.settings().admin().email())); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/server/ConnectCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.server; 2 | 3 | import fr.enimaloc.jircd.message.Message; 4 | import fr.enimaloc.jircd.commands.Command; 5 | import fr.enimaloc.jircd.user.User; 6 | 7 | @Command(name = "connect") 8 | public class ConnectCommand { 9 | 10 | @Command 11 | public void execute(User user, String target) { 12 | execute(user, target, ""); 13 | } 14 | 15 | @Command 16 | public void execute(User user, String target, String port) { 17 | execute(user, target, port, ""); 18 | } 19 | 20 | @Command 21 | public void execute(User user, String target, String port, String remote) { 22 | user.send(Message.ERR_UNKNOWNERROR.client(user.info()) 23 | .addFormat("command", "CONNECT") 24 | .addFormat("info", "Not supported yet")); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/server/HelpCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * HelpCommand 3 | * 4 | * 0.0.1 5 | * 6 | * 19/07/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.server; 9 | 10 | import fr.enimaloc.jircd.commands.Command; 11 | import fr.enimaloc.jircd.message.Message; 12 | import fr.enimaloc.jircd.user.User; 13 | 14 | /** 15 | * 16 | */ 17 | @Command(name = "help") 18 | public class HelpCommand { 19 | 20 | @Command 21 | public void execute(User user) { 22 | user.send(Message.ERR_HELPNOTFOUND.client(user.info()) 23 | .addFormat("subject", "\0") 24 | .format(user.server().settings().host()) 25 | .replace("\0 ", "")); 26 | } 27 | 28 | @Command 29 | public void execute(User user, String subject) { 30 | user.send(Message.ERR_HELPNOTFOUND.client(user.info()).addFormat("subject", subject)); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/server/InfoCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.server; 2 | 3 | import fr.enimaloc.jircd.server.JIRCD; 4 | import fr.enimaloc.jircd.message.Message; 5 | import fr.enimaloc.jircd.commands.Command; 6 | import fr.enimaloc.jircd.user.User; 7 | import java.util.Optional; 8 | import java.util.regex.Pattern; 9 | 10 | @Command(name = "info") 11 | public class InfoCommand { 12 | 13 | @Command 14 | public void execute(User user) { 15 | execute(user, user.server().settings().host()); 16 | } 17 | 18 | @Command 19 | public void execute(User user, String target) { 20 | Pattern compile = Pattern.compile(target); 21 | JIRCD server; 22 | Optional subject = user.server() 23 | .users() 24 | .stream() 25 | .filter(u -> compile.matcher(u.info().format()).matches()) 26 | .findFirst(); 27 | if (compile.matcher(user.server().settings().host()).matches()) { 28 | server = user.server(); 29 | } else if (subject.isPresent()) { 30 | server = subject.get().server(); 31 | } else { 32 | user.send(Message.ERR_NOSUCHSERVER.client(user.info()).addFormat("server name", target)); 33 | return; 34 | } 35 | 36 | for (String info : server.info()) { 37 | user.send(Message.RPL_INFO.addFormat("string", info)); 38 | } 39 | user.send(Message.RPL_ENDOFINFO); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/server/LUserCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * LUserCommand 3 | * 4 | * 0.0.1 5 | * 6 | * 19/07/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.server; 9 | 10 | import fr.enimaloc.jircd.commands.Command; 11 | import fr.enimaloc.jircd.message.Message; 12 | import fr.enimaloc.jircd.user.User; 13 | import fr.enimaloc.jircd.user.UserState; 14 | 15 | /** 16 | * 17 | */ 18 | @Command(name = "luser") 19 | public class LUserCommand { 20 | 21 | @Command 22 | public void execute(User user) { 23 | user.send(Message.RPL_LUSERCLIENT.client(user.info()) 24 | .addFormat("u", user.server() 25 | .users() 26 | .stream() 27 | .filter(u -> u.state() == UserState.LOGGED) 28 | .filter(u -> !u.modes().invisible()) 29 | .count()) 30 | .addFormat("i", user.server() 31 | .users() 32 | .stream() 33 | .filter(u -> u.state() == UserState.LOGGED) 34 | .filter(u -> u.modes().invisible()) 35 | .count()) 36 | .addFormat("s", 1)); 37 | user.send(Message.RPL_LUSEROP.client(user.info()) 38 | .addFormat("ops", user.server() 39 | .users() 40 | .stream() 41 | .filter(u -> u.state() == UserState.LOGGED) 42 | .filter(u -> u.modes().oper()) 43 | .count())); 44 | user.send(Message.RPL_LUSERUNKNOWN.client(user.info()) 45 | .addFormat("connections", user.server() 46 | .users() 47 | .stream() 48 | .filter(u -> u.state() == 49 | UserState.REGISTRATION) 50 | .count())); 51 | user.send(Message.RPL_LUSERCHANNELS.client(user.info()) 52 | .addFormat("channels", user.server() 53 | .channels() 54 | .size())); 55 | user.send(Message.RPL_LUSERME.client(user.info()) 56 | .addFormat("u", user.server().users().size()) 57 | .addFormat("s", 1)); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/server/MotdCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.server; 2 | 3 | import fr.enimaloc.jircd.message.Message; 4 | import fr.enimaloc.jircd.commands.Command; 5 | import fr.enimaloc.jircd.user.User; 6 | 7 | @Command(name = "motd") 8 | public class MotdCommand { 9 | 10 | @Command 11 | public void execute(User user) { 12 | execute(user, user.server().settings().host()); 13 | } 14 | 15 | @Command 16 | public void execute(User user, String server) { 17 | if (!user.server().settings().host().equals(server)) { 18 | user.send(Message.ERR_NOSUCHSERVER.client(user.info()).addFormat("", server)); 19 | return; 20 | } 21 | String[] motd = user.server().settings().motd(); 22 | if (motd.length == 0) { 23 | user.send(Message.ERR_NOMOTD.client(user.info())); 24 | return; 25 | } 26 | user.send(Message.RPL_MOTDSTART.client(user.info()).addFormat("server", server)); 27 | for (String line : motd) { 28 | user.send(Message.RPL_MOTD.client(user.info()).addFormat("line of the motd", line)); 29 | } 30 | user.send(Message.RPL_ENDOFMOTD.client(user.info())); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/server/StatsCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.server; 2 | 3 | import fr.enimaloc.jircd.server.JIRCD; 4 | import fr.enimaloc.jircd.message.Message; 5 | import fr.enimaloc.jircd.commands.Command; 6 | import fr.enimaloc.jircd.user.User; 7 | import java.time.ZonedDateTime; 8 | import java.time.temporal.ChronoUnit; 9 | 10 | @Command(name = "stats") 11 | public class StatsCommand { 12 | 13 | @Command 14 | public void execute(User user, String query) { 15 | execute(user, query, user.server().settings().host()); 16 | } 17 | 18 | @Command 19 | public void execute(User user, String queryS, String server) { 20 | JIRCD serverObj = user.server(); 21 | if (!user.server().settings().host().equals(server)) { 22 | user.send(Message.ERR_NOSUCHSERVER.client(user.info()).addFormat("", server)); 23 | return; 24 | } 25 | switch (Character.toLowerCase(queryS.toCharArray()[0])) { 26 | case 'c' -> user.send(Message.RPL_STATSCLINE.client(user.info())); 27 | case 'h' -> user.send(Message.RPL_STATSHLINE.client(user.info())); 28 | case 'i' -> user.send(Message.RPL_STATSILINE.client(user.info())); 29 | case 'k' -> user.send(Message.RPL_STATSKLINE.client(user.info())); 30 | case 'l' -> user.send(Message.RPL_STATSLLINE.client(user.info())); 31 | case 'm' -> serverObj.commandUsage() 32 | .forEach((command, usage) -> user.send( 33 | Message.RPL_STATSCOMMANDS.addFormat("command", command) 34 | .addFormat("count", usage) 35 | )); 36 | case 'o' -> user.send(Message.RPL_STATSOLINE.client(user.info())); 37 | case 'u' -> user.send(Message.RPL_STATSUPTIME.rawFormat( 38 | ChronoUnit.DAYS.between(serverObj.createdAt().toInstant(), ZonedDateTime.now()), 39 | ChronoUnit.HOURS.between(serverObj.createdAt().toInstant(), ZonedDateTime.now())%24, 40 | ChronoUnit.MINUTES.between(serverObj.createdAt().toInstant(), ZonedDateTime.now())%60, 41 | ChronoUnit.SECONDS.between(serverObj.createdAt().toInstant(), ZonedDateTime.now())%60 42 | )); 43 | case 'y' -> user.send(Message.RPL_STATSLINKLINE.client(user.info())); 44 | default -> noOp(); 45 | } 46 | user.send(Message.RPL_ENDOFSTATS.addFormat("stats letter", Character.toUpperCase(queryS.toCharArray()[0]))); 47 | } 48 | 49 | private void noOp() { 50 | // Nothing to do 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/server/TimeCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.server; 2 | 3 | import fr.enimaloc.jircd.message.Message; 4 | import fr.enimaloc.jircd.commands.Command; 5 | import fr.enimaloc.jircd.user.User; 6 | import java.time.ZonedDateTime; 7 | import java.time.format.DateTimeFormatter; 8 | import java.util.Locale; 9 | 10 | @Command(name = "time") 11 | public class TimeCommand { 12 | 13 | @Command 14 | public void execute(User user) { 15 | execute(user, user.server().settings().host()); 16 | } 17 | 18 | @Command 19 | public void execute(User user, String server) { 20 | if (!user.server().settings().host().equals(server)) { 21 | user.send(Message.ERR_NOSUCHSERVER.client(user.info()).addFormat("server name", server)); 22 | return; 23 | } 24 | user.send(Message.RPL_TIME.addFormat("server", server) 25 | .addFormat("string showing server's local time", 26 | DateTimeFormatter.ofPattern("EEEE LLLL dd yyyy - HH:mm O", Locale.ENGLISH) 27 | .format(ZonedDateTime.now()))); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/server/VersionCommand.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.server; 2 | 3 | import fr.enimaloc.jircd.Constant; 4 | import fr.enimaloc.jircd.server.JIRCD; 5 | import fr.enimaloc.jircd.message.Message; 6 | import fr.enimaloc.jircd.commands.Command; 7 | import fr.enimaloc.jircd.user.User; 8 | import java.util.*; 9 | import java.util.regex.Pattern; 10 | 11 | @Command(name = "version") 12 | public class VersionCommand { 13 | 14 | public static void sendISUPPORT(User user) { 15 | sendISUPPORT(user, user.server()); 16 | } 17 | 18 | public static void sendISUPPORT(User user, JIRCD server) { 19 | List> tokens = server.supportAttribute() 20 | .asMapsWithLimit(13, 21 | (key, value) -> { 22 | if (value == null) { 23 | return false; 24 | } 25 | if (value instanceof Boolean) { 26 | return (boolean) value; 27 | } 28 | if (value instanceof Character) { 29 | return (char) value != '\u0000'; 30 | } 31 | return true; 32 | }); 33 | for (Map token : tokens) { 34 | StringBuilder builder = new StringBuilder(); 35 | if (token.isEmpty()) { 36 | continue; 37 | } 38 | token.forEach((s, o) -> builder.append(s.toUpperCase(Locale.ROOT)) 39 | .append(parseOptional(o)) 40 | .append(" ")); 41 | user.send(Message.RPL_ISUPPORT.client(user.info()) 42 | .addFormat("tokens", builder.deleteCharAt(builder.length() - 1))); 43 | } 44 | } 45 | 46 | private static String parseOptional(Object potentialOptional) { 47 | if (potentialOptional instanceof Optional) { 48 | return parseOptional0((Optional) potentialOptional); 49 | } else if (potentialOptional instanceof OptionalInt optInt) { 50 | return parseOptional0(optInt); 51 | } 52 | return "=" + potentialOptional; 53 | } 54 | 55 | private static String parseOptional0(@SuppressWarnings("OptionalUsedAsFieldOrParameterType") Optional optional) { 56 | return optional.map(o -> "=" + o).orElse(""); 57 | } 58 | 59 | private static String parseOptional0(@SuppressWarnings("OptionalUsedAsFieldOrParameterType") OptionalInt optional) { 60 | return (optional.isPresent() ? "=" + optional.getAsInt() : ""); 61 | } 62 | 63 | @Command 64 | public void execute(User user) { 65 | execute(user, user.server().settings().host()); 66 | } 67 | 68 | @Command 69 | public void execute(User user, String target) { 70 | Pattern compile = Pattern.compile(target); 71 | JIRCD server; 72 | Optional subject = user.server() 73 | .users() 74 | .stream() 75 | .filter(u -> compile.matcher(u.info().format()).matches()) 76 | .findFirst(); 77 | if (compile.matcher(user.server().settings().host()).matches()) { 78 | server = user.server(); 79 | } else if (subject.isPresent()) { 80 | server = subject.get().server(); 81 | } else { 82 | user.send(Message.ERR_NOSUCHSERVER.client(user.info()).addFormat("", target)); 83 | return; 84 | } 85 | 86 | user.send(Message.RPL_VERSION.client(user.info()) 87 | .addFormat("version", Constant.VERSION) 88 | .addFormat("server", server.settings().host()) 89 | .addFormat("comments", "")); 90 | sendISUPPORT(user, server); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/user/WhoCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * WhoCommand 3 | * 4 | * 0.0.1 5 | * 6 | * 19/07/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.user; 9 | 10 | import fr.enimaloc.jircd.channel.Channel; 11 | import fr.enimaloc.jircd.commands.Command; 12 | import fr.enimaloc.jircd.message.Mask; 13 | import fr.enimaloc.jircd.message.Message; 14 | import fr.enimaloc.jircd.server.JIRCD; 15 | import fr.enimaloc.jircd.user.User; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Optional; 19 | 20 | /** 21 | * 22 | */ 23 | @Command(name = "who") 24 | public class WhoCommand { 25 | 26 | @Command 27 | public void execute(User user, String mask) { 28 | JIRCD server = user.server(); 29 | Optional userOpt = server.users() 30 | .stream() 31 | .filter(u -> mask.equals(u.info().nickname())) 32 | .findFirst(); 33 | Optional channelOpt = server.channels() 34 | .stream() 35 | .filter(c -> c.name().equals(mask)) 36 | .findFirst(); 37 | 38 | List targets = 39 | channelOpt.map(Channel::users) 40 | .map(ArrayList::new) 41 | .orElse(userOpt.map(List::of) 42 | .map(ArrayList::new) 43 | .orElse(new ArrayList<>(server.users() 44 | .stream() 45 | .filter(u -> u.info() 46 | .nickname() 47 | .matches(new Mask( 48 | mask).toRegex())) 49 | .toList()))); 50 | 51 | for (User target : targets) { 52 | user.send(Message.RPL_WHOREPLY.client(user.info()) 53 | .channel(target.channels() 54 | .stream() 55 | .findFirst() 56 | .map(Channel::name) 57 | .orElse("*")) 58 | .addFormat("username", target.info().username()) 59 | .addFormat("host", target.info().host().replaceFirst(":", "0:")) 60 | .addFormat("server", server.settings().host()) 61 | .addFormat("nick", target.info().nickname()) 62 | .addFormat("flags", target.away() 63 | .map(away -> "G") 64 | .orElse("H") 65 | + (target.modes().oper() ? "*" : "") 66 | + (target.channels() 67 | .stream() 68 | .findFirst() 69 | .map(channel -> channel.prefix(target)) 70 | .orElse(""))) 71 | .addFormat("hopcount", 0) 72 | .addFormat("realname", target.info().realName())); 73 | 74 | } 75 | 76 | user.send(Message.RPL_ENDOFWHO.client(user.info()).addFormat("mask", mask)); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/user/WhoisCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * WhoisCommand 3 | * 4 | * 0.0.1 5 | * 6 | * 19/07/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.user; 9 | 10 | import fr.enimaloc.jircd.channel.Channel; 11 | import fr.enimaloc.jircd.commands.Command; 12 | import fr.enimaloc.jircd.message.Message; 13 | import fr.enimaloc.jircd.user.User; 14 | import java.util.stream.Collectors; 15 | 16 | /** 17 | * 18 | */ 19 | @Command(name = "whois") 20 | public class WhoisCommand { 21 | 22 | @Command 23 | public void execute(User user, String nick) { 24 | User target = user.server().users() 25 | .stream() 26 | .filter(u -> u.info().nickname().equals(nick)) 27 | .findFirst() 28 | .orElse(null); 29 | if (target == null) { 30 | user.send(Message.ERR_NOSUCHNICK.client(user.info()) 31 | .addFormat("nickname", nick)); 32 | return; 33 | } 34 | target.away() 35 | .map(away -> Message.RPL_AWAY.addFormat("message", away)) 36 | .map(msg -> msg.addFormat("nick", nick)) 37 | .map(msg -> msg.client(user.info())) 38 | .ifPresent(user::send); 39 | // TODO: 23/07/2022 implement certificate tls before 40 | // if (target.modes().oper() || target.equals(user)) { 41 | // user.send(Message.RPL_WHOISCERTFP.client(user.info()) 42 | // .addFormat("nick", target.info().nickname()) 43 | // .addFormat("fingerprint", target.info().fingerprint())); 44 | // } 45 | 46 | if (target.modes().registered()) { 47 | user.send(Message.RPL_WHOISREGNICK.client(user.info()) 48 | .addFormat("nick", target.info().nickname())); 49 | } 50 | user.send(Message.RPL_WHOISUSER.client(user.info()) 51 | .addFormat("nick", target.info().nickname()) 52 | .addFormat("username", target.info().username()) 53 | .addFormat("host", target.info().host().replaceFirst(":", "0:")) 54 | .addFormat("realname", target.info().realName())); 55 | user.send(Message.RPL_WHOISSERVER.client(user.info()) 56 | .addFormat("nick", target.info().nickname()) 57 | .addFormat("server", target.server().settings().host()) 58 | .addFormat("serverinfo", target.server().settings().description())); 59 | if (target.modes().oper()) { 60 | user.send(Message.RPL_WHOISOPERATOR.client(user.info()) 61 | .addFormat("nick", target.info().nickname())); 62 | } 63 | user.send(Message.RPL_WHOISIDLE.client(user.info()) 64 | .addFormat("nick", target.info().nickname()) 65 | .addFormat("secs", (System.currentTimeMillis() - target.lastActivity()) / 1000) 66 | .addFormat("signon", target.info().joinedAt())); 67 | user.send(Message.RPL_WHOISCHANNELS.client(user.info()) 68 | .addFormat("nick", target.info().nickname()) 69 | .addFormat("channels", target.channels().stream() 70 | .map(Channel::name) 71 | .collect(Collectors.joining(" ")))); 72 | // TODO: 20/07/2022 What's that? 73 | // user.send(Message.RPL_WHOISSPECIAL.client(user.info()) 74 | // .addFormat("nick", target.info().nickname()) 75 | // .addFormat("special", "")); 76 | // TODO: 20/07/2022 What's condition for reply this message? 77 | // user.send(Message.RPL_WHOISACCOUNT.client(user.info()) 78 | // .addFormat("nick", target.info().nickname()) 79 | // .addFormat("account", "")); 80 | // TODO: 20/07/2022 See todo of RPL_WHOISACTUALLY 81 | // user.send(Message.RPL_WHOISACTUALLY.client(user.info()) 82 | // .addFormat("nick", target.info().nickname()) 83 | // .addFormat("actually", "")); 84 | user.send(Message.RPL_WHOISHOST.client(user.info()) 85 | .addFormat("nick", target.info().nickname()) 86 | .addFormat("host", target.info().host())); 87 | user.send(Message.RPL_WHOISMODES.client(user.info()) 88 | .addFormat("nick", target.info().nickname()) 89 | .addFormat("modes", "+" + target.modes().toString())); 90 | if (target.info().secure()) { 91 | user.send(Message.RPL_WHOISSECURE.client(user.info()) 92 | .addFormat("nick", target.info().nickname())); 93 | } 94 | 95 | 96 | user.send(Message.RPL_ENDOFWHOIS.client(user.info()) 97 | .addFormat("nick", target.info().nickname())); 98 | } 99 | 100 | // TODO: 20/07/2022 - need to understand how to implement this command and specially target parameter, 101 | // this command return ENDOFWHOIS for the moment 102 | @Command 103 | public void execute(User user, String target, String nick) { 104 | user.send(Message.RPL_ENDOFWHOIS.client(user.info()) 105 | .addFormat("nick", nick)); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/commands/user/WhowasCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * WhowasCommand 3 | * 4 | * 0.0.1 5 | * 6 | * 19/07/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.user; 9 | 10 | import fr.enimaloc.jircd.commands.Command; 11 | import fr.enimaloc.jircd.message.Message; 12 | import fr.enimaloc.jircd.user.User; 13 | 14 | /** 15 | * 16 | */ 17 | @Command(name = "whowas") 18 | public class WhowasCommand { 19 | 20 | @Command 21 | public void execute(User user, String nick) { 22 | execute(user, nick, "0"); 23 | } 24 | 25 | // todo: 26/07/2022 implement required structure(name history) for command 26 | @Command 27 | public void execute(User user, String nick, String count) { 28 | user.send(Message.ERR_WASNOSUCHNICK.client(user.info())); 29 | user.send(Message.RPL_ENDOFWHOWAS.client(user.info()) 30 | .addFormat("nick", nick)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/message/Mask.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.message; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public record Mask(String mask) { 6 | 7 | public String toRegex() { 8 | StringBuilder ret = new StringBuilder(); 9 | for (int i = 0; i < mask.length(); i++) { 10 | char c = mask.charAt(i); 11 | switch (c) { 12 | case '\\' -> ret.append(Pattern.quote(mask.charAt(++i)+"")); 13 | case '?' -> ret.append("."); 14 | case '*' -> ret.append(".*"); 15 | default -> ret.append(Pattern.quote(c+"")); 16 | } 17 | } 18 | return ret.toString(); 19 | } 20 | 21 | public Pattern toPattern() { 22 | return Pattern.compile(toRegex()); 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return mask; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/message/Regex.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.message; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public class Regex { 6 | 7 | Regex() {} 8 | 9 | public static final Pattern NICKNAME = Pattern.compile("[a-zA-Z][a-zA-Z0-9\\-_]{0,15}"); 10 | public static final Pattern CHANNEL = Pattern.compile("&|#[a-zA-Z0-9\\-_]{1,49}"); 11 | public static final Pattern CHANNEL_PREFIX = Pattern.compile("[~&@%+]"); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/server/attributes/Attribute.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.server.attributes; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Modifier; 5 | import java.util.*; 6 | import java.util.function.BiPredicate; 7 | import org.slf4j.LoggerFactory; 8 | 9 | public class Attribute { 10 | 11 | public List> asMapsWithLimit(int limit) { 12 | return asMapsWithLimit(limit, null); 13 | } 14 | 15 | public List> asMapsWithLimit(int limit, BiPredicate filter) { 16 | List> ret = new ArrayList<>(); 17 | if (asMap(filter).keySet().size() > limit) { 18 | Map actual = new HashMap<>(); 19 | for (int i = 0; i < asMap(filter).keySet().size(); i++) { 20 | if (i % limit == 0) { 21 | ret.add(actual); 22 | actual = new HashMap<>(); 23 | } 24 | String key = asMap(filter).keySet().toArray(String[]::new)[i]; 25 | actual.put(key, asMap(filter).get(key)); 26 | } 27 | ret.add(actual); 28 | } else { 29 | ret.add(asMap(filter)); 30 | } 31 | return ret; 32 | } 33 | 34 | public Map asMap() { 35 | return asMap(null); 36 | } 37 | 38 | public Map asMap(BiPredicate filter) { 39 | Map ret = new HashMap<>(); 40 | for (Field declaredField : Arrays.stream(this.getClass().getDeclaredFields()) 41 | .filter(f -> !Modifier.isTransient(f.getModifiers())) 42 | .toArray(Field[]::new)) { 43 | try { 44 | if (filter != null && filter.test(declaredField.getName(), declaredField.get(this))) { 45 | ret.put(declaredField.getName(), declaredField.get(this)); 46 | } 47 | } catch (IllegalAccessException e) { 48 | LoggerFactory.getLogger(Attribute.class).error(e.getLocalizedMessage(), e); 49 | } 50 | } 51 | return ret; 52 | } 53 | 54 | public int length() { 55 | return asMap((s, o) -> o != null).size(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/server/attributes/ChannelAttribute.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.server.attributes; 2 | 3 | import java.util.Arrays; 4 | import java.util.Optional; 5 | 6 | public class ChannelAttribute extends Attribute { 7 | String chanLimit; 8 | char[] chanModes; 9 | int channelLen; 10 | String chanTypes; 11 | char excepts; 12 | String extBan; 13 | char invEx; 14 | int kickLen; 15 | int topicLen; 16 | String prefix; 17 | String statusMsg; 18 | 19 | public ChannelAttribute( 20 | String chanLimit, char[] chanModes, int channelLen, String chanTypes, char excepts, String extBan, 21 | char invEx, 22 | int kickLen, 23 | int topicLen, 24 | String prefix, 25 | String statusMsg 26 | ) { 27 | this.chanLimit = chanLimit; 28 | this.chanModes = chanModes; 29 | this.channelLen = channelLen; 30 | this.chanTypes = chanTypes; 31 | this.excepts = excepts; 32 | this.extBan = extBan; 33 | this.invEx = invEx; 34 | this.kickLen = kickLen; 35 | this.topicLen = topicLen; 36 | this.prefix = prefix; 37 | this.statusMsg = statusMsg; 38 | } 39 | 40 | public void chanLimit(String chanLimit) { 41 | this.chanLimit = chanLimit; 42 | } 43 | 44 | public String chanLimit() { 45 | return chanLimit; 46 | } 47 | 48 | public void chanModes(char... chanModes) { 49 | this.chanModes = chanModes; 50 | } 51 | 52 | public char[] chanModes() { 53 | return chanModes; 54 | } 55 | 56 | public void channelLen(int channelLen) { 57 | this.channelLen = channelLen; 58 | } 59 | 60 | public int channelLen() { 61 | return channelLen; 62 | } 63 | 64 | public void chanTypes(String chanTypes) { 65 | this.chanTypes = chanTypes; 66 | } 67 | 68 | public Optional chanTypes() { 69 | return Optional.ofNullable(chanTypes); 70 | } 71 | 72 | public char excepts() { 73 | return excepts; 74 | } 75 | 76 | public void excepts(char excepts) { 77 | this.excepts = excepts; 78 | } 79 | 80 | public String extBan() { 81 | return extBan; 82 | } 83 | 84 | public void extBan(String extBan) { 85 | this.extBan = extBan; 86 | } 87 | 88 | public char invEx() { 89 | return invEx; 90 | } 91 | 92 | public void invEx(char invEx) { 93 | this.invEx = invEx; 94 | } 95 | 96 | public int kickLen() { 97 | return kickLen; 98 | } 99 | 100 | public void kickLen(int kickLen) { 101 | this.kickLen = kickLen; 102 | } 103 | 104 | public int topicLen() { 105 | return topicLen; 106 | } 107 | 108 | public void topicLen(int topicLen) { 109 | this.topicLen = topicLen; 110 | } 111 | 112 | public Optional prefix() { 113 | return Optional.ofNullable(prefix); 114 | } 115 | 116 | public void prefix(String prefix) { 117 | this.prefix = prefix; 118 | } 119 | 120 | public String statusMsg() { 121 | return statusMsg; 122 | } 123 | 124 | public void statusMsg(String statusMsg) { 125 | this.statusMsg = statusMsg; 126 | } 127 | 128 | @Override 129 | public String toString() { 130 | return "ChannelAttribute{" + 131 | "chanLimit='" + chanLimit + '\'' + 132 | ", chanModes=" + Arrays.toString(chanModes) + 133 | ", channelLen=" + channelLen + 134 | ", chanTypes='" + chanTypes + '\'' + 135 | ", excepts=" + excepts + 136 | ", extBan='" + extBan + '\'' + 137 | ", invEx=" + invEx + 138 | ", kickLen=" + kickLen + 139 | ", topicLen=" + topicLen + 140 | ", prefix='" + prefix + '\'' + 141 | ", statusMsg='" + statusMsg + '\'' + 142 | '}'; 143 | } 144 | } -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/server/attributes/ServerAttribute.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.server.attributes; 2 | 3 | import java.util.Optional; 4 | import java.util.OptionalInt; 5 | 6 | public class ServerAttribute extends Attribute { 7 | String caseMapping; 8 | String eList; 9 | String maxList; 10 | int maxTarget; 11 | int modes; 12 | String network; 13 | boolean safeList; 14 | int silence; 15 | String targMax; 16 | 17 | public ServerAttribute( 18 | String caseMapping, String eList, String maxList, int maxTarget, int modes, String network, 19 | boolean safeList, 20 | int silence, 21 | String targMax 22 | ) { 23 | this.caseMapping = caseMapping; 24 | this.eList = eList; 25 | this.maxList = maxList; 26 | this.maxTarget = maxTarget; 27 | this.modes = modes; 28 | this.network = network; 29 | this.safeList = safeList; 30 | this.silence = silence; 31 | this.targMax = targMax; 32 | } 33 | 34 | public void caseMapping(String caseMapping) { 35 | this.caseMapping = caseMapping; 36 | } 37 | 38 | public String caseMapping() { 39 | return caseMapping; 40 | } 41 | 42 | public void eList(String eList) { 43 | this.eList = eList; 44 | } 45 | 46 | public String eList() { 47 | return eList; 48 | } 49 | 50 | public void maxList(String maxList) { 51 | this.maxList = maxList; 52 | } 53 | 54 | public String maxList() { 55 | return maxList; 56 | } 57 | 58 | public void maxTarget(int maxTarget) { 59 | this.maxTarget = maxTarget; 60 | } 61 | 62 | public OptionalInt maxTarget() { 63 | return maxTarget < 0 ? OptionalInt.empty() : OptionalInt.of(maxTarget); 64 | } 65 | 66 | public void modes(int modes) { 67 | this.modes = modes; 68 | } 69 | 70 | public OptionalInt modes() { 71 | return modes < 0 ? OptionalInt.empty() : OptionalInt.of(modes); 72 | } 73 | 74 | public void network(String network) { 75 | this.network = network; 76 | } 77 | 78 | public String network() { 79 | return network; 80 | } 81 | 82 | public boolean safeList() { 83 | return safeList; 84 | } 85 | 86 | public void safeList(boolean safeList) { 87 | this.safeList = safeList; 88 | } 89 | 90 | public OptionalInt silence() { 91 | return silence < 0 ? OptionalInt.empty() : OptionalInt.of(silence); 92 | } 93 | 94 | public void silence(int silence) { 95 | this.silence = silence; 96 | } 97 | 98 | public void targMax(String targMax) { 99 | this.targMax = targMax; 100 | } 101 | 102 | public Optional targMax() { 103 | return Optional.ofNullable(targMax); 104 | } 105 | 106 | @Override 107 | public String toString() { 108 | return "ServerAttribute{" + 109 | "caseMapping='" + caseMapping + '\'' + 110 | ", eList='" + eList + '\'' + 111 | ", maxList='" + maxList + '\'' + 112 | ", maxTarget=" + maxTarget + 113 | ", modes=" + modes + 114 | ", network='" + network + '\'' + 115 | ", safeList=" + safeList + 116 | ", silence=" + silence + 117 | ", targMax='" + targMax + '\'' + 118 | '}'; 119 | } 120 | } -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/server/attributes/SupportAttribute.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.server.attributes; 2 | 3 | import java.util.*; 4 | import java.util.function.BiPredicate; 5 | 6 | public final class SupportAttribute extends Attribute { 7 | private final ChannelAttribute channelAttribute; 8 | private final UserAttribute userAttribute; 9 | private final ServerAttribute serverAttribute; 10 | 11 | public SupportAttribute( 12 | int awayLen, 13 | String caseMapping, 14 | String chanLimit, 15 | char[] chanModes, 16 | int channelLen, 17 | String chanTypes, 18 | String eList, 19 | char excepts, 20 | String extBan, 21 | int hostLen, 22 | char invEx, 23 | int kickLen, 24 | String maxList, 25 | int maxTarget, 26 | int modes, 27 | String network, 28 | int nickLen, 29 | String prefix, 30 | boolean safeList, 31 | int silence, 32 | String statusMsg, 33 | String targMax, 34 | int topicLen, 35 | int userLen 36 | ) { 37 | this( 38 | new ChannelAttribute(chanLimit, chanModes, channelLen, chanTypes, excepts, extBan, invEx, kickLen, 39 | topicLen, prefix, statusMsg), 40 | new UserAttribute(awayLen, nickLen, userLen, hostLen), 41 | new ServerAttribute(caseMapping, eList, maxList, maxTarget, modes, network, safeList, silence, targMax) 42 | ); 43 | } 44 | 45 | public SupportAttribute( 46 | ChannelAttribute channelAttribute, UserAttribute userAttribute, 47 | ServerAttribute serverAttribute 48 | ) { 49 | this.channelAttribute = channelAttribute; 50 | this.userAttribute = userAttribute; 51 | this.serverAttribute = serverAttribute; 52 | } 53 | 54 | public ChannelAttribute channelAttribute() { 55 | return channelAttribute; 56 | } 57 | 58 | public UserAttribute userAttribute() { 59 | return userAttribute; 60 | } 61 | 62 | public ServerAttribute serverAttribute() { 63 | return serverAttribute; 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return "SupportAttribute{" + 69 | "channelAttribute=" + channelAttribute + 70 | ", userAttribute=" + userAttribute + 71 | ", serverAttribute=" + serverAttribute + 72 | '}'; 73 | } 74 | 75 | @Override 76 | public Map asMap(BiPredicate filter) { 77 | Map map = new HashMap<>(); 78 | map.putAll(serverAttribute.asMap(filter)); 79 | map.putAll(channelAttribute.asMap(filter)); 80 | map.putAll(userAttribute.asMap(filter)); 81 | return map; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/server/attributes/UserAttribute.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.server.attributes; 2 | 3 | public class UserAttribute extends Attribute { 4 | int awayLen; 5 | int nickLen; 6 | int userLen; 7 | int hostLen; 8 | 9 | public UserAttribute(int awayLen, int nickLen, int userLen, int hostLen) { 10 | this.awayLen = awayLen; 11 | this.nickLen = nickLen; 12 | this.userLen = userLen; 13 | this.hostLen = hostLen; 14 | } 15 | 16 | public int awayLen() { 17 | return awayLen; 18 | } 19 | 20 | public void awayLen(int awayLen) { 21 | this.awayLen = awayLen; 22 | } 23 | 24 | public void nickLen(int nickLen) { 25 | this.nickLen = nickLen; 26 | } 27 | 28 | public int nickLen() { 29 | return nickLen; 30 | } 31 | 32 | public void userLen(int userLen) { 33 | this.userLen = userLen; 34 | } 35 | 36 | public int userLen() { 37 | return userLen; 38 | } 39 | 40 | public int hostLen() { 41 | return hostLen; 42 | } 43 | 44 | public void hostLen(int hostLen) { 45 | this.hostLen = hostLen; 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return "UserAttribute{" + 51 | "awayLen=" + awayLen + 52 | ", nickLen=" + nickLen + 53 | ", userLen=" + userLen + 54 | ", hostLen=" + hostLen + 55 | '}'; 56 | } 57 | } -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/user/UserInfo.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.user; 2 | 3 | import fr.enimaloc.jircd.server.ServerSettings; 4 | import java.net.InetAddress; 5 | import java.net.UnknownHostException; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.Set; 9 | 10 | public class UserInfo { 11 | 12 | private final User user; 13 | private final long joinAt = System.currentTimeMillis(); 14 | private final ServerSettings settings; 15 | private String host; 16 | private String username; 17 | private String nickname; 18 | private String realName; 19 | private boolean passwordValid; 20 | private ServerSettings.Operator oper; 21 | private List capabilities; // TODO: 29/07/2022 - Implement CAP command 22 | 23 | public UserInfo(User user, ServerSettings settings) { 24 | this.user = user; 25 | this.passwordValid = settings.pass().isEmpty(); 26 | this.settings = settings; 27 | } 28 | 29 | public String host() { 30 | return host; 31 | } 32 | 33 | public String username() { 34 | return username; 35 | } 36 | 37 | public String nickname() { 38 | return nickname; 39 | } 40 | 41 | public String realName() { 42 | return realName; 43 | } 44 | 45 | public boolean passwordValid() { 46 | return passwordValid; 47 | } 48 | 49 | public boolean canRegistrationBeComplete() { 50 | return hasString(host) && hasString(username) && hasString(nickname) && hasString(realName) && 51 | passwordValid && user.state() != UserState.LOGGED; 52 | } 53 | 54 | public ServerSettings.Operator oper() { 55 | return oper; 56 | } 57 | 58 | public Set capabilities() { 59 | return capabilities == null ? Set.of() : Set.copyOf(capabilities); 60 | } 61 | 62 | public void validPass() { 63 | passwordValid = true; 64 | } 65 | 66 | public void setHost(String host) { 67 | this.host = host; 68 | } 69 | 70 | public void setUsername(String username) { 71 | this.username = username; 72 | } 73 | 74 | public void setNickname(String nickname) { 75 | this.nickname = nickname; 76 | } 77 | 78 | public void setRealName(String realName) { 79 | this.realName = realName; 80 | } 81 | 82 | public void setOper(ServerSettings.Operator oper) { 83 | this.oper = oper; 84 | this.user.modes().oper(oper != null); 85 | } 86 | 87 | public String format() { 88 | return (hasString(nickname) ? nickname : "@" + host); 89 | } 90 | 91 | public String full() { 92 | return nickname + "!" + username + "@" + host; 93 | } 94 | 95 | private boolean hasString(String s) { 96 | return s != null && !s.isEmpty() && !s.isBlank(); 97 | } 98 | 99 | @Override 100 | public boolean equals(Object o) { 101 | if (this == o) { 102 | return true; 103 | } 104 | if (o == null || getClass() != o.getClass()) { 105 | return false; 106 | } 107 | UserInfo info = (UserInfo) o; 108 | return Objects.equals(host, info.host) && Objects.equals(username, info.username) && 109 | Objects.equals(nickname, info.nickname) && Objects.equals(realName, info.realName); 110 | } 111 | 112 | @Override 113 | public int hashCode() { 114 | return Objects.hash(host, username, nickname, realName); 115 | } 116 | 117 | @Override 118 | public String toString() { 119 | return "Info{" + 120 | "host='" + host + '\'' + 121 | ", username='" + username + '\'' + 122 | ", nickname='" + nickname + '\'' + 123 | ", realName='" + realName + '\'' + 124 | '}'; 125 | } 126 | 127 | public long joinedAt() { 128 | return joinAt; 129 | } 130 | 131 | public boolean secure() { 132 | return host().matches(String.join("|", settings.safeNet())) || isFromTor(); 133 | } 134 | 135 | public boolean isFromTor() { 136 | if (!host().matches("^(\\d{1,3}\\.){3}\\d{1,3}$")) { 137 | return false; 138 | } 139 | String[] split = host().split("\\."); 140 | try { 141 | return InetAddress.getByName( 142 | split[3] + "." + split[2] + "." + split[1] + "." + split[0] + ".dnsel.torproject.org") 143 | .getHostAddress() 144 | .equals("127.0.0.2"); 145 | } catch (UnknownHostException e) { 146 | return false; 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/user/UserModes.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.user; 2 | 3 | public class UserModes { 4 | 5 | private boolean invisible; 6 | private boolean oper; 7 | private boolean localOper; 8 | private boolean registered; 9 | private boolean wallops; 10 | 11 | public boolean invisible() { 12 | return invisible; 13 | } 14 | 15 | public UserModes invisible(boolean invisible) { 16 | this.invisible = invisible; 17 | return this; 18 | } 19 | 20 | public boolean oper() { 21 | return oper; 22 | } 23 | 24 | public UserModes oper(boolean oper) { 25 | this.oper = oper; 26 | return this; 27 | } 28 | 29 | public boolean registered() { 30 | return registered; 31 | } 32 | 33 | public UserModes registered(boolean registered) { 34 | this.registered = registered; 35 | return this; 36 | } 37 | 38 | public boolean localOper() { 39 | return localOper; 40 | } 41 | 42 | public UserModes localOper(boolean localOper) { 43 | this.localOper = localOper; 44 | return this; 45 | } 46 | 47 | public boolean wallops() { 48 | return wallops; 49 | } 50 | 51 | public UserModes wallops(boolean wallops) { 52 | this.wallops = wallops; 53 | return this; 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return (invisible() ? "i" : "") + 59 | (oper() ? "o" : "") + 60 | (localOper() ? "O" : "") + 61 | (registered() ? "r" : "") + 62 | (wallops() ? "w" : ""); 63 | } 64 | 65 | public void apply(String rawMode, Runnable onUnknown) { 66 | boolean add = true; 67 | for (char c : rawMode.toCharArray()) { 68 | switch (c) { 69 | case '+', '-' -> add = c == '+'; 70 | case 'i' -> invisible(add); 71 | case 'o' -> oper(false); 72 | case 'O' -> localOper(false); 73 | // case 'r' -> registered(add); TODO 74 | case 'w' -> wallops(add); 75 | default -> onUnknown.run(); 76 | } 77 | } 78 | } 79 | 80 | public String prefix() { 81 | return (localOper() || oper() ? "@" : ""); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/user/UserState.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.user; 2 | 3 | public enum UserState { 4 | CONNECTED, 5 | REGISTRATION, 6 | LOGGED, 7 | DISCONNECTED 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/fr/enimaloc/jircd/utils/function/TriConsumer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * TriConsumer 3 | * 4 | * 0.0.1 5 | * 6 | * 15/08/2022 7 | */ 8 | package fr.enimaloc.jircd.utils.function; 9 | 10 | import java.util.Objects; 11 | 12 | /** 13 | * 14 | */ 15 | @FunctionalInterface 16 | public interface TriConsumer { 17 | 18 | void accept(T t, U u, V v); 19 | 20 | default TriConsumer andThen(TriConsumer after) { 21 | Objects.requireNonNull(after); 22 | 23 | return (l, c, r) -> { 24 | accept(l, c, r); 25 | after.accept(l, c, r); 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [%d] [%-5level] [%-27thread] [%logger{0}] %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/ServerBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ServerTest 3 | * 4 | * 0.0.1 5 | * 6 | * 05/08/2022 7 | */ 8 | package fr.enimaloc.jircd; 9 | 10 | import fr.enimaloc.enutils.classes.NumberUtils; 11 | import fr.enimaloc.jircd.server.JIRCD; 12 | import fr.enimaloc.jircd.server.ServerSettings; 13 | import java.io.IOException; 14 | import java.lang.reflect.Modifier; 15 | import java.net.BindException; 16 | import java.util.Arrays; 17 | import java.util.Random; 18 | import java.util.concurrent.TimeUnit; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | import static org.junit.jupiter.api.Assertions.fail; 23 | 24 | /** 25 | * 26 | */ 27 | public class ServerBase { 28 | public static final String ENDING = "\r\n"; 29 | public static final int TIMEOUT_WHEN_WAITING_RESPONSE = NumberUtils.getSafe( 30 | System.getenv("TIMEOUT_WHEN_WAITING_RESPONSE"), Integer.class).orElse(500); 31 | 32 | public static final String[] EMPTY_ARRAY = new String[]{"\0"}; 33 | public static final String[] SOCKET_CLOSE = new String[]{null}; 34 | 35 | protected static ServerSettings baseSettings; 36 | protected static int attrLength; 37 | protected JIRCD server; 38 | protected static Logger logger = LoggerFactory.getLogger(ServerBase.class); 39 | 40 | protected ServerSettings.Builder buildSettings() { 41 | return new ServerSettings.Builder() 42 | .motd(new String[0]) 43 | .host("jircd-host") 44 | .networkName("JIRCD") 45 | .pass("jircd-pass") 46 | .pingTimeout(TimeUnit.DAYS.toMillis(1)) 47 | .operators( 48 | new ServerSettings.Operator("oper", "*", "oper"), 49 | new ServerSettings.Operator("googleOper", "google", "pass") 50 | ); 51 | } 52 | 53 | protected void init() { 54 | ServerSettings.Builder builder = buildSettings(); 55 | 56 | ServerSettings tmp = builder.build(); 57 | logger.info("Creating server with settings: {}", tmp); 58 | while (server == null) { 59 | try { 60 | server = new JIRCD(tmp) { 61 | @Override 62 | public void shutdown() { 63 | logger.info("Stopping server..."); 64 | this.isShutdown = true; 65 | this.interrupt(); 66 | try { 67 | serverSocket.close(); 68 | } catch (IOException e) { 69 | logger.warn("Failed to correctly shutdown server", e); 70 | } 71 | } 72 | }; 73 | attrLength = (int) Math.max(Math.ceil(server.supportAttribute().length() / 13.), 1); 74 | baseSettings = server.settings().copy(); 75 | break; 76 | } catch (BindException ignored) { 77 | int newPort = new Random().nextInt(1000) + 1024; 78 | logger.warn("Port {} is currently used, replaced with {}", baseSettings.port(), newPort); 79 | tmp = builder.port(newPort).build(); 80 | } catch (IOException e) { 81 | fail("Can't start IRCServer", e); 82 | break; 83 | } 84 | } 85 | } 86 | 87 | protected void setSettings(ServerSettings settings) { 88 | Arrays.stream(ServerSettings.class.getDeclaredFields()) 89 | .filter(field -> !Modifier.isFinal(field.getModifiers())) 90 | .forEach(field -> { 91 | field.setAccessible(true); 92 | try { 93 | field.set(server.settings(), field.get(settings)); 94 | } catch (IllegalAccessException e) { 95 | logger.error(e.getLocalizedMessage(), e); 96 | } 97 | }); 98 | } 99 | 100 | protected void off() { 101 | server.shutdown(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/channel/ChannelModesTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.channel; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Optional; 5 | import java.util.OptionalInt; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | import static org.junit.jupiter.api.Assumptions.assumeFalse; 10 | 11 | class ChannelModesTest { 12 | 13 | @Test 14 | void password() { 15 | ChannelModes channelModes = new ChannelModes().password("password"); 16 | assertEquals(Optional.of("password"), channelModes.password()); 17 | assertTrue(channelModes.password().isPresent()); 18 | assertEquals("k", channelModes.modesString()); 19 | assertEquals("password", channelModes.modesArguments()); 20 | } 21 | 22 | @Test 23 | void limit() { 24 | ChannelModes channelModes = new ChannelModes().limit(1); 25 | assertEquals(OptionalInt.of(1), channelModes.limit()); 26 | assertTrue(channelModes.limit().isPresent()); 27 | assertEquals("l", channelModes.modesString()); 28 | assertEquals("1", channelModes.modesArguments()); 29 | } 30 | 31 | @Test 32 | void inviteOnly() { 33 | ChannelModes channelModes = new ChannelModes().inviteOnly(true); 34 | assertTrue(channelModes.inviteOnly()); 35 | assertEquals("i", channelModes.modesString()); 36 | assertEquals("", channelModes.modesArguments()); 37 | } 38 | 39 | @Test 40 | void secret() { 41 | ChannelModes channelModes = new ChannelModes().secret(true); 42 | assertTrue(channelModes.secret()); 43 | assertEquals("s", channelModes.modesString()); 44 | assertEquals("", channelModes.modesArguments()); 45 | } 46 | 47 | @Test 48 | void bans() { 49 | ChannelModes channelModes = new ChannelModes(); 50 | channelModes.bans().add("b!an@ed"); 51 | assertEquals("b", channelModes.modesString()); 52 | assertEquals("", channelModes.modesArguments()); 53 | } 54 | 55 | @Test 56 | void moderate() { 57 | ChannelModes channelModes = new ChannelModes().moderate(true); 58 | assertTrue(channelModes.moderate()); 59 | assertEquals("m", channelModes.modesString()); 60 | assertEquals("", channelModes.modesArguments()); 61 | } 62 | 63 | @Test 64 | void protected0() { 65 | ChannelModes channelModes = new ChannelModes().protected0(true); 66 | assertTrue(channelModes.protected0()); 67 | assertEquals("t", channelModes.modesString()); 68 | assertEquals("", channelModes.modesArguments()); 69 | } 70 | 71 | @Test 72 | void noExternalMessage() { 73 | ChannelModes channelModes = new ChannelModes().noExternalMessage(true); 74 | assertTrue(channelModes.noExternalMessage()); 75 | assertEquals("n", channelModes.modesString()); 76 | assertEquals("", channelModes.modesArguments()); 77 | } 78 | 79 | @Test 80 | void except() { 81 | ChannelModes channelModes = new ChannelModes(); 82 | channelModes.except().add("e!xce@pt"); 83 | assertEquals("e", channelModes.modesString()); 84 | assertEquals("", channelModes.modesArguments()); 85 | } 86 | 87 | @Test 88 | void invEx() { 89 | ChannelModes channelModes = new ChannelModes(); 90 | channelModes.invEx().add("e!xem@pted"); 91 | assertEquals("I", channelModes.modesString()); 92 | assertEquals("", channelModes.modesArguments()); 93 | } 94 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/channel/CommandChannelBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * CommandChanneltest 3 | * 4 | * 0.0.1 5 | * 6 | * 05/08/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.channel; 9 | 10 | import fr.enimaloc.jircd.SocketBase; 11 | 12 | /** 13 | * 14 | */ 15 | public class CommandChannelBase extends SocketBase { 16 | @Override 17 | protected void init() { 18 | super.init(); 19 | connections[0].createUser("bob", "bobby", "Mobbye Plav"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/channel/NamesCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.channel; 2 | 3 | import fr.enimaloc.jircd.channel.Channel; 4 | import java.util.Optional; 5 | import org.junit.jupiter.api.AfterEach; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import utils.CommandClazzTest; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 12 | 13 | @CommandClazzTest 14 | class NamesCommandTest extends CommandChannelBase { 15 | 16 | @BeforeEach 17 | void setUp() { 18 | init(); 19 | } 20 | 21 | @Test 22 | void namesTest() { 23 | addConnections(1); 24 | connections[0].send("JOIN #names"); 25 | connections[0].ignoreMessage(4); 26 | 27 | connections[1].createUser("john", "John Doe"); 28 | connections[1].send("NAMES #names"); 29 | assertArrayEquals(new String[]{ 30 | ":jircd-host 353 john = #names :~bob", 31 | ":jircd-host 366 john #names :End of /NAMES list" 32 | }, connections[1].awaitMessage(2)); 33 | } 34 | 35 | @Test 36 | void namesNoArgumentsTest() { 37 | addConnections(2); 38 | connections[0].send("JOIN #names"); 39 | connections[0].ignoreMessage(3); 40 | 41 | connections[1].createUser("john", "John Doe"); 42 | connections[1].send("JOIN #names,#joker"); 43 | connections[1].ignoreMessage(6); 44 | 45 | connections[2].createUser("fred", "Fred Bloggs"); 46 | connections[2].send("NAMES"); 47 | assertArrayEquals(new String[]{ 48 | ":jircd-host 353 fred = #names :~bob john", 49 | ":jircd-host 366 fred #names :End of /NAMES list", 50 | ":jircd-host 353 fred = #joker :~john", 51 | ":jircd-host 366 fred #joker :End of /NAMES list" 52 | }, connections[2].awaitMessage(4)); 53 | } 54 | 55 | @Test 56 | void namesNotJoinedSecretChannelTest() { 57 | addConnections(1); 58 | connections[0].send("JOIN #names"); 59 | connections[0].ignoreMessage(4); 60 | Optional channelOpt = getChannel("#names"); 61 | assumeTrue(channelOpt.isPresent()); 62 | Channel channel = channelOpt.get(); 63 | channel.modes().secret(true); 64 | 65 | connections[1].createUser("john", "John Doe"); 66 | connections[1].send("NAMES #names"); 67 | assertArrayEquals(new String[]{ 68 | ":jircd-host 366 john #names :End of /NAMES list" 69 | }, connections[1].awaitMessage(1)); 70 | } 71 | 72 | @Test 73 | void namesJoinedSecretChannelTest() { 74 | addConnections(1); 75 | connections[0].send("JOIN #names"); 76 | connections[0].ignoreMessage(4); 77 | Optional channelOpt = getChannel("#names"); 78 | assumeTrue(channelOpt.isPresent()); 79 | Channel channel = channelOpt.get(); 80 | channel.modes().secret(true); 81 | 82 | connections[1].createUser("john", "John Doe"); 83 | connections[1].send("JOIN #names"); 84 | connections[1].ignoreMessage(4); 85 | connections[1].send("NAMES #names"); 86 | assertArrayEquals(new String[]{ 87 | ":jircd-host 353 john @ #names :~bob john", 88 | ":jircd-host 366 john #names :End of /NAMES list" 89 | }, connections[1].awaitMessage(2)); 90 | } 91 | 92 | @Test 93 | void namesWithInvisibleNotJoinedChannelTest() { 94 | addConnections(1); 95 | connections[0].send("JOIN #names"); 96 | connections[0].ignoreMessage(4); 97 | Optional channelOpt = getChannel("#names"); 98 | assumeTrue(channelOpt.isPresent()); 99 | server.users().get(0).modes().invisible(true); 100 | 101 | connections[1].createUser("john", "John Doe"); 102 | connections[1].send("NAMES #names"); 103 | assertArrayEquals(new String[]{ 104 | ":jircd-host 353 john = #names :", 105 | ":jircd-host 366 john #names :End of /NAMES list" 106 | }, connections[1].awaitMessage(2)); 107 | } 108 | 109 | @Test 110 | void namesWithInvisibleJoinedChannelTest() { 111 | addConnections(1); 112 | connections[0].send("JOIN #names"); 113 | connections[0].ignoreMessage(4); 114 | Optional channelOpt = getChannel("#names"); 115 | assumeTrue(channelOpt.isPresent()); 116 | server.users().get(0).modes().invisible(true); 117 | 118 | connections[1].createUser("john", "John Doe"); 119 | connections[1].send("JOIN #names"); 120 | connections[1].ignoreMessage(4); 121 | connections[1].send("NAMES #names"); 122 | assertArrayEquals(new String[]{ 123 | ":jircd-host 353 john = #names :~bob john", 124 | ":jircd-host 366 john #names :End of /NAMES list" 125 | }, connections[1].awaitMessage(2)); 126 | } 127 | 128 | @AfterEach 129 | void tearDown() { 130 | off(); 131 | } 132 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/channel/PartCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.channel; 2 | 3 | import fr.enimaloc.jircd.channel.Channel; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.stream.Collectors; 9 | import org.junit.jupiter.api.AfterEach; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | import utils.CommandClazzTest; 13 | 14 | import static org.junit.jupiter.api.Assertions.*; 15 | 16 | @CommandClazzTest 17 | class PartCommandTest extends CommandChannelBase { 18 | 19 | @BeforeEach 20 | void setUp() { 21 | init(); 22 | } 23 | 24 | @Test 25 | void partTest() { 26 | connections[0].send("JOIN #jircd"); 27 | connections[0].ignoreMessage(4); 28 | Optional channelOpt = getChannel("#jircd"); 29 | assertFalse(channelOpt.isEmpty()); 30 | Channel channel = channelOpt.get(); 31 | 32 | assertFalse(server.users().get(0).channels().isEmpty()); 33 | connections[0].send("PART " + channel.name()); 34 | assertArrayEquals(new String[]{ 35 | ":bob PART #jircd" 36 | }, connections[0].awaitMessage()); 37 | } 38 | 39 | @Test 40 | void partWithReason() { 41 | connections[0].send("JOIN #jircd"); 42 | connections[0].ignoreMessage(4); 43 | Optional channelOpt = getChannel("#jircd"); 44 | assertFalse(channelOpt.isEmpty()); 45 | Channel channel = channelOpt.get(); 46 | 47 | assertFalse(server.users().get(0).channels().isEmpty()); 48 | connections[0].send("PART " + channel.name() + " :Bye!"); 49 | assertArrayEquals(new String[]{ 50 | ":bob PART #jircd :Bye!" 51 | }, connections[0].awaitMessage()); 52 | } 53 | 54 | @Test 55 | void partMultipleTest() { 56 | List channels = new ArrayList<>(); 57 | addConnections(1); 58 | for (int i = 0; i < 5; i++) { 59 | String channelName = "#" + getRandomString(5); 60 | connections[0].send("JOIN " + channelName); 61 | connections[0].ignoreMessage(3); 62 | Optional channelOpt = getChannel(channelName); 63 | assertFalse(channelOpt.isEmpty()); 64 | channels.add(channelOpt.get()); 65 | } 66 | 67 | connections[1].createUser("john", "John Doe"); 68 | connections[1].ignoreMessage(6 + attrLength + baseSettings.motd().length); 69 | connections[1].send("JOIN " + channels.stream() 70 | .map(Channel::name) 71 | .collect(Collectors.joining(","))); 72 | connections[1].ignoreMessage(channels.size() * 4); 73 | assertFalse(server.users().get(1).channels().isEmpty()); 74 | connections[1].send("PART " + channels.stream() 75 | .map(Channel::name) 76 | .collect(Collectors.joining(","))); 77 | assertArrayEquals(channels.stream().map(c -> ":john PART " + c.name()).toArray(), 78 | connections[1].awaitMessage(channels.size())); 79 | assertTrue(waitFor(() -> server.users().get(1).channels().isEmpty())); 80 | } 81 | 82 | @Test 83 | void partWithoutParametersTest() { 84 | connections[0].send("PART"); 85 | assertArrayEquals(new String[]{ 86 | ":jircd-host 461 bob PART :Not enough parameters" 87 | }, connections[0].awaitMessage()); 88 | } 89 | 90 | @Test 91 | void partInvalidChannelNameTest() { 92 | String channelName = getRandomString(7, 128, 255, i -> true, StandardCharsets.ISO_8859_1); 93 | connections[0].send("PART " + channelName); 94 | assertArrayEquals(new String[]{ 95 | ":jircd-host 403 bob %s :No such channel".formatted(channelName) 96 | }, connections[0].awaitMessage()); 97 | } 98 | 99 | @Test 100 | void partNotJoinedChannelTest() { 101 | addConnections(1); 102 | connections[0].send("JOIN #jircd"); 103 | connections[0].ignoreMessage(4); 104 | Optional channelOpt = getChannel("#jircd"); 105 | assertFalse(channelOpt.isEmpty()); 106 | 107 | connections[1].createUser("john", "John Doe"); 108 | connections[1].send("PART #jircd"); 109 | assertArrayEquals(new String[]{ 110 | ":jircd-host 442 john #jircd :You're not on that channel" 111 | }, connections[1].awaitMessage()); 112 | } 113 | 114 | @AfterEach 115 | void tearDown() { 116 | off(); 117 | } 118 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/channel/TopicCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.channel; 2 | 3 | import fr.enimaloc.jircd.channel.Channel; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.Optional; 6 | import org.junit.jupiter.api.AfterEach; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import utils.CommandClazzTest; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | import static org.junit.jupiter.api.Assumptions.assumeFalse; 13 | 14 | @CommandClazzTest 15 | class TopicCommandTest extends CommandChannelBase { 16 | 17 | @BeforeEach 18 | void setUp() { 19 | init(); 20 | } 21 | 22 | @Test 23 | void noTopicTest() { 24 | connections[0].send("JOIN #jircd"); 25 | connections[0].ignoreMessage(4); 26 | Optional channelOpt = getChannel("#jircd"); 27 | assertFalse(channelOpt.isEmpty()); 28 | Channel channel = channelOpt.get(); 29 | 30 | assertTrue(channel.topic().isEmpty()); 31 | 32 | connections[0].send("TOPIC #jircd"); 33 | assertArrayEquals(new String[]{ 34 | ":jircd-host 331 bob #jircd :No topic is set" 35 | }, connections[0].awaitMessage()); 36 | } 37 | 38 | @Test 39 | void topicTest() { 40 | addConnections(1); 41 | connections[0].send("JOIN #jircd"); 42 | connections[0].ignoreMessage(4); 43 | Optional channelOpt = getChannel("#jircd"); 44 | assertFalse(channelOpt.isEmpty()); 45 | Channel channel = channelOpt.get(); 46 | 47 | channel.topic(new Channel.Topic("Example topic", server.users().get(0))); 48 | assertTrue(channel.topic().isPresent()); 49 | 50 | connections[1].createUser("john", "John Doe"); 51 | connections[1].send("JOIN " + channel.name()); 52 | connections[1].ignoreMessage(4); 53 | connections[1].send("TOPIC " + channel.name()); 54 | Channel.Topic topic = channel.topic().get(); 55 | assertArrayEquals(new String[]{ 56 | ":jircd-host 332 john #jircd :Example topic", 57 | ":jircd-host 333 john #jircd bob %s".formatted(topic.unixTimestamp()) 58 | }, connections[1].awaitMessage(2)); 59 | } 60 | 61 | @Test 62 | void topicWithNoParameterTest() { 63 | connections[0].send("TOPIC"); 64 | assertArrayEquals(new String[]{ 65 | ":jircd-host 461 bob TOPIC :Not enough parameters" 66 | }, connections[0].awaitMessage()); 67 | } 68 | 69 | @Test 70 | void topicIncorrectChannelNameTest() { 71 | String channelName = "#" + getRandomString(7, 128, 255, i -> true, StandardCharsets.ISO_8859_1); 72 | connections[0].send("TOPIC " + channelName); 73 | assertArrayEquals(new String[]{ 74 | ":jircd-host 403 bob %s :No such channel".formatted(channelName) 75 | }, connections[0].awaitMessage()); 76 | } 77 | 78 | @Test 79 | void topicNotOnChannelTest() { 80 | addConnections(1); 81 | connections[0].send("JOIN #jircd"); 82 | connections[0].ignoreMessage(4); 83 | Optional channelOpt = getChannel("#jircd"); 84 | assertFalse(channelOpt.isEmpty()); 85 | 86 | connections[1].createUser("john", "John Doe"); 87 | connections[1].send("TOPIC #jircd"); 88 | assertArrayEquals(new String[]{ 89 | ":jircd-host 442 john #jircd :You're not on that channel" 90 | }, connections[1].awaitMessage()); 91 | } 92 | 93 | @Test 94 | void protectedTopicChangeButNoPermTest() { 95 | addConnections(1); 96 | connections[0].send("JOIN #jircd"); 97 | connections[0].ignoreMessage(3); 98 | Optional channelOpt = getChannel("#jircd"); 99 | assertFalse(channelOpt.isEmpty()); 100 | Channel channel = channelOpt.get(); 101 | channel.modes().protected0(true); 102 | 103 | assumeFalse(channel.users().isEmpty()); 104 | connections[1].createUser("john", "John Doe"); 105 | connections[1].send("JOIN #jircd"); 106 | connections[1].send("TOPIC #jircd :Another topic"); 107 | connections[1].ignoreMessage(3); 108 | assertArrayEquals(new String[]{ 109 | ":jircd-host 482 john #jircd :You're not channel operator" 110 | }, connections[1].awaitMessage()); 111 | assertTrue(channel.topic().isEmpty()); 112 | } 113 | 114 | @Test 115 | void nonProtectedTopicChangeButNoPermTest() { 116 | addConnections(1); 117 | connections[0].send("JOIN #jircd"); 118 | connections[0].ignoreMessage(3); 119 | Optional channelOpt = getChannel("#jircd"); 120 | assertFalse(channelOpt.isEmpty()); 121 | Channel channel = channelOpt.get(); 122 | 123 | assumeFalse(channel.users().isEmpty()); 124 | connections[1].createUser("john", "John Doe"); 125 | connections[1].send("JOIN #jircd"); 126 | connections[1].send("TOPIC #jircd :Another topic"); 127 | connections[1].ignoreMessage(3); 128 | assertArrayEquals(new String[]{ 129 | ":jircd-host 332 john #jircd :Another topic" 130 | }, connections[1].awaitMessage()); 131 | assertFalse(channel.topic().isEmpty()); 132 | } 133 | 134 | @Test 135 | void topicChangeWithPermTest() { 136 | connections[0].send("JOIN #jircd"); 137 | connections[0].ignoreMessage(3); 138 | Optional channelOpt = getChannel("#jircd"); 139 | assertFalse(channelOpt.isEmpty()); 140 | Channel channel = channelOpt.get(); 141 | 142 | assumeFalse(channel.users().isEmpty()); 143 | connections[0].send("TOPIC #jircd :Another topic"); 144 | assertArrayEquals(new String[]{ 145 | ":jircd-host 332 bob #jircd :Another topic" 146 | }, connections[0].awaitMessage()); 147 | assertFalse(channel.topic().isEmpty()); 148 | } 149 | 150 | @AfterEach 151 | void tearDown() { 152 | off(); 153 | } 154 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/connection/ConnectionCommandBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ConnectionCommandBase 3 | * 4 | * 0.0.1 5 | * 6 | * 05/08/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.connection; 9 | 10 | import fr.enimaloc.jircd.SocketBase; 11 | 12 | /** 13 | * 14 | */ 15 | public class ConnectionCommandBase extends SocketBase { 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/connection/NickCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.connection; 2 | 3 | import fr.enimaloc.jircd.SocketBase; 4 | import fr.enimaloc.jircd.user.UserInfo; 5 | import org.junit.jupiter.api.AfterEach; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import utils.CommandClazzTest; 9 | import utils.ListUtils; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 13 | 14 | @CommandClazzTest 15 | class NickCommandTest extends ConnectionCommandBase { 16 | 17 | @BeforeEach 18 | void setUp() { 19 | init(); 20 | } 21 | 22 | @Test 23 | void nickTest() { 24 | baseSettings.pass().ifPresent(pass -> connections[0].send("PASS " + pass)); 25 | 26 | connections[0].send("NICK bob"); 27 | assumeTrue(waitFor(() -> server.users().size() > 0)); 28 | UserInfo info = server.users().get(0).info(); 29 | assertTrue(waitFor(info::passwordValid)); 30 | assumeTrue(waitFor(() -> info.nickname() != null)); 31 | assertEquals("bob", info.nickname()); 32 | assertArrayEquals(EMPTY_ARRAY, connections[0].awaitMessage()); 33 | 34 | assertEquals("127.0.0.1", info.host()); 35 | assertNull(info.username()); 36 | assertNull(info.realName()); 37 | assertFalse(info.canRegistrationBeComplete()); 38 | } 39 | 40 | @Test 41 | void incorrectLengthNickTest() { 42 | baseSettings.pass().ifPresent(pass -> connections[0].send("PASS " + pass)); 43 | String nickname = getRandomString(50); 44 | connections[0].send("NICK " + nickname); 45 | assertArrayEquals(new String[]{ 46 | ":jircd-host 432 @127.0.0.1 " + nickname + " :Erroneus nickname" 47 | }, connections[0].awaitMessage()); 48 | } 49 | 50 | @Test 51 | void incorrectNickTest() { 52 | baseSettings.pass().ifPresent(pass -> connections[0].send("PASS " + pass)); 53 | String nickname = getRandomString(7, 160, 255, i -> true); 54 | connections[0].send("NICK " + nickname); 55 | assertArrayEquals(new String[]{ 56 | ":jircd-host 432 @127.0.0.1 " + nickname + " :Erroneus nickname" 57 | }, connections[0].awaitMessage()); 58 | } 59 | 60 | @Test 61 | void duplicateNickTest() { 62 | addConnections(1); 63 | baseSettings.pass().ifPresent(pass -> connections[0].send("PASS " + pass)); 64 | baseSettings.pass().ifPresent(pass -> connections[1].send("PASS " + pass)); 65 | connections[0].send("NICK dup"); 66 | connections[1].ignoreMessage(); 67 | connections[1].send("NICK dup"); 68 | assertArrayEquals(new String[]{ 69 | ":jircd-host 433 @127.0.0.1 dup :Nickname is already in use" 70 | }, connections[1].awaitMessage()); 71 | } 72 | 73 | @Test 74 | void unsafeNickWithSafenetTest() { 75 | baseSettings.pass().ifPresent(pass -> connections[0].send("PASS " + pass)); 76 | String nick = ListUtils.getRandom(baseSettings.unsafeNickname()); 77 | connections[0].send("NICK " + nick); 78 | 79 | assumeTrue(waitFor(() -> server.users().size() > 0)); 80 | UserInfo info = server.users().get(0).info(); 81 | assertTrue(waitFor(info::passwordValid)); 82 | assumeTrue(waitFor(() -> info.nickname() != null)); 83 | assertEquals(nick, info.nickname()); 84 | assertArrayEquals(EMPTY_ARRAY, connections[0].awaitMessage()); 85 | 86 | assertEquals("127.0.0.1", info.host()); 87 | assertNull(info.username()); 88 | assertNull(info.realName()); 89 | assertFalse(info.canRegistrationBeComplete()); 90 | } 91 | 92 | @Test 93 | void unsafeNickWithUnsafenetTest() { 94 | baseSettings.pass().ifPresent(pass -> connections[0].send("PASS " + pass)); 95 | String nick = ListUtils.getRandom(baseSettings.unsafeNickname()); 96 | 97 | assumeTrue(waitFor(() -> server.users().size() > 0)); 98 | UserInfo info = server.users().get(0).info(); 99 | info.setHost("255.255.255.255"); 100 | 101 | connections[0].send("NICK " + nick); 102 | assertArrayEquals(new String[]{ 103 | ":jircd-host 432 @255.255.255.255 " + nick + " :Erroneus nickname" 104 | }, connections[0].awaitMessage()); 105 | assertNull(info.nickname()); 106 | } 107 | 108 | @AfterEach 109 | void tearDown() { 110 | off(); 111 | } 112 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/connection/OperCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.connection; 2 | 3 | import fr.enimaloc.jircd.SocketBase; 4 | import fr.enimaloc.jircd.server.ServerSettings; 5 | import java.util.Random; 6 | import org.junit.jupiter.api.AfterEach; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import utils.CommandClazzTest; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | 13 | @CommandClazzTest 14 | class OperCommandTest extends ConnectionCommandBase { 15 | 16 | @BeforeEach 17 | void setUp() { 18 | init(); 19 | } 20 | 21 | @Test 22 | void operTest() { 23 | ServerSettings.Operator savedOper = baseSettings.operators().get(0); 24 | connections[0].send("OPER " + savedOper.username() + " " + savedOper.password()); 25 | assertArrayEquals(new String[]{ 26 | ":jircd-host 381 @127.0.0.1 :You are now an IRC operator" 27 | }, connections[0].awaitMessage()); 28 | } 29 | 30 | @Test 31 | void incorrectPasswdOperTest() { 32 | ServerSettings.Operator savedOper = baseSettings.operators().get(0); 33 | connections[0].send( 34 | "OPER " + savedOper.username() + " " + getRandomString(new Random().nextInt(9) + 1)); 35 | assertArrayEquals(new String[]{ 36 | ":jircd-host 464 @127.0.0.1 :Password incorrect" 37 | }, connections[0].awaitMessage()); 38 | } 39 | 40 | @Test 41 | void incorrectParamsNumberOperTest() { 42 | connections[0].send("OPER"); 43 | assertArrayEquals(new String[]{ 44 | ":jircd-host 461 @127.0.0.1 OPER :Not enough parameters" 45 | }, connections[0].awaitMessage()); 46 | } 47 | 48 | @Test 49 | void incorrectOperHostTest() { 50 | connections[0].send("OPER " + baseSettings.operators().get(1).username() + " " + 51 | baseSettings.operators().get(1).password()); 52 | assertArrayEquals(new String[]{ 53 | ":jircd-host 491 @127.0.0.1 :No O-lines for your host" 54 | }, connections[0].awaitMessage()); 55 | } 56 | 57 | @AfterEach 58 | void tearDown() { 59 | off(); 60 | } 61 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/connection/PassCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.connection; 2 | 3 | import fr.enimaloc.jircd.SocketBase; 4 | import fr.enimaloc.jircd.user.UserInfo; 5 | import java.util.Random; 6 | import org.junit.jupiter.api.AfterEach; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import utils.CommandClazzTest; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | import static org.junit.jupiter.api.Assumptions.assumeFalse; 13 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 14 | 15 | @CommandClazzTest 16 | class PassCommandTest extends ConnectionCommandBase { 17 | 18 | @BeforeEach 19 | void setUp() { 20 | init(); 21 | } 22 | 23 | @Test 24 | void passTest() { 25 | baseSettings.pass().ifPresent(pass -> connections[0].send("PASS " + pass)); 26 | assertArrayEquals(EMPTY_ARRAY, connections[0].awaitMessage()); 27 | assumeTrue(waitFor(() -> server.users().size() > 0)); 28 | UserInfo info = server.users().get(0).info(); 29 | assertTrue(waitFor(info::passwordValid)); 30 | 31 | assertEquals("127.0.0.1", info.host()); 32 | assertNull(info.username()); 33 | assertNull(info.nickname()); 34 | assertNull(info.realName()); 35 | assertFalse(info.canRegistrationBeComplete()); 36 | } 37 | 38 | @Test 39 | void noParamTest() { 40 | assertArrayEquals(new String[]{ 41 | ":jircd-host 461 @127.0.0.1 PASS :Not enough parameters" 42 | }, connections[0].send("PASS", 1)); 43 | assumeTrue(waitFor(() -> server.users().size() > 0)); 44 | assertFalse(server.users().get(0).info().passwordValid()); 45 | } 46 | 47 | @Test 48 | void incorrectPassTest() { 49 | String passwd = getRandomString(new Random().nextInt(9) + 1); 50 | assumeFalse(baseSettings.pass().equals(passwd)); 51 | assertArrayEquals(new String[]{ 52 | ":jircd-host 464 @127.0.0.1 :Password incorrect" 53 | }, connections[0].send("PASS " + passwd, 1)); 54 | assumeTrue(waitFor(() -> server.users().size() > 0)); 55 | assertFalse(server.users().get(0).info().passwordValid()); 56 | } 57 | 58 | @Test 59 | void alreadyRegisteredPassTest() { 60 | connections[0].createUser("john", "John Doe"); 61 | baseSettings.pass().ifPresent(pass -> connections[0].send("PASS " + pass)); 62 | assertArrayEquals(new String[]{ 63 | ":jircd-host 462 john :You may not reregister" 64 | }, connections[0].awaitMessage()); 65 | } 66 | 67 | @AfterEach 68 | void tearDown() { 69 | off(); 70 | } 71 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/connection/PingCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.connection; 2 | 3 | import fr.enimaloc.jircd.SocketBase; 4 | import org.junit.jupiter.api.AfterEach; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import utils.CommandClazzTest; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | @CommandClazzTest 12 | class PingCommandTest extends ConnectionCommandBase { 13 | 14 | @BeforeEach 15 | void setUp() { 16 | init(); 17 | } 18 | 19 | @Test 20 | void pingNoParamsTest() { 21 | assertArrayEquals( 22 | new String[]{":jircd-host 461 @127.0.0.1 PING :Not enough parameters"}, 23 | connections[0].send("PING", 1) 24 | ); 25 | } 26 | 27 | @Test 28 | void pingWithParamsTest() { 29 | assertArrayEquals( 30 | new String[]{"PONG Hello"}, 31 | connections[0].send("PING Hello", 1) 32 | ); 33 | } 34 | 35 | @Test 36 | void pingWithTrailingTest() { 37 | assertArrayEquals( 38 | new String[]{"PONG Hello "}, 39 | connections[0].send("PING :Hello ", 1) 40 | ); 41 | } 42 | 43 | @AfterEach 44 | void tearDown() { 45 | off(); 46 | } 47 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/connection/QuitCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.connection; 2 | 3 | import fr.enimaloc.jircd.SocketBase; 4 | import fr.enimaloc.jircd.user.User; 5 | import fr.enimaloc.jircd.user.UserState; 6 | import org.junit.jupiter.api.AfterEach; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import utils.CommandClazzTest; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 13 | 14 | @CommandClazzTest 15 | class QuitCommandTest extends ConnectionCommandBase { 16 | 17 | @BeforeEach 18 | void setUp() { 19 | init(); 20 | } 21 | 22 | @Test 23 | void quitTest() { 24 | assumeTrue(waitFor(() -> server.users().size() == 1)); 25 | User user = server.users().get(0); 26 | 27 | connections[0].send("QUIT", 1); 28 | assertTrue(waitFor(server.users()::isEmpty)); 29 | assertEquals(UserState.DISCONNECTED, user.state()); 30 | } 31 | 32 | @Test 33 | void quitWithReasonTest() { 34 | assumeTrue(waitFor(() -> server.users().size() == 1)); 35 | User user = server.users().get(0); 36 | 37 | connections[0].send("QUIT :Bye", 1); 38 | assertTrue(waitFor(() -> server.users().isEmpty())); 39 | assertEquals(UserState.DISCONNECTED, user.state()); 40 | } 41 | 42 | @AfterEach 43 | void tearDown() { 44 | off(); 45 | } 46 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/connection/UserCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.connection; 2 | 3 | import fr.enimaloc.jircd.SocketBase; 4 | import fr.enimaloc.jircd.user.UserInfo; 5 | import org.junit.jupiter.api.AfterEach; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import utils.CommandClazzTest; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 12 | 13 | @CommandClazzTest 14 | class UserCommandTest extends ConnectionCommandBase { 15 | 16 | @BeforeEach 17 | void setUp() { 18 | init(); 19 | } 20 | 21 | @Test 22 | void userTest() { 23 | baseSettings.pass().ifPresent(pass -> connections[0].send("PASS " + pass)); 24 | connections[0].send("USER bobby 0 * :Mobbye Plav"); 25 | assumeTrue(waitFor(() -> server.users().size() > 0)); 26 | UserInfo info = server.users().get(0).info(); 27 | assertTrue(waitFor(() -> info.username() != null && info.realName() != null)); 28 | assertEquals("bobby", info.username()); 29 | assertEquals("Mobbye Plav", info.realName()); 30 | assertTrue(info.passwordValid()); 31 | 32 | assertEquals("127.0.0.1", info.host()); 33 | assertNull(info.nickname()); 34 | assertFalse(info.canRegistrationBeComplete()); 35 | } 36 | 37 | @Test 38 | void alreadyRegisteredUserTest() { 39 | baseSettings.pass().ifPresent(pass -> connections[0].send("PASS " + pass)); 40 | connections[0].send("NICK bob"); 41 | connections[0].send("USER bobby 0 * :Mobby Plav"); 42 | connections[0].ignoreMessage(6 + attrLength + baseSettings.motd().length); 43 | connections[0].send("USER bob 0 * :Mobba Plav"); 44 | assertArrayEquals(new String[]{ 45 | ":jircd-host 462 bob :You may not reregister" 46 | }, connections[0].awaitMessage()); 47 | } 48 | 49 | @AfterEach 50 | void tearDown() { 51 | off(); 52 | } 53 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/messages/MessageCommandBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MessageCommandBase 3 | * 4 | * 0.0.1 5 | * 6 | * 06/08/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.messages; 9 | 10 | import fr.enimaloc.jircd.SocketBase; 11 | 12 | /** 13 | * 14 | */ 15 | public class MessageCommandBase extends SocketBase { 16 | @Override 17 | protected void init() { 18 | super.init(); 19 | connections[0].createUser("bob", "bobby", "Mobbye Plav"); 20 | addConnections(1); 21 | connections[1].createUser("john", "John Doe"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/messages/NoticeCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.messages; 2 | 3 | import fr.enimaloc.jircd.channel.Channel; 4 | import fr.enimaloc.jircd.user.User; 5 | import java.util.Optional; 6 | import org.junit.jupiter.api.AfterEach; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Nested; 9 | import org.junit.jupiter.api.Test; 10 | import utils.CommandClazzTest; 11 | 12 | import static org.junit.jupiter.api.Assertions.*; 13 | import static org.junit.jupiter.api.Assumptions.assumeFalse; 14 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 15 | 16 | @CommandClazzTest 17 | class NoticeCommandTest extends MessageCommandBase { 18 | 19 | Channel channel; 20 | 21 | @BeforeEach 22 | void setUp() { 23 | init(); 24 | connections[1].send("JOIN #hello"); 25 | connections[1].ignoreMessage(3); 26 | assumeTrue(getChannel("#hello").isPresent()); 27 | channel = getChannel("#hello").get(); 28 | assumeFalse(channel.users().isEmpty()); 29 | } 30 | 31 | @Test 32 | void noticeSameChannelTest() { 33 | connections[0].send("JOIN #hello"); 34 | connections[0].ignoreMessage(3); 35 | connections[1].ignoreMessage(); 36 | connections[0].send("NOTICE #hello :Hey!"); 37 | assertArrayEquals(EMPTY_ARRAY, connections[0].awaitMessage()); 38 | assertArrayEquals(new String[]{ 39 | ":bob NOTICE #hello :Hey!" 40 | }, connections[1].awaitMessage()); 41 | } 42 | 43 | @Test 44 | void noticeNotSameChannelTest() { 45 | connections[0].send("NOTICE #hello :Hey!"); 46 | assertArrayEquals(EMPTY_ARRAY, connections[0].awaitMessage()); 47 | assertArrayEquals(new String[]{ 48 | ":bob NOTICE #hello :Hey!" 49 | }, connections[1].awaitMessage()); 50 | } 51 | 52 | @Test 53 | void noticeNotSameChannelAndRefuseExternalMessageTest() { 54 | channel.modes().noExternalMessage(true); 55 | connections[0].send("NOTICE #hello :Hey!"); 56 | assertArrayEquals(EMPTY_ARRAY, connections[0].awaitMessage()); 57 | assertArrayEquals(EMPTY_ARRAY, connections[1].awaitMessage()); 58 | } 59 | 60 | @Test 61 | void noticeBannedAndNoExemptTest() { 62 | channel.modes().bans().add("bob!*@*"); 63 | connections[0].send("NOTICE #hello :Hey!"); 64 | assertArrayEquals(EMPTY_ARRAY, connections[0].awaitMessage()); 65 | assertArrayEquals(EMPTY_ARRAY, connections[1].awaitMessage()); 66 | } 67 | 68 | @Test 69 | void noticeInModerateButNotVoiceTest() { 70 | connections[0].send("JOIN #hello"); 71 | connections[0].ignoreMessage(3); 72 | connections[1].ignoreMessage(); 73 | channel.modes().moderate(true); 74 | connections[0].send("NOTICE #hello :Hey!"); 75 | assertArrayEquals(EMPTY_ARRAY, connections[0].awaitMessage()); 76 | assertArrayEquals(EMPTY_ARRAY, connections[1].awaitMessage()); 77 | } 78 | 79 | @Test 80 | void noticeInModerateWithVoiceTest() { 81 | connections[0].send("JOIN #hello"); 82 | connections[0].ignoreMessage(3); 83 | connections[1].ignoreMessage(); 84 | channel.modes().moderate(true); 85 | channel.prefix(server.users().get(0), "+"); 86 | connections[0].send("NOTICE #hello :Hey!"); 87 | assertArrayEquals(EMPTY_ARRAY, connections[0].awaitMessage()); 88 | assertArrayEquals(new String[]{ 89 | ":bob NOTICE #hello :Hey!" 90 | }, connections[1].awaitMessage()); 91 | } 92 | 93 | @Test 94 | void noticeToOwnerChannelTest() { 95 | addConnections(1); 96 | connections[0].send("JOIN #hello"); 97 | connections[0].ignoreMessage(3); 98 | connections[1].ignoreMessage(); 99 | connections[2].send("JOIN #hello"); 100 | connections[2].ignoreMessage(3); 101 | connections[0].ignoreMessage(); 102 | connections[1].ignoreMessage(); 103 | 104 | connections[0].send("NOTICE ~#hello :Hey!"); 105 | assertArrayEquals(EMPTY_ARRAY, connections[0].awaitMessage()); 106 | assertArrayEquals(new String[]{ 107 | ":bob NOTICE ~#hello :Hey!" 108 | }, connections[1].awaitMessage()); 109 | assertArrayEquals(EMPTY_ARRAY, connections[2].awaitMessage()); 110 | } 111 | 112 | @AfterEach 113 | void tearDown() { 114 | off(); 115 | } 116 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/operator/KillCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.operator; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import utils.CommandClazzTest; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 10 | 11 | @CommandClazzTest 12 | class KillCommandTest extends OperatorCommandBase { 13 | 14 | @BeforeEach 15 | void setUp() { 16 | init(); 17 | addConnections(1); 18 | connections[0].createUser("bob", "bobby", "Mobbye Plav"); 19 | connections[1].createUser("john", "John Doe"); 20 | connections[0].send("JOIN #kill"); 21 | connections[0].ignoreMessage(3); 22 | 23 | connections[1].send("JOIN #kill"); 24 | connections[1].ignoreMessage(3); 25 | connections[0].ignoreMessage(); 26 | } 27 | 28 | @Test 29 | void killTest() { 30 | assumeTrue(server.users().get(0) != null); 31 | server.users().get(0).modes().oper(true); 32 | connections[0].send("KILL john :Stop spamming"); 33 | assertArrayEquals(new String[]{ 34 | ":john QUIT :Quit: Killed (bob (Stop spamming))" 35 | }, connections[0].awaitMessage()); 36 | assertArrayEquals(new String[]{ 37 | "Closing Link: jircd-host (Killed (bob (Stop spamming)))" 38 | }, connections[1].awaitMessage()); 39 | assertArrayEquals(SOCKET_CLOSE, connections[1].awaitMessage()); 40 | } 41 | 42 | @Test 43 | void killLocalOperTest() { 44 | assumeTrue(server.users().get(0) != null); 45 | server.users().get(0).modes().localOper(true); 46 | connections[0].send("KILL john :Stop spamming"); 47 | assertArrayEquals(new String[]{ 48 | ":john QUIT :Quit: Killed (bob (Stop spamming))" 49 | }, connections[0].awaitMessage()); 50 | assertArrayEquals(new String[]{ 51 | "Closing Link: jircd-host (Killed (bob (Stop spamming)))" 52 | }, connections[1].awaitMessage()); 53 | assertArrayEquals(SOCKET_CLOSE, connections[1].awaitMessage()); 54 | } 55 | 56 | @Test 57 | void killUnknownTest() { 58 | assumeTrue(server.users().get(0) != null); 59 | server.users().get(0).modes().localOper(true); 60 | connections[0].send("KILL x :Stop spamming"); 61 | assertArrayEquals(EMPTY_ARRAY, connections[0].awaitMessage()); 62 | assertArrayEquals(EMPTY_ARRAY, connections[1].awaitMessage()); 63 | } 64 | 65 | @Test 66 | void killNotOperTest() { 67 | connections[0].send("KILL john :Stop spamming"); 68 | assertArrayEquals(new String[]{ 69 | ":jircd-host 481 bob :Permission Denied- You're not an IRC operator" 70 | }, connections[0].awaitMessage()); 71 | assertArrayEquals(EMPTY_ARRAY, connections[1].awaitMessage()); 72 | } 73 | 74 | @AfterEach 75 | void tearDown() { 76 | off(); 77 | } 78 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/operator/OperatorCommandBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * OperatorCommandBase 3 | * 4 | * 0.0.1 5 | * 6 | * 07/08/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.operator; 9 | 10 | import fr.enimaloc.jircd.SocketBase; 11 | 12 | /** 13 | * 14 | */ 15 | public class OperatorCommandBase extends SocketBase { 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/operator/RehashCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.operator; 2 | 3 | import fr.enimaloc.jircd.commands.Command; 4 | import fr.enimaloc.jircd.message.Message; 5 | import fr.enimaloc.jircd.server.ServerSettings; 6 | import fr.enimaloc.jircd.user.User; 7 | import java.io.IOException; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import org.junit.jupiter.api.AfterEach; 11 | import org.junit.jupiter.api.BeforeEach; 12 | import org.junit.jupiter.api.Test; 13 | import utils.CommandClazzTest; 14 | 15 | import static org.junit.jupiter.api.Assertions.*; 16 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 17 | 18 | @CommandClazzTest 19 | class RehashCommandTest extends OperatorCommandBase { 20 | 21 | @BeforeEach 22 | void setUp() { 23 | init(); 24 | } 25 | 26 | @Test 27 | void rehashTest() { 28 | connections[0].createUser("bob", "Mobbye Plav"); 29 | 30 | assumeTrue(server.users().get(0) != null); 31 | server.users().get(0).modes().oper(true); 32 | 33 | // TODO: 28/07/2022 - Add config change here 34 | // temp solution : create a file named "settings.toml", and put another value, an hardcoded text is 35 | // loaded a start of the test, and don't take settings.toml as base config file. 36 | ServerSettings temp = new ServerSettings.Builder() 37 | .host("another-host") 38 | .build(); 39 | 40 | Path path = Path.of("settings.toml"); 41 | if (!Files.exists(path)) { 42 | temp.saveAs(path); 43 | } 44 | 45 | assertArrayEquals(new String[]{ 46 | ":%s 382 bob settings.toml :Rehashing".formatted(server.settings().host()) 47 | }, connections[0].send("REHASH", 1)); 48 | connections[0].ignoreMessage(); 49 | 50 | assertNotEquals(baseSettings, server.settings()); 51 | try { 52 | Files.delete(path); 53 | } catch (IOException e) { 54 | throw new RuntimeException(e); 55 | } 56 | } 57 | 58 | @Test 59 | void rehashNotOperTest() { 60 | connections[0].createUser("bob", "Mobbye Plav"); 61 | 62 | connections[0].send("REHASH"); 63 | assertArrayEquals(new String[]{ 64 | ":jircd-host 481 bob :Permission Denied- You're not an IRC operator" 65 | }, connections[0].awaitMessage()); 66 | 67 | assertEquals(baseSettings, server.settings()); 68 | } 69 | 70 | @AfterEach 71 | void tearDown() { 72 | off(); 73 | } 74 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/operator/RestartCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.operator; 2 | 3 | import java.io.IOException; 4 | import java.net.ConnectException; 5 | import java.util.concurrent.TimeUnit; 6 | import org.junit.jupiter.api.AfterEach; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; 10 | import utils.CommandClazzTest; 11 | 12 | import static org.junit.jupiter.api.Assertions.*; 13 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 14 | 15 | @CommandClazzTest 16 | class RestartCommandTest extends OperatorCommandBase { 17 | 18 | @BeforeEach 19 | void setUp() { 20 | init(); 21 | } 22 | 23 | // FIXME: 30/07/2022 Thread not interrupted on TravisCI 24 | @Test 25 | @DisabledIfEnvironmentVariable(named = "CI", matches = "true", disabledReason = "Thread not interrupt on TravisCI") 26 | void restartTest() { 27 | connections[0].createUser("bob", "Mobbye Plav"); 28 | 29 | assumeTrue(server.users().get(0) != null); 30 | server.users().get(0).modes().oper(true); 31 | 32 | connections[0].send("RESTART"); 33 | assertTrue(waitFor(server::isShutdown, 5, TimeUnit.MINUTES)); 34 | assertTrue(waitFor(server::isInterrupted, 1, TimeUnit.MINUTES)); 35 | assertTrue(waitFor(() -> !connections[0].testConnection(baseSettings.port()))); 36 | assertTrue(waitFor(() -> { 37 | try { 38 | connections[0] = createConnection(); 39 | return true; 40 | } catch (ConnectException e) { 41 | return false; 42 | } catch (IOException e) { 43 | fail(e); 44 | return false; 45 | } 46 | }, 1, TimeUnit.MINUTES)); 47 | connections[0].createUser("bob", "Mobbye Plav"); 48 | } 49 | 50 | @Test 51 | void restartNotOperTest() { 52 | connections[0].createUser("bob", "Mobbye Plav"); 53 | 54 | connections[0].send("RESTART"); 55 | assertArrayEquals(new String[]{ 56 | ":jircd-host 481 bob :Permission Denied- You're not an IRC operator" 57 | }, connections[0].awaitMessage()); 58 | } 59 | 60 | @AfterEach 61 | void tearDown() { 62 | off(); 63 | } 64 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/operator/SQuitCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.operator; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import utils.CommandClazzTest; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 10 | 11 | @CommandClazzTest 12 | class SQuitCommandTest extends OperatorCommandBase { 13 | 14 | @BeforeEach 15 | void setUp() { 16 | init(); 17 | } 18 | 19 | @Test 20 | void quitTest() { 21 | connections[0].createUser("bob", "Mobbye Plav"); 22 | 23 | assumeTrue(server.users().get(0) != null); 24 | server.users().get(0).modes().oper(true); 25 | 26 | connections[0].send("SQUIT notASrv :I'm out"); 27 | assertArrayEquals(new String[]{ 28 | ":jircd-host 402 bob notASrv :No such server" 29 | }, connections[0].awaitMessage()); 30 | } 31 | 32 | @Test 33 | void quitNotOperTest() { 34 | connections[0].createUser("bob", "Mobbye Plav"); 35 | 36 | connections[0].send("SQUIT notASrv :I'm out"); 37 | assertArrayEquals(new String[]{ 38 | ":jircd-host 481 bob :Permission Denied- You're not an IRC operator" 39 | }, connections[0].awaitMessage()); 40 | } 41 | 42 | @AfterEach 43 | void tearDown() { 44 | off(); 45 | } 46 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/optional/AwayCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.optional; 2 | 3 | import fr.enimaloc.jircd.user.User; 4 | import org.junit.jupiter.api.AfterEach; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import utils.CommandClazzTest; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 11 | 12 | @CommandClazzTest 13 | class AwayCommandTest extends OptionalCommandBase { 14 | 15 | @BeforeEach 16 | void setUp() { 17 | init(); 18 | } 19 | 20 | @Test 21 | void awayTest() { 22 | connections[0].createUser("bob", "Mobbye Plav"); 23 | 24 | User bob = server.users().get(0); 25 | assumeTrue(bob != null); 26 | 27 | connections[0].send("AWAY :I'm away"); 28 | assertArrayEquals(new String[]{ 29 | ":jircd-host 306 bob :You have been marked as being away" 30 | }, connections[0].awaitMessage()); 31 | assertTrue(bob.away().isPresent()); 32 | } 33 | 34 | @Test 35 | void unawayTest() { 36 | connections[0].createUser("bob", "Mobbye Plav"); 37 | 38 | User bob = server.users().get(0); 39 | assumeTrue(bob != null); 40 | bob.away("I'm away"); 41 | assumeTrue(bob.away().isPresent()); 42 | 43 | connections[0].send("AWAY"); 44 | assertArrayEquals(new String[]{ 45 | ":jircd-host 305 bob :You are no longer marked as being away" 46 | }, connections[0].awaitMessage()); 47 | assertTrue(bob.away().isEmpty()); 48 | } 49 | 50 | @AfterEach 51 | void tearDown() { 52 | off(); 53 | } 54 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/optional/LinksCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.optional; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import utils.CommandClazzTest; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | @CommandClazzTest 11 | class LinksCommandTest extends OptionalCommandBase { 12 | 13 | @BeforeEach 14 | void setUp() { 15 | init(); 16 | } 17 | 18 | @Test 19 | void linksTest() { 20 | connections[0].createUser("bob", "Mobbye Plav"); 21 | 22 | connections[0].send("LINKS"); 23 | assertArrayEquals(new String[]{ 24 | ":jircd-host 364 bob * jircd-host :0 jircd is a lightweight IRC server written in Java.", 25 | ":jircd-host 365 bob * :End of /LINKS list" 26 | }, connections[0].awaitMessage(2)); 27 | } 28 | 29 | @AfterEach 30 | void tearDown() { 31 | off(); 32 | } 33 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/optional/OptionalCommandBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * OptionalCommandBase 3 | * 4 | * 0.0.1 5 | * 6 | * 07/08/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.optional; 9 | 10 | import fr.enimaloc.jircd.SocketBase; 11 | 12 | /** 13 | * 14 | */ 15 | public class OptionalCommandBase extends SocketBase { 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/optional/UserhostCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.optional; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import utils.CommandClazzTest; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 10 | 11 | @CommandClazzTest 12 | class UserhostCommandTest extends OptionalCommandBase { 13 | 14 | @BeforeEach 15 | void setUp() { 16 | init(); 17 | connections[0].createUser("bob", "bobby", "Mobbye Plav"); 18 | } 19 | 20 | @Test 21 | void userhostWithOneParametersTest() { 22 | addConnections(1); 23 | connections[1].createUser("john", "John Doe"); 24 | 25 | connections[0].send("USERHOST john"); 26 | assertArrayEquals(new String[]{ 27 | ":jircd-host 302 bob :john+127.0.0.1" 28 | }, connections[0].awaitMessage()); 29 | } 30 | 31 | @Test 32 | void userhostWithTwoParametersTest() { 33 | addConnections(2); 34 | connections[1].createUser("john", "John Doe"); 35 | connections[2].createUser("fred", "Fred Bloggs"); 36 | 37 | connections[0].send("USERHOST john fred"); 38 | assertArrayEquals(new String[]{ 39 | ":jircd-host 302 bob :john+127.0.0.1 fred+127.0.0.1" 40 | }, connections[0].awaitMessage()); 41 | } 42 | 43 | @Test 44 | void userhostWithThreeParametersTest() { 45 | addConnections(3); 46 | connections[1].createUser("john", "John Doe"); 47 | connections[2].createUser("fred", "Fred Bloggs"); 48 | connections[3].createUser("tommy", "Tommy Atkins"); 49 | 50 | connections[0].send("USERHOST john fred tommy"); 51 | assertArrayEquals(new String[]{ 52 | ":jircd-host 302 bob :john+127.0.0.1 fred+127.0.0.1 tommy+127.0.0.1" 53 | }, connections[0].awaitMessage()); 54 | } 55 | 56 | @Test 57 | void userhostWithFourParametersTest() { 58 | addConnections(4); 59 | connections[1].createUser("john", "John Doe"); 60 | connections[2].createUser("fred", "Fred Bloggs"); 61 | connections[3].createUser("tommy", "Tommy Atkins"); 62 | connections[4].createUser("ann", "Ann Yonne"); 63 | 64 | connections[0].send("USERHOST john fred tommy ann"); 65 | assertArrayEquals(new String[]{ 66 | ":jircd-host 302 bob :john+127.0.0.1 fred+127.0.0.1 tommy+127.0.0.1 ann+127.0.0.1" 67 | }, connections[0].awaitMessage()); 68 | } 69 | 70 | @Test 71 | void userhostWithFiveParametersTest() { 72 | addConnections(5); 73 | connections[1].createUser("john", "John Doe"); 74 | connections[2].createUser("fred", "Fred Bloggs"); 75 | connections[3].createUser("tommy", "Tommy Atkins"); 76 | connections[4].createUser("ann", "Ann Yonne"); 77 | connections[5].createUser("ratman", "Doug Rattmann"); 78 | 79 | connections[0].send("USERHOST john fred tommy ann ratman"); 80 | assertArrayEquals(new String[]{ 81 | ":jircd-host 302 bob :john+127.0.0.1 fred+127.0.0.1 tommy+127.0.0.1 ann+127.0.0.1 ratman+127.0.0.1" 82 | }, connections[0].awaitMessage()); 83 | } 84 | 85 | @Test 86 | void userhostOperatorTest() { 87 | addConnections(2); 88 | connections[1].createUser("john", "John Doe"); 89 | assumeTrue(server.users().get(1) != null); 90 | server.users().get(1).modes().oper(true); 91 | connections[2].createUser("fred", "Fred Bloggs"); 92 | 93 | connections[0].send("USERHOST john fred"); 94 | assertArrayEquals(new String[]{ 95 | ":jircd-host 302 bob :john*+127.0.0.1 fred+127.0.0.1" 96 | }, connections[0].awaitMessage()); 97 | } 98 | 99 | @Test 100 | void userhostAwayTest() { 101 | addConnections(2); 102 | connections[1].createUser("john", "John Doe"); 103 | assumeTrue(server.users().get(1) != null); 104 | server.users().get(1).away("Away!"); 105 | connections[2].createUser("fred", "Fred Bloggs"); 106 | 107 | connections[0].send("USERHOST john fred"); 108 | assertArrayEquals(new String[]{ 109 | ":jircd-host 302 bob :john-127.0.0.1 fred+127.0.0.1" 110 | }, connections[0].awaitMessage()); 111 | } 112 | 113 | @AfterEach 114 | void tearDown() { 115 | off(); 116 | } 117 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/optional/WallOpsCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.optional; 2 | 3 | import fr.enimaloc.jircd.user.User; 4 | import org.junit.jupiter.api.AfterEach; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import utils.CommandClazzTest; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | import static org.junit.jupiter.api.Assumptions.assumeFalse; 11 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 12 | 13 | @CommandClazzTest 14 | class WallOpsCommandTest extends OptionalCommandBase { 15 | 16 | @BeforeEach 17 | void setUp() { 18 | init(); 19 | addConnections(2); 20 | connections[0].createUser("bob", "Mobbye Plav"); 21 | 22 | assumeTrue(getUser("bob").isPresent()); 23 | User bob = getUser("bob").get(); 24 | bob.info().setOper(baseSettings.operators().get(0)); 25 | assumeTrue(bob.modes().oper()); 26 | 27 | connections[1].createUser("john", "John Doe"); 28 | 29 | assumeTrue(getUser("john").isPresent()); 30 | User john = getUser("john").get(); 31 | john.modes().wallops(true); 32 | assumeFalse(john.modes().oper()); 33 | assumeTrue(john.modes().wallops()); 34 | 35 | connections[2].createUser("jane", "Jane Doe"); 36 | 37 | assumeTrue(getUser("jane").isPresent()); 38 | User jane = getUser("jane").get(); 39 | assumeFalse(jane.modes().oper()); 40 | assumeFalse(jane.modes().wallops()); 41 | } 42 | 43 | @Test 44 | void wallopsTest() { 45 | connections[0].send("WALLOPS :Hello world!"); 46 | assertArrayEquals(new String[]{ 47 | ":bob WALLOPS :Hello world!" 48 | }, connections[0].awaitMessage()); 49 | assertArrayEquals(new String[]{ 50 | ":bob WALLOPS :Hello world!" 51 | }, connections[1].awaitMessage()); 52 | assertArrayEquals(EMPTY_ARRAY, connections[2].awaitMessage()); 53 | } 54 | 55 | @AfterEach 56 | void tearDown() { 57 | off(); 58 | } 59 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/server/AdminCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.server; 2 | 3 | import fr.enimaloc.jircd.server.ServerSettings; 4 | import org.junit.jupiter.api.AfterEach; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import utils.CommandClazzTest; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | @CommandClazzTest 12 | class AdminCommandTest extends ServerCommandBase { 13 | 14 | @BeforeEach 15 | void setUp() { 16 | init(); 17 | } 18 | 19 | @Test 20 | void adminWithUnknownServerTest() { 21 | connections[0].send("ADMIN UnknownServer"); 22 | assertArrayEquals(new String[]{ 23 | ":jircd-host 402 bob UnknownServer :No such server" 24 | }, connections[0].awaitMessage()); 25 | } 26 | 27 | @Override 28 | protected ServerSettings.Builder buildSettings() { 29 | return super.buildSettings().admin(new ServerSettings.Admin( 30 | "Location 1", 31 | "Location 2", 32 | "jircd@local.host" 33 | )); 34 | } 35 | 36 | @Test 37 | void adminTest() { 38 | connections[0].send("ADMIN"); 39 | 40 | assertArrayEquals(new String[]{ 41 | ":jircd-host 256 bob jircd-host :Administrative info", 42 | ":jircd-host 257 bob :Location 1", 43 | ":jircd-host 258 bob :Location 2", 44 | ":jircd-host 259 bob :jircd@local.host" 45 | }, connections[0].awaitMessage(4)); 46 | } 47 | 48 | @AfterEach 49 | void tearDown() { 50 | off(); 51 | } 52 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/server/ConnectCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.server; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Disabled; 6 | import org.junit.jupiter.api.Test; 7 | import utils.CommandClazzTest; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | @CommandClazzTest 12 | class ConnectCommandTest extends ServerCommandBase { 13 | 14 | @BeforeEach 15 | void setUp() { 16 | init(); 17 | } 18 | 19 | @Test 20 | void connectErrorTest() { 21 | connections[0].send("CONNECT one"); 22 | assertArrayEquals(new String[]{ 23 | ":jircd-host 400 bob CONNECT :Not supported yet" 24 | }, connections[0].awaitMessage()); 25 | 26 | connections[0].send("CONNECT one two"); 27 | assertArrayEquals(new String[]{ 28 | ":jircd-host 400 bob CONNECT :Not supported yet" 29 | }, connections[0].awaitMessage()); 30 | 31 | connections[0].send("CONNECT one two three"); 32 | assertArrayEquals(new String[]{ 33 | ":jircd-host 400 bob CONNECT :Not supported yet" 34 | }, connections[0].awaitMessage()); 35 | } 36 | 37 | @Test 38 | @Disabled("Not supported yet") 39 | void connectOneArgumentTest() { 40 | connections[0].send("CONNECT one"); 41 | } 42 | 43 | @Test 44 | @Disabled("Not supported yet") 45 | void connectTwoArgumentTest() { 46 | connections[0].send("CONNECT one two"); 47 | } 48 | 49 | @Test 50 | @Disabled("Not supported yet") 51 | void connectThreeArgumentTest() { 52 | connections[0].send("CONNECT one two three"); 53 | } 54 | 55 | @AfterEach 56 | void tearDown() { 57 | off(); 58 | } 59 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/server/HelpCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.server; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import utils.CommandClazzTest; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | @CommandClazzTest 11 | class HelpCommandTest extends ServerCommandBase { 12 | 13 | @BeforeEach 14 | void setUp() { 15 | init(); 16 | } 17 | 18 | @Test 19 | void helpNoSubjectTest() { 20 | connections[0].send("HELP"); 21 | assertArrayEquals(new String[]{ 22 | ":jircd-host 524 bob :No help available on this topic", 23 | }, connections[0].awaitMessage()); 24 | } 25 | 26 | @Test 27 | void helpTest() { 28 | connections[0].send("HELP subject"); 29 | assertArrayEquals(new String[]{ 30 | ":jircd-host 524 bob subject :No help available on this topic", 31 | }, connections[0].awaitMessage()); 32 | } 33 | 34 | @AfterEach 35 | void tearDown() { 36 | off(); 37 | } 38 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/server/InfoCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.server; 2 | 3 | import fr.enimaloc.jircd.Constant; 4 | import org.junit.jupiter.api.AfterEach; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import utils.CommandClazzTest; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | @CommandClazzTest 12 | class InfoCommandTest extends ServerCommandBase { 13 | 14 | @BeforeEach 15 | void setUp() { 16 | init(); 17 | } 18 | 19 | @Test 20 | void infoWithUnknownServerTest() { 21 | connections[0].send("INFO UnknownServer"); 22 | assertArrayEquals(new String[]{ 23 | ":jircd-host 402 bob UnknownServer :No such server" 24 | }, connections[0].awaitMessage()); 25 | } 26 | 27 | @Test 28 | void infoTest() { 29 | connections[0].send("INFO"); 30 | assertArrayEquals(new String[]{ 31 | ":jircd-host 371 :jircd v%s".formatted(Constant.VERSION), 32 | ":jircd-host 371 :by Antoine ", 33 | ":jircd-host 371 :Source code: https://github.com/enimaloc/jircd", 34 | ":jircd-host 374 :End of /INFO list" 35 | }, connections[0].awaitMessage(4)); 36 | } 37 | 38 | @AfterEach 39 | void tearDown() { 40 | off(); 41 | } 42 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/server/LUserCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.server; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import utils.CommandClazzTest; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 10 | 11 | @CommandClazzTest 12 | class LUserCommandTest extends ServerCommandBase { 13 | 14 | @BeforeEach 15 | void setUp() { 16 | init(); 17 | } 18 | 19 | @Test 20 | void luserTest() { 21 | connections[0].send("LUSER"); 22 | assertArrayEquals(new String[]{ 23 | ":jircd-host 251 bob :There are 1 users and 0 invisibles on 1 servers", 24 | ":jircd-host 252 bob 0 :operator(s) online", 25 | ":jircd-host 253 bob 0 :unknown connection(s)", 26 | ":jircd-host 254 bob 0 :channels formed", 27 | ":jircd-host 255 bob :I have 1 clients and 1 servers" 28 | }, connections[0].awaitMessage(5)); 29 | } 30 | 31 | @Test 32 | void luserTestWithUnknown() { 33 | addConnections(1); 34 | 35 | connections[0].send("LUSER"); 36 | assertArrayEquals(new String[]{ 37 | ":jircd-host 251 bob :There are 1 users and 0 invisibles on 1 servers", 38 | ":jircd-host 252 bob 0 :operator(s) online", 39 | ":jircd-host 253 bob 1 :unknown connection(s)", 40 | ":jircd-host 254 bob 0 :channels formed", 41 | ":jircd-host 255 bob :I have 2 clients and 1 servers" 42 | }, connections[0].awaitMessage(5)); 43 | } 44 | 45 | @Test 46 | void luserTestWithChannel() { 47 | connections[0].send("JOIN #jircd"); 48 | connections[0].ignoreMessage(3); 49 | 50 | assumeTrue(server.channels().size() == 1); 51 | 52 | connections[0].send("LUSER"); 53 | assertArrayEquals(new String[]{ 54 | ":jircd-host 251 bob :There are 1 users and 0 invisibles on 1 servers", 55 | ":jircd-host 252 bob 0 :operator(s) online", 56 | ":jircd-host 253 bob 0 :unknown connection(s)", 57 | ":jircd-host 254 bob 1 :channels formed", 58 | ":jircd-host 255 bob :I have 1 clients and 1 servers" 59 | }, connections[0].awaitMessage(5)); 60 | } 61 | 62 | @Test 63 | void luserTestWithInvisible() { 64 | assumeTrue(waitFor(() -> server.users().size() > 0)); 65 | server.users().get(0).modes().invisible(true); 66 | assumeTrue(server.users().get(0).modes().invisible()); 67 | 68 | connections[0].send("LUSER"); 69 | assertArrayEquals(new String[]{ 70 | ":jircd-host 251 bob :There are 0 users and 1 invisibles on 1 servers", 71 | ":jircd-host 252 bob 0 :operator(s) online", 72 | ":jircd-host 253 bob 0 :unknown connection(s)", 73 | ":jircd-host 254 bob 0 :channels formed", 74 | ":jircd-host 255 bob :I have 1 clients and 1 servers" 75 | }, connections[0].awaitMessage(5)); 76 | } 77 | 78 | @Test 79 | void luserTestWithOper() { 80 | connections[0].send("OPER " + baseSettings.operators().get(0).username() + " " + 81 | baseSettings.operators().get(0).password()); 82 | connections[0].ignoreMessage(); 83 | assumeTrue(waitFor(() -> server.users().size() > 0)); 84 | assumeTrue(server.users().get(0).modes().oper()); 85 | 86 | connections[0].send("LUSER"); 87 | assertArrayEquals(new String[]{ 88 | ":jircd-host 251 bob :There are 1 users and 0 invisibles on 1 servers", 89 | ":jircd-host 252 bob 1 :operator(s) online", 90 | ":jircd-host 253 bob 0 :unknown connection(s)", 91 | ":jircd-host 254 bob 0 :channels formed", 92 | ":jircd-host 255 bob :I have 1 clients and 1 servers" 93 | }, connections[0].awaitMessage(5)); 94 | } 95 | 96 | @AfterEach 97 | void tearDown() { 98 | off(); 99 | } 100 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/server/MotdCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.server; 2 | 3 | import fr.enimaloc.jircd.server.ServerSettings; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import org.junit.jupiter.api.AfterEach; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | import utils.CommandClazzTest; 11 | 12 | import static org.junit.jupiter.api.Assertions.*; 13 | import static org.junit.jupiter.api.Assumptions.assumeFalse; 14 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 15 | 16 | @CommandClazzTest 17 | class MotdCommandTest extends ServerCommandBase { 18 | 19 | @BeforeEach 20 | void setUp() { 21 | init(); 22 | } 23 | 24 | @Test 25 | void motdWithUnknownServerTest() { 26 | connections[0].send("MOTD UnknownServer"); 27 | assertArrayEquals(new String[]{ 28 | ":jircd-host 402 bob UnknownServer :No such server" 29 | }, connections[0].awaitMessage()); 30 | } 31 | 32 | @Test 33 | void motdWithNoMOTDTest() { 34 | assumeTrue(server.settings().motd().length == 0); 35 | connections[0].send("MOTD"); 36 | assertArrayEquals(new String[]{ 37 | ":jircd-host 422 bob :MOTD File is missing" 38 | }, connections[0].awaitMessage()); 39 | } 40 | 41 | @Test 42 | void motdTest() throws IOException { 43 | Path tempFile = Files.createTempFile("motd", ".txt"); 44 | Files.writeString(tempFile, "Custom motd set in temp file"); 45 | 46 | setSettings( 47 | baseSettings.copy(new ServerSettings.Builder().motdFile(tempFile).build(), field -> !field.getName().equals("motd"))); 48 | 49 | assumeFalse(server.settings().motd().length == 0); 50 | connections[0].send("MOTD"); 51 | assertArrayEquals(new String[]{ 52 | ":jircd-host 375 bob :- jircd-host Message of the day - ", 53 | ":jircd-host 372 bob :Custom motd set in temp file", 54 | ":jircd-host 376 bob :End of /MOTD command." 55 | }, connections[0].awaitMessage(3)); 56 | } 57 | 58 | @AfterEach 59 | void tearDown() { 60 | off(); 61 | } 62 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/server/ServerCommandBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ServerCommandBase 3 | * 4 | * 0.0.1 5 | * 6 | * 06/08/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.server; 9 | 10 | import fr.enimaloc.jircd.SocketBase; 11 | 12 | /** 13 | * 14 | */ 15 | public class ServerCommandBase extends SocketBase { 16 | 17 | @Override 18 | protected void init() { 19 | super.init(); 20 | connections[0].createUser("bob", "bobby", "Mobbye Plav"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/server/StatsCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.server; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.regex.Pattern; 5 | import org.junit.jupiter.api.AfterEach; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Disabled; 8 | import org.junit.jupiter.api.Test; 9 | import utils.CommandClazzTest; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | 13 | @CommandClazzTest 14 | class StatsCommandTest extends ServerCommandBase { 15 | 16 | @BeforeEach 17 | void setUp() { 18 | init(); 19 | } 20 | 21 | @Test 22 | @Disabled("No completed") 23 | void statsCTest() { 24 | connections[0].send("STATS c"); 25 | } 26 | 27 | @Test 28 | @Disabled("No completed") 29 | void statsHTest() { 30 | connections[0].send("STATS h"); 31 | } 32 | 33 | @Test 34 | @Disabled("No completed") 35 | void statsITest() { 36 | connections[0].send("STATS i"); 37 | } 38 | 39 | @Test 40 | @Disabled("No completed") 41 | void statsKTest() { 42 | connections[0].send("STATS k"); 43 | } 44 | 45 | @Test 46 | @Disabled("No completed") 47 | void statsLTest() { 48 | connections[0].send("STATS l"); 49 | } 50 | 51 | @Test 52 | void statsMTest() { 53 | connections[0].send("STATS m"); 54 | assertArrayEquals(new String[]{ 55 | ":jircd-host 212 ADMIN 0", 56 | ":jircd-host 212 AWAY 0", 57 | ":jircd-host 212 CONNECT 0", 58 | ":jircd-host 212 HELP 0", 59 | ":jircd-host 212 INFO 0", 60 | ":jircd-host 212 JOIN 0", 61 | ":jircd-host 212 KICK 0", 62 | ":jircd-host 212 KILL 0", 63 | ":jircd-host 212 LINKS 0", 64 | ":jircd-host 212 LIST 0", 65 | ":jircd-host 212 LUSER 0", 66 | ":jircd-host 212 MODE 0", 67 | ":jircd-host 212 MOTD 0", 68 | ":jircd-host 212 NAMES 0", 69 | ":jircd-host 212 NICK 1", 70 | ":jircd-host 212 NOTICE 0", 71 | ":jircd-host 212 OPER 0", 72 | ":jircd-host 212 PART 0", 73 | ":jircd-host 212 PASS 1", 74 | ":jircd-host 212 PING 0", 75 | ":jircd-host 212 PRIVMSG 0", 76 | ":jircd-host 212 QUIT 0", 77 | ":jircd-host 212 REHASH 0", 78 | ":jircd-host 212 RESTART 0", 79 | ":jircd-host 212 SQUIT 0", 80 | ":jircd-host 212 STATS 1", 81 | ":jircd-host 212 TIME 0", 82 | ":jircd-host 212 TOPIC 0", 83 | ":jircd-host 212 USER 1", 84 | ":jircd-host 212 USERHOST 0", 85 | ":jircd-host 212 VERSION 0", 86 | ":jircd-host 212 WALLOPS 0", 87 | ":jircd-host 212 WHO 0", 88 | ":jircd-host 212 WHOIS 0", 89 | ":jircd-host 212 WHOWAS 0", 90 | ":jircd-host 219 M :End of /STATS report" 91 | }, connections[0].awaitMessage(36)); 92 | } 93 | 94 | @Test 95 | @Disabled("No completed") 96 | void statsOTest() { 97 | connections[0].send("STATS o"); 98 | } 99 | 100 | @Test 101 | void statsUTest() { 102 | Pattern pat = Pattern.compile("^:jircd-host 242 :Server Up 0 days 0:[0-5][0-9]:[0-5][0-9]$"); 103 | 104 | connections[0].send("STATS u"); 105 | assertTrue(pat.matcher(connections[0].awaitMessage()[0]).matches()); 106 | assertEquals(":jircd-host 219 U :End of /STATS report", connections[0].awaitMessage()[0]); 107 | 108 | assertTrue(waitFor(72, TimeUnit.SECONDS)); 109 | connections[0].send("STATS u"); 110 | assertTrue(pat.matcher(connections[0].awaitMessage()[0]).matches()); 111 | assertEquals(":jircd-host 219 U :End of /STATS report", connections[0].awaitMessage()[0]); 112 | } 113 | 114 | @Test 115 | @Disabled("No completed") 116 | void statsYTest() { 117 | connections[0].send("STATS y"); 118 | } 119 | 120 | @AfterEach 121 | void tearDown() { 122 | off(); 123 | } 124 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/server/TimeCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.server; 2 | 3 | import java.time.ZonedDateTime; 4 | import java.time.format.DateTimeFormatter; 5 | import java.util.Locale; 6 | import org.junit.jupiter.api.AfterEach; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import utils.CommandClazzTest; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | 13 | @CommandClazzTest 14 | class TimeCommandTest extends ServerCommandBase { 15 | 16 | @BeforeEach 17 | void setUp() { 18 | init(); 19 | } 20 | 21 | @Test 22 | void timeWithUnknownServerTest() { 23 | connections[0].send("TIME UnknownServer"); 24 | assertArrayEquals(new String[]{ 25 | ":jircd-host 402 bob UnknownServer :No such server" 26 | }, connections[0].awaitMessage()); 27 | } 28 | 29 | @Test 30 | void timeTest() { 31 | connections[0].send("TIME"); 32 | 33 | assertArrayEquals(new String[]{ 34 | ":jircd-host 391 jircd-host :%s".formatted( 35 | DateTimeFormatter.ofPattern("EEEE LLLL dd yyyy - HH:mm O", Locale.ENGLISH) 36 | .format(ZonedDateTime.now())) 37 | }, connections[0].awaitMessage()); 38 | } 39 | 40 | @AfterEach 41 | void tearDown() { 42 | off(); 43 | } 44 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/server/VersionCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.server; 2 | 3 | import fr.enimaloc.jircd.Constant; 4 | import fr.enimaloc.jircd.server.attributes.SupportAttribute; 5 | import java.lang.reflect.ParameterizedType; 6 | import java.util.Arrays; 7 | import java.util.Map; 8 | import java.util.Optional; 9 | import java.util.OptionalInt; 10 | import org.junit.jupiter.api.AfterEach; 11 | import org.junit.jupiter.api.BeforeEach; 12 | import org.junit.jupiter.api.Test; 13 | import utils.CommandClazzTest; 14 | 15 | import static org.junit.jupiter.api.Assertions.*; 16 | 17 | @CommandClazzTest 18 | class VersionCommandTest extends ServerCommandBase { 19 | 20 | @BeforeEach 21 | void setUp() { 22 | init(); 23 | } 24 | 25 | @Test 26 | void versionWithUnknownServerTest() { 27 | connections[0].send("VERSION UnknownServer"); 28 | assertArrayEquals(new String[]{ 29 | ":jircd-host 402 bob UnknownServer :No such server" 30 | }, connections[0].awaitMessage()); 31 | } 32 | 33 | @Test 34 | void versionTest() { 35 | connections[0].send("VERSION"); 36 | 37 | assertArrayEquals(new String[]{ 38 | ":jircd-host 351 bob %s jircd-host :".formatted(Constant.VERSION) 39 | }, connections[0].awaitMessage()); 40 | 41 | SupportAttribute attr = server.supportAttribute(); 42 | for (int i = 0; i < attrLength; i++) { 43 | String[] messages = connections[0].awaitMessage(); 44 | if (messages.length == 0) { 45 | continue; 46 | } 47 | String isSupport = messages[0]; 48 | assertTrue(isSupport.startsWith(":jircd-host 005 bob ")); 49 | assertTrue(isSupport.endsWith(":are supported by this server")); 50 | isSupport = isSupport.replaceFirst(":jircd-host 005 bob ", "") 51 | .replace(" :are supported by this server", ""); 52 | 53 | String[] attributes = isSupport.split(" "); 54 | assertTrue(attributes.length <= 13); 55 | for (String attribute : attributes) { 56 | String key = attribute.contains("=") ? 57 | attribute.split("=")[0] : 58 | attribute; 59 | String value = attribute.contains("=") ? 60 | attribute.split("=")[1] : 61 | null; 62 | Map map = attr.asMap((s, o) -> s.equalsIgnoreCase(key)); 63 | String fieldName = (String) map.keySet().toArray()[0]; 64 | Object expectedValue = map.values().toArray()[0]; 65 | Class expectedClazz = null; 66 | Class actualClazz = null; 67 | Object actualValue = null; 68 | if (value != null) { 69 | try { 70 | actualValue = Integer.parseInt(value); 71 | actualClazz = Integer.class; 72 | } catch (NumberFormatException ignored) { 73 | if (value.equals("true") || value.equals("false")) { 74 | actualValue = Boolean.parseBoolean(value); 75 | actualClazz = Boolean.class; 76 | } else { 77 | if (value.contains(",") && Arrays.stream(value.split(",")).allMatch( 78 | s -> s.length() == 1)) { 79 | actualValue = value.toCharArray(); 80 | actualClazz = Character[].class; 81 | } else { 82 | actualValue = value; 83 | actualClazz = String.class; 84 | } 85 | } 86 | } 87 | } 88 | if (expectedValue instanceof Optional) { 89 | try { 90 | if (value == null) { 91 | actualClazz 92 | = (Class) ((ParameterizedType) SupportAttribute.class.getDeclaredField( 93 | fieldName).getGenericType()).getActualTypeArguments()[0]; 94 | expectedValue = null; 95 | } 96 | expectedClazz 97 | = (Class) ((ParameterizedType) SupportAttribute.class.getDeclaredField( 98 | fieldName).getGenericType()).getActualTypeArguments()[0]; 99 | expectedValue = expectedValue != null ? 100 | ((Optional) expectedValue).orElse(null) : 101 | null; 102 | } catch (NoSuchFieldException e) { 103 | fail(e); 104 | } 105 | } else if (expectedValue instanceof OptionalInt) { 106 | if (value == null) { 107 | actualClazz = Integer.class; 108 | expectedValue = null; 109 | } 110 | expectedClazz = Integer.class; 111 | // Is present is not detected by idea here 112 | //noinspection OptionalGetWithoutIsPresent 113 | expectedValue = expectedValue != null && ((OptionalInt) expectedValue).isPresent() ? 114 | ((OptionalInt) expectedValue).getAsInt() : 115 | null; 116 | } else { 117 | expectedClazz = expectedValue.getClass(); 118 | } 119 | 120 | assertEquals(expectedValue, actualValue); 121 | assertEquals(expectedClazz, actualClazz); 122 | } 123 | } 124 | } 125 | 126 | @AfterEach 127 | void tearDown() { 128 | off(); 129 | } 130 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/user/UserCommandBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * UserCommandBase 3 | * 4 | * 0.0.1 5 | * 6 | * 06/08/2022 7 | */ 8 | package fr.enimaloc.jircd.commands.user; 9 | 10 | import fr.enimaloc.jircd.SocketBase; 11 | import fr.enimaloc.jircd.user.User; 12 | import java.util.Optional; 13 | 14 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 15 | 16 | /** 17 | * 18 | */ 19 | public class UserCommandBase extends SocketBase { 20 | @Override 21 | protected void init() { 22 | super.init(); 23 | 24 | addConnections(3); 25 | connections[0].createUser("bob", "Mobbie Plav"); 26 | connections[1].createUser("fred", "Fred Bloggs"); 27 | connections[2].createUser("john", "John Doe"); 28 | connections[3].createUser("jane", "Jane Doe"); 29 | 30 | connections[1].oper(0); 31 | 32 | Optional johnOpt = server.users() 33 | .stream() 34 | .filter(u -> u.info().nickname().equals("john")) 35 | .findFirst(); 36 | assumeTrue(johnOpt.isPresent()); 37 | User john = johnOpt.get(); 38 | john.info().setHost("enimaloc.fr"); 39 | 40 | Optional janeOpt = server.users() 41 | .stream() 42 | .filter(u -> u.info().nickname().equals("jane")) 43 | .findFirst(); 44 | assumeTrue(janeOpt.isPresent()); 45 | User jane = janeOpt.get(); 46 | jane.away("Away"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/user/WhoCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.user; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import utils.CommandClazzTest; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | @CommandClazzTest 11 | class WhoCommandTest extends UserCommandBase { 12 | 13 | @BeforeEach 14 | void setUp() { 15 | super.init(); 16 | } 17 | 18 | @Test 19 | void whoWithChannelTest() { 20 | connections[0].send("JOIN #bob"); 21 | connections[0].awaitMessage(3); 22 | 23 | connections[1].send("JOIN #bob"); 24 | connections[1].awaitMessage(3); 25 | connections[0].awaitMessage(); 26 | 27 | connections[2].send("JOIN #bob"); 28 | connections[2].awaitMessage(3); 29 | connections[0].awaitMessage(); 30 | connections[1].awaitMessage(); 31 | 32 | connections[3].send("JOIN #bob"); 33 | connections[3].awaitMessage(3); 34 | connections[0].awaitMessage(); 35 | connections[1].awaitMessage(); 36 | connections[2].awaitMessage(); 37 | 38 | connections[0].send("WHO #bob"); 39 | assertArrayEquals(new String[]{ 40 | ":jircd-host 352 bob #bob bob 127.0.0.1 jircd-host bob H~ :0 Mobbie Plav", 41 | ":jircd-host 352 bob #bob fred 127.0.0.1 jircd-host fred H* :0 Fred Bloggs", 42 | ":jircd-host 352 bob #bob john enimaloc.fr jircd-host john H :0 John Doe", 43 | ":jircd-host 352 bob #bob jane 127.0.0.1 jircd-host jane G :0 Jane Doe", 44 | ":jircd-host 315 bob #bob :End of /WHO list" 45 | }, connections[0].awaitMessage(5)); 46 | } 47 | 48 | @Test 49 | void whoWhitUserTest() { 50 | connections[0].send("JOIN #bob"); 51 | connections[0].awaitMessage(3); 52 | 53 | connections[0].send("WHO bob"); 54 | assertArrayEquals(new String[]{ 55 | ":jircd-host 352 bob #bob bob 127.0.0.1 jircd-host bob H~ :0 Mobbie Plav", 56 | ":jircd-host 315 bob bob :End of /WHO list" 57 | }, connections[0].awaitMessage(2)); 58 | 59 | connections[0].send("WHO fred"); 60 | assertArrayEquals(new String[]{ 61 | ":jircd-host 352 bob * fred 127.0.0.1 jircd-host fred H* :0 Fred Bloggs", 62 | ":jircd-host 315 bob fred :End of /WHO list" 63 | }, connections[0].awaitMessage(2)); 64 | } 65 | 66 | @Test 67 | void whoWithMaskTest() { 68 | connections[0].send("JOIN #bob"); 69 | connections[0].awaitMessage(3); 70 | 71 | connections[2].send("JOIN #bob"); 72 | connections[2].awaitMessage(3); 73 | connections[0].awaitMessage(); 74 | 75 | connections[0].send("WHO *"); 76 | assertArrayEquals(new String[]{ 77 | ":jircd-host 352 bob #bob bob 127.0.0.1 jircd-host bob H~ :0 Mobbie Plav", 78 | ":jircd-host 352 bob * fred 127.0.0.1 jircd-host fred H* :0 Fred Bloggs", 79 | ":jircd-host 352 bob #bob john enimaloc.fr jircd-host john H :0 John Doe", 80 | ":jircd-host 352 bob * jane 127.0.0.1 jircd-host jane G :0 Jane Doe", 81 | ":jircd-host 315 bob * :End of /WHO list" 82 | }, connections[0].awaitMessage(5)); 83 | } 84 | 85 | @AfterEach 86 | void tearDown() { 87 | off(); 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/commands/user/WhowasCommandTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.commands.user; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import utils.CommandClazzTest; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | @CommandClazzTest 11 | class WhowasCommandTest extends UserCommandBase { 12 | 13 | @BeforeEach 14 | void setUp() { 15 | init(); 16 | } 17 | 18 | @Test 19 | void whowasTest() { 20 | connections[0].send("WHOWAS joe"); 21 | assertArrayEquals(new String[]{ 22 | ":jircd-host 406 bob :There was no such nickname", 23 | ":jircd-host 369 bob joe :End of WHOWAS" 24 | }, connections[0].awaitMessage(2)); 25 | } 26 | 27 | @AfterEach 28 | void tearDown() { 29 | off(); 30 | } 31 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/message/MaskTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.message; 2 | 3 | import java.util.regex.Pattern; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.*; 7 | 8 | class MaskTest { 9 | 10 | @Test 11 | void toRegex() { 12 | assertEquals("\\Q?\\E", new Mask("\\?").toRegex()); 13 | assertEquals(".", new Mask("?").toRegex()); 14 | assertEquals(".*", new Mask("*").toPattern().toString()); 15 | assertEquals("\\Qj\\E\\Qi\\E\\Qr\\E\\Qc\\E\\Qd\\E", new Mask("jircd").toPattern().toString()); 16 | } 17 | 18 | @Test 19 | void toPattern() { 20 | assertTrue(patternEquals(Pattern.compile("\\Q?\\E"), new Mask("\\?").toPattern())); 21 | assertTrue(patternEquals(Pattern.compile("."), new Mask("?").toPattern())); 22 | assertTrue(patternEquals(Pattern.compile(".*"), new Mask("*").toPattern())); 23 | assertTrue(patternEquals(Pattern.compile("\\Qj\\E\\Qi\\E\\Qr\\E\\Qc\\E\\Qd\\E"), new Mask("jircd").toPattern())); 24 | } 25 | 26 | boolean patternEquals(Pattern p1, Pattern p2) { 27 | return p1.pattern().equals(p2.pattern()) && p1.flags() == p2.flags() && p1.toString().equals(p2.toString()); 28 | } 29 | } -------------------------------------------------------------------------------- /src/test/java/fr/enimaloc/jircd/user/UserModesTest.java: -------------------------------------------------------------------------------- 1 | package fr.enimaloc.jircd.user; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class UserModesTest { 8 | 9 | @Test 10 | void invisible() { 11 | UserModes userModes = new UserModes(); 12 | assertFalse(userModes.invisible()); 13 | userModes.invisible(true); 14 | assertTrue(userModes.invisible()); 15 | assertEquals("i", userModes.toString()); 16 | assertEquals("", userModes.prefix()); 17 | } 18 | 19 | @Test 20 | void oper() { 21 | UserModes userModes = new UserModes(); 22 | assertFalse(userModes.oper()); 23 | userModes.oper(true); 24 | assertTrue(userModes.oper()); 25 | assertEquals("o", userModes.toString()); 26 | assertEquals("@", userModes.prefix()); 27 | } 28 | 29 | @Test 30 | void registered() { 31 | UserModes userModes = new UserModes(); 32 | assertFalse(userModes.registered()); 33 | userModes.registered(true); 34 | assertTrue(userModes.registered()); 35 | assertEquals("r", userModes.toString()); 36 | assertEquals("", userModes.prefix()); 37 | } 38 | 39 | @Test 40 | void localOper() { 41 | UserModes userModes = new UserModes(); 42 | assertFalse(userModes.localOper()); 43 | userModes.localOper(true); 44 | assertTrue(userModes.localOper()); 45 | assertEquals("O", userModes.toString()); 46 | assertEquals("@", userModes.prefix()); 47 | } 48 | 49 | @Test 50 | void wallops() { 51 | UserModes userModes = new UserModes(); 52 | assertFalse(userModes.wallops()); 53 | userModes.wallops(true); 54 | assertTrue(userModes.wallops()); 55 | assertEquals("w", userModes.toString()); 56 | assertEquals("", userModes.prefix()); 57 | } 58 | } -------------------------------------------------------------------------------- /src/test/java/utils/CommandClazzTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * CommandTest 3 | * 4 | * 0.0.1 5 | * 6 | * 08/08/2022 7 | */ 8 | package utils; 9 | 10 | import java.lang.annotation.ElementType; 11 | import java.lang.annotation.Retention; 12 | import java.lang.annotation.RetentionPolicy; 13 | import java.lang.annotation.Target; 14 | import org.junit.jupiter.api.DisplayNameGeneration; 15 | 16 | /** 17 | * 18 | */ 19 | 20 | @Retention(RetentionPolicy.RUNTIME) 21 | @Target({ElementType.TYPE}) 22 | @DisplayNameGeneration(CommandNameGen.class) 23 | public @interface CommandClazzTest { 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/utils/CommandNameGen.java: -------------------------------------------------------------------------------- 1 | /* 2 | * CommandNameTest 3 | * 4 | * 0.0.1 5 | * 6 | * 08/08/2022 7 | */ 8 | package utils; 9 | 10 | import java.lang.reflect.Method; 11 | import org.junit.jupiter.api.DisplayNameGenerator; 12 | 13 | /** 14 | * 15 | */ 16 | public class CommandNameGen implements DisplayNameGenerator { 17 | @Override 18 | public String generateDisplayNameForClass(Class testClass) { 19 | return testClass.getSimpleName().endsWith("CommandTest") 20 | ? testClass.getSimpleName().substring(0, testClass.getSimpleName().length() - "CommandTest".length()).toUpperCase() 21 | : testClass.getSimpleName(); 22 | } 23 | 24 | @Override 25 | public String generateDisplayNameForNestedClass(Class nestedClass) { 26 | return generateDisplayNameForClass(nestedClass); 27 | } 28 | 29 | @Override 30 | public String generateDisplayNameForMethod(Class testClass, Method testMethod) { 31 | Class clazz = testClass; 32 | while (clazz != clazz.getNestHost()) { 33 | clazz = clazz.getNestHost(); 34 | } 35 | String clazzName = generateDisplayNameForClass(clazz); 36 | String methodName = testMethod.getName(); 37 | if (methodName.toLowerCase().startsWith(clazzName.toLowerCase())) { 38 | methodName = methodName.substring(clazzName.length()); 39 | } 40 | if (methodName.toLowerCase().endsWith("test")) { 41 | methodName = methodName.substring(0, methodName.length() - "test".length()); 42 | } 43 | StringBuilder sb = new StringBuilder(); 44 | for (int i = 0; i < methodName.length(); i++) { 45 | char c = methodName.charAt(i); 46 | if ((Character.isUpperCase(c) || Character.isDigit(c)) && i > 0) { 47 | sb.append(' '); 48 | c = Character.toLowerCase(c); 49 | } 50 | sb.append(c); 51 | } 52 | return sb.isEmpty() ? "Default" : sb.toString(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/utils/FullModuleTest.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | import org.junit.jupiter.api.Tag; 8 | import org.junit.jupiter.api.Test; 9 | 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) 12 | @Tag("fullModule") 13 | @Test 14 | public @interface FullModuleTest { 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/test/java/utils/ListUtils.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import java.util.List; 4 | import java.util.Random; 5 | 6 | public class ListUtils { 7 | 8 | public static T getRandom(List list) { 9 | return !list.isEmpty() ? list.get(new Random().nextInt(list.size()-1)) : null; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 1.0.0 2 | --------------------------------------------------------------------------------