M .builds/alpine.yml => .builds/alpine.yml +9 -1
@@ 13,6 13,14 @@ tasks:
cd rirc
git submodule init
git submodule update --recursive
+ export MAKEFLAGS='-j $(nproc)'
- build: |
cd rirc
- make rirc rirc.debug test
+ make clean
+ make all
+ make check
+
+triggers:
+ - action: email
+ condition: failure
+ to: mail+sourcehut+builds@rcr.io
M .builds/debian.yml => .builds/debian.yml +14 -28
@@ 10,9 10,6 @@ packages:
sources:
- https://git.sr.ht/~rcr/rirc
-environment:
- SONAR_VER: 4.4.0.2170
-
secrets:
- 8c2439c9-5f91-4b19-b3c3-33d82f1b861f
- a58d0951-f57f-44ef-8ef2-25f2f84f0e89
@@ 22,36 19,25 @@ tasks:
cd rirc
git submodule init
git submodule update --recursive
+ export MAKEFLAGS='-j $(nproc)'
- build: |
cd rirc
- make rirc rirc.debug test
+ make clean
+ make all
+ make check
- static-analysis: |
cd rirc
- branch=$(git name-rev --name-only HEAD)
- [ $branch = "remotes/origin/static_analysis" ] || complete-build
+ [ $(git name-rev --name-only HEAD) = "remotes/origin/static_analysis" ] || complete-build
set +x
source ~/export_coverity
source ~/export_sonarscan
set -x
- # Coverity
- ./scripts/coverity_get.sh coverity
- ./scripts/coverity_run.sh coverity
- # Sonarcloud
- curl -o build-wrapper.zip https://sonarcloud.io/static/cpp/build-wrapper-linux-x86.zip
- curl -o sonar-scanner.zip https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_VER-linux.zip
- unzip build-wrapper.zip
- unzip sonar-scanner.zip
- ./scripts/coverage.sh
- echo >> sonar-project.properties "sonar.branch.name=$branch"
- echo >> sonar-project.properties "sonar.cfamily.build-wrapper-output=bw-output"
- echo >> sonar-project.properties "sonar.cfamily.cache.enabled=false"
- echo >> sonar-project.properties "sonar.cfamily.gcov.reportsPath=."
- echo >> sonar-project.properties "sonar.cfamily.threads=1"
- echo >> sonar-project.properties "sonar.coverage.exclusions=test"
- echo >> sonar-project.properties "sonar.host.url=https://sonarcloud.io"
- echo >> sonar-project.properties "sonar.organization=rirc"
- echo >> sonar-project.properties "sonar.projectKey=rirc"
- echo >> sonar-project.properties "sonar.sources=src"
- echo >> sonar-project.properties "sonar.tests=test"
- ./build-wrapper-linux-x86/build-wrapper-linux-x86-64 --out-dir bw-output make clean rirc.debug test
- ./sonar-scanner-$SONAR_VER-linux/bin/sonar-scanner
+ ./scripts/sa_coverity_get.sh coverity
+ ./scripts/sa_coverity_run.sh coverity
+ ./scripts/sa_sonarcloud_get.sh sonarcloud
+ ./scripts/sa_sonarcloud_run.sh sonarcloud
+
+triggers:
+ - action: email
+ condition: failure
+ to: mail+sourcehut+builds@rcr.io
M .gitignore => .gitignore +1 -0
@@ 6,6 6,7 @@
*.t
*.td
.clangd
+.cache
bld
compile_commands.json
config.h
M CHANGELOG => CHANGELOG +15 -0
@@ 2,6 2,21 @@
Summary of notable changes and features
## Unreleased (dev)
+### Features
+ - add IRCv3 CAP account-notify
+ - add ACCOUNT_THRESHOLD define to config.def.h
+ - add IRCv3 CAP away-notify
+ - add AWAY_THRESHOLD define to config.def.h
+ - add IRCv3 CAP chghost
+ - add CHGHOST_THRESHOLD define to config.def.h
+ - add IRCv3 CAP invite-notify
+ - add IRCv3 CAP extended-join
+ - add action message colouring config
+ - add ACTION_FG define to config.def.h
+ - add ACTION_BG define to config.def.h
+### Fixes
+ - fix incorrect INVITE message handling
+ - fix message filter thresholds
## [0.1.3]
### Features
M LICENSE => LICENSE +1 -1
@@ 1,4 1,4 @@
-Copyright (C) 2014-2020 Richard Robbins <mail@rcr.io>
+Copyright (C) 2014-2021 Richard Robbins <mail@rcr.io>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
M Makefile => Makefile +31 -20
@@ 28,6 28,8 @@ DIR_B := bld
DIR_S := src
DIR_T := test
+PWD := $(shell pwd)
+
SRC := $(shell find $(DIR_S) -name '*.c')
SUBDIRS += $(shell find $(DIR_S) -name '*.c' -exec dirname {} \; | sort -u)
@@ 44,27 46,34 @@ OBJS_T += $(DIR_B)/utils/tree.t # Header only file
OBJS_G := $(patsubst %.gperf, %.gperf.out, $(SRC_G))
# Release build executable
-$(BIN_R): $(TLS_LIBS) $(DIR_B) $(OBJS_G) $(OBJS_R)
+$(BIN_R): $(TLS_LIBS) $(OBJS_G) $(OBJS_R)
@echo cc $@
@$(CC) $(LDFLAGS) -o $@ $(OBJS_R) $(TLS_LIBS)
# Debug build executable
-$(BIN_D): $(TLS_LIBS) $(DIR_B) $(OBJS_G) $(OBJS_D)
+$(BIN_D): $(TLS_LIBS) $(OBJS_G) $(OBJS_D)
@echo cc $@
@$(CC) $(LDFLAGS) -o $@ $(OBJS_D) $(TLS_LIBS)
# Release build objects
-$(DIR_B)/%.o: $(DIR_S)/%.c config.h
+$(DIR_B)/%.o: $(DIR_S)/%.c config.h | $(DIR_B)
@echo "cc $<..."
- @$(PP) $(CFLAGS_R) -MM -MP -MT $@ -MF $(@:.o=.d) $<
+ @$(PP) $(CFLAGS_R) -MM -MP -MT $@ -MF $(@:.o=.o.d) $<
@$(CC) $(CFLAGS_R) -c -o $@ $<
# Debug build objects
-$(DIR_B)/%.db.o: $(DIR_S)/%.c config.h
+$(DIR_B)/%.db.o: $(DIR_S)/%.c config.h | $(DIR_B)
@echo "cc $<..."
- @$(PP) $(CFLAGS_D) -MM -MP -MT $@ -MF $(@:.o=.d) $<
+ @$(PP) $(CFLAGS_D) -MM -MP -MT $@ -MF $(@:.o=.o.d) $<
@$(CC) $(CFLAGS_D) -c -o $@ $<
+# Testcases
+$(DIR_B)/%.t: $(DIR_T)/%.c $(OBJS_G) | $(DIR_B)
+ @$(PP) $(CFLAGS_D) -MM -MP -MT $@ -MF $(@:.t=.t.d) $<
+ @$(CC) $(CFLAGS_D) -c -o $(@:.t=.t.o) $<
+ @$(CC) $(CFLAGS_D) -o $@ $(@:.t=.t.o)
+ @$(TEST_EXT) ./$@
+
# Default config file
config.h:
cp config.def.h config.h
@@ 73,24 82,26 @@ config.h:
%.gperf.out: %.gperf
gperf --output-file=$@ $<
-# Testcase files
-$(DIR_B)/%.t: $(DIR_T)/%.c
- @$(PP) $(CFLAGS_D) -MM -MP -MT $@ -MF $(@:.t=.d) $<
- @$(CC) $(CFLAGS_D) $(LDFLAGS) -o $@ $<
- -@rm -f $(@:.t=.td) && $(TEST_EXT) ./$@ || mv $@ $(@:.t=.td)
- @[ ! -f $(@:.t=.td) ]
-
# Build directories
$(DIR_B):
@for dir in $(patsubst $(DIR_S)/%, %, $(SUBDIRS)); do mkdir -p $(DIR_B)/$$dir; done
# TLS libraries
$(TLS_LIBS): $(TLS_CONF)
- @CFLAGS="$(TLS_INCL)" $(MAKE) -C ./lib/mbedtls clean lib
+ @CFLAGS="$(TLS_INCL)" $(MAKE) --silent -C ./lib/mbedtls clean
+ @CFLAGS="$(TLS_INCL)" $(MAKE) --silent -C ./lib/mbedtls lib
+
+all:
+ @$(MAKE) --silent $(TLS_LIBS)
+ @$(MAKE) --silent $(BIN_R)
+ @$(MAKE) --silent $(BIN_D)
+
+check:
+ @$(MAKE) --silent $(OBJS_T)
clean:
rm -rf $(DIR_B) $(BIN_R) $(BIN_D)
- find . -name "*gperf.out" -print0 | xargs -0 -I % rm %
+ @find . -name "*gperf.out" -print0 | xargs -0 -I % rm -rfv %
install: $(BIN_R)
@echo installing executable to $(BIN_DIR)
@@ 105,10 116,10 @@ uninstall:
rm -f $(BIN_DIR)/rirc
rm -f $(MAN_DIR)/rirc.1
-test: $(DIR_B) $(OBJS_G) $(OBJS_T)
+-include $(OBJS_R:.o=.o.d)
+-include $(OBJS_D:.o=.o.d)
+-include $(OBJS_T:.t=.t.d)
--include $(OBJS_R:.o=.d)
--include $(OBJS_D:.o=.d)
--include $(OBJS_T:.t=.d)
+.PHONY: all check clean install uninstall
-.PHONY: clean install uninstall test
+.PRECIOUS: $(OBJS_T)
M README.md => README.md +2 -3
@@ 93,7 93,7 @@ Commands:
```
:clear
:close
- :connect [host [port] [pass] [user] [real]]
+ :connect
:disconnect
:quit
```
@@ 105,7 105,6 @@ Keys:
^P : go to previous channel
^L : clear channel
^X : close channel
- ^F : find channel
^C : cancel input/action
^U : scroll buffer up
^D : scroll buffer down
@@ 117,4 116,4 @@ Keys:
### More info:
-[rcr.io/rirc/](http://rcr.io/rirc/)
+[https://rcr.io/rirc/](http://rcr.io/rirc/)
M config.def.h => config.def.h +11 -4
@@ 16,12 16,15 @@
#define DEFAULT_USERNAME ""
#define DEFAULT_REALNAME ""
-/* User count in channel before filtering JOIN/PART/QUIT messages
+/* User count in channel before filtering message types
* Integer
* (0: no filtering) */
-#define JOIN_THRESHOLD 0
-#define PART_THRESHOLD 0
-#define QUIT_THRESHOLD 0
+#define FILTER_THRESHOLD_JOIN 0
+#define FILTER_THRESHOLD_PART 0
+#define FILTER_THRESHOLD_QUIT 0
+#define FILTER_THRESHOLD_ACCOUNT 0
+#define FILTER_THRESHOLD_AWAY 0
+#define FILTER_THRESHOLD_CHGHOST 0
/* Message sent for PART and QUIT by default */
#define DEFAULT_QUIT_MESG "rirc v" VERSION
@@ 61,6 64,10 @@
#define INPUT_PREFIX_FG 239
#define INPUT_PREFIX_BG -1
+/* Action message */
+#define ACTION_FG -1
+#define ACTION_BG 239
+
/* Input line text colours */
#define INPUT_FG 250
#define INPUT_BG -1
M lib/mbedtls => lib/mbedtls +1 -1
@@ 1,1 1,1 @@
-Subproject commit 523f0554b6cdc7ace5d360885c3f5bbcc73ec0e8
+Subproject commit 1c54b5410fd48d6bcada97e30cac417c5c7eea67
M rirc.1 => rirc.1 +1 -2
@@ 68,7 68,6 @@ rirc is controlled by a combination of key bindings and commands, where:
.tab(;);
lb l .
Keys:
- ^F;find channel
^N;go to next channel
^P;go to previous channel
^C;cancel current input/action
@@ 94,7 93,7 @@ lb l .
Commands:
:clear;
:close;
- :connect;[host [port] [pass] [user] [real]]
+ :connect;
:disconnect;
:quit;
.TE
M scripts/compile_commands.sh => scripts/compile_commands.sh +2 -2
@@ 2,9 2,9 @@
set -e
-rm -f compile_commands.json
-
export CC=clang
export CC_EXT="-Wno-empty-translation-unit"
+rm -f compile_commands.json
+
bear make clean rirc.debug
M scripts/coverage.sh => scripts/coverage.sh +32 -23
@@ 5,34 5,46 @@ set -e
CDIR="coverage"
export CC=gcc
-export CC_EXT="-fprofile-arcs -ftest-coverage"
+export CC_EXT="-fprofile-arcs -ftest-coverage -fprofile-abs-path"
export LD_EXT="-fprofile-arcs"
-make -e clean test
+export MAKEFLAGS="-e -j $(nproc)"
-rm -rf $CDIR
-mkdir -p $CDIR
+rm -rf $CDIR && mkdir -p $CDIR
-find . -name "*.gcno" -print0 | xargs -0 -I % mv % $CDIR
-find . -name "*.gcda" -print0 | xargs -0 -I % mv % $CDIR
+make clean
+make check
-FILTER=$(cat <<'EOF'
+GCNO=$(find bld -name '*.t.gcno')
+
+FILTER=$(cat << 'EOF'
{
+ use Cwd;
+ @results;
+ @result_ds;
+ @result_fs;
if (eof()) {
- $cov = ($lc / $lt) * 100.0;
- printf("~\n");
- printf("~ total %21d/%d %7.2f%%\n", $lc, $lt, $cov);
+ print "- Coverage:";
+ print "- ", "=" x 40;
+ print "- $_", for sort(@result_fs);
+ print "- $_", for sort(@result_ds);
+ print "- ", "=" x 40;
+ printf("- Total %20d/%d %6.2f%%\n", $lc, $lt, (($lc / $lt) * 100.0));
} elsif ($p) {
chomp $file;
chomp $_;
$file =~ s/'//g;
- my @s1 = split / /, $file;
- my @s2 = split /:/, $_;
- my @s3 = split / /, $s2[1];
- chop($s3[0]);
- printf("%-30s%4s %7s%%\n", $s1[1], $s3[2], $s3[0]);
- $lt = $lt + $s3[2];
- $lc = $lc + $s3[2] * ($s3[0] / 100.0);
+ $file = substr($file, (length(getcwd()) + 6));
+ @s1 = split /:/, $_;
+ @s2 = split / /, $s1[1];
+ $lt = $lt + $s2[2];
+ $lc = $lc + $s2[2] * ($s2[0] / 100.0);
+ $result = sprintf("%-25s %4s %7s", $file, $s2[2], $s2[0]);
+ if ($file =~ m|src/.*/.*|) {
+ push @result_fs, $result;
+ } else {
+ push @result_ds, $result;
+ }
$p = 0;
}
$p++ if /^File.*src.*c'/;
@@ 41,13 53,10 @@ FILTER=$(cat <<'EOF'
EOF
)
-echo "~ Coverage:"
-
-gcov -pr $CDIR/*.gcno | perl -ne "$FILTER" | sort
+gcov --preserve-paths $GCNO | perl -lne "$FILTER"
-find . -name "*gperf*.gcov" -print0 | xargs -0 -I % rm %
-find . -name "*test#*.gcov" -print0 | xargs -0 -I % rm %
+mv *.gcov $CDIR
if [ -x "$(command -v gcovr)" ]; then
- gcovr -r . --html --html-details --filter "src.*c$" -o $CDIR/index.html
+ gcovr -r . --html --html-details --filter ".*src.*c$" -o $CDIR/index.html
fi
M scripts/pre-commit.sh => scripts/pre-commit.sh +1 -1
@@ 5,7 5,7 @@
echo "Running pre-commit hook..."
-RESULTS=$(make test)
+RESULTS=$(make check)
if [[ "$RESULTS" == *"failure"* ]];
then
R scripts/coverity_get.sh => scripts/sa_coverity_get.sh +2 -4
@@ 17,11 17,9 @@ COVERITY_TGZ="$1/coverity_tool.tgz"
mkdir "$1"
-echo "curl https://scan.coverity.com/download/linux64 ..."
-
-curl -fs --show-error https://scan.coverity.com/download/linux64 -o "$COVERITY_TGZ" --data "token=$COVERITY_TOKEN&project=rcr%2Frirc"
curl -fs --show-error https://scan.coverity.com/download/linux64 -o "$COVERITY_MD5" --data "token=$COVERITY_TOKEN&project=rcr%2Frirc&md5=1"
+curl -fs --show-error https://scan.coverity.com/download/linux64 -o "$COVERITY_TGZ" --data "token=$COVERITY_TOKEN&project=rcr%2Frirc"
-printf "%s\t$COVERITY_TGZ" "$(cat "$COVERITY_MD5")" | md5sum -c -
+printf "%s\t$COVERITY_TGZ" "$(cat "$COVERITY_MD5")" | md5sum --quiet -c -
tar xzf "$COVERITY_TGZ" -C "$1" --strip-components 1
R scripts/coverity_run.sh => scripts/sa_coverity_run.sh +1 -1
@@ 21,7 21,7 @@ COVERITY_TAR="cov-int.tgz"
VERSION=$(git rev-parse --short HEAD)
-PATH=$(pwd)/$1/bin:$PATH cov-build --dir "$COVERITY_OUT" make clean rirc rirc.debug test
+PATH=$(pwd)/$1/bin:$PATH cov-build --dir "$COVERITY_OUT" make clean all check
tar czf "$COVERITY_TAR" "$COVERITY_OUT"
A scripts/sa_sonarcloud_get.sh => scripts/sa_sonarcloud_get.sh +30 -0
@@ 0,0 1,30 @@
+#!/bin/bash
+
+set -e
+
+fail() { >&2 printf "%s\n" "$*"; exit 1; }
+
+if [[ -z $1 ]]; then
+ fail "Usage: '$0 dir'"
+fi
+
+SONAR_VER="4.5.0.2216"
+
+BUILD_ZIP="$1/build-wrapper.zip"
+SONAR_ZIP="$1/sonar-scanner.zip"
+SONAR_MD5="$1/sonar-scanner.md5"
+
+BUILD_ZIP_URL="https://sonarcloud.io/static/cpp/build-wrapper-linux-x86.zip"
+SONAR_ZIP_URL="https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_VER-linux.zip"
+SONAR_MD5_URL="https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_VER-linux.zip.md5"
+
+mkdir "$1"
+
+curl -fs --show-error "$BUILD_ZIP_URL" -o "$BUILD_ZIP"
+curl -fs --show-error "$SONAR_ZIP_URL" -o "$SONAR_ZIP"
+curl -fs --show-error "$SONAR_MD5_URL" -o "$SONAR_MD5"
+
+printf "%s\t$SONAR_ZIP" "$(cat "$SONAR_MD5")" | md5sum --quiet -c -
+
+unzip -qq "$BUILD_ZIP" -d "$1"
+unzip -qq "$SONAR_ZIP" -d "$1"
A scripts/sa_sonarcloud_run.sh => scripts/sa_sonarcloud_run.sh +66 -0
@@ 0,0 1,66 @@
+#!/bin/bash
+
+# FIXME: undo the long live branch pattern in
+# https://sonarcloud.io/project/branches?id=rirc
+
+set -e
+
+fail() { >&2 printf "%s\n" "$*"; exit 1; }
+
+if [[ -z $1 ]]; then
+ fail "Usage: '$0 dir'"
+fi
+
+SONAR_VER="4.5.0.2216"
+
+BUILD_WRAPPER_BIN="$1/build-wrapper-linux-x86/build-wrapper-linux-x86-64"
+SONAR_SCANNER_BIN="$1/sonar-scanner-$SONAR_VER-linux/bin/sonar-scanner"
+
+BUILD_WRAPPER_OUT="$1/bw-out"
+
+SONAR_SCANNER_CONF="sonar-project.properties"
+
+if [[ ! -f "$BUILD_WRAPPER_BIN" ]]; then
+ fail "missing build-wrapper binary"
+fi
+
+if [[ ! -f "$SONAR_SCANNER_BIN" ]]; then
+ fail "missing sonar-scanner binary"
+fi
+
+# FIXME:
+# "the branch or pull request parameter is missing"
+
+# FIXME: just forget about coverage until it works properly with llvm or something
+# ive wasted enough time on this...
+# add test coverage to README or something? or use something else like coveralls?
+
+cat << EOF >> "$SONAR_SCANNER_CONF"
+# Server
+sonar.host.url = https://sonarcloud.io
+
+# Project
+sonar.organization = rirc
+sonar.projectKey = rirc
+sonar.projectName = rirc
+sonar.projectVersion = $(git rev-parse --short HEAD)
+sonar.branch.name = $(git rev-parse --symbolic-full-name HEAD)
+sonar.links.homepage = https://rcr.io/rirc/
+sonar.links.scm = https://git.sr.ht/~rcr/rirc/
+sonar.links.ci = https://builds.sr.ht/~rcr/rirc/
+
+# Source
+sonar.sources = src,test
+
+# C
+sonar.cfamily.build-wrapper-output = $BUILD_WRAPPER_OUT
+sonar.cfamily.cache.enabled = false
+sonar.cfamily.threads = $(nproc)
+EOF
+
+make clean
+
+eval "$BUILD_WRAPPER_BIN --out-dir $BUILD_WRAPPER_OUT make all check"
+eval "$SONAR_SCANNER_BIN"
+
+rm -f "$SONAR_SCANNER_CONF"
M scripts/sanitizers_build.sh => scripts/sanitizers_build.sh +1 -1
@@ 11,7 11,7 @@ make -e clean rirc.debug
mv rirc.debug rirc.debug.address
-export CC_EXT="-fsanitize=address,undefined -fno-omit-frame-pointer"
+export CC_EXT="-fsanitize=thread,undefined -fno-omit-frame-pointer"
export LD_EXT="-fsanitize=thread,undefined"
make -e clean rirc.debug
M scripts/sanitizers_test.sh => scripts/sanitizers_test.sh +2 -3
@@ 3,11 3,10 @@
set -e
export CC=clang
-
export CC_EXT="-fsanitize=address,undefined -fno-omit-frame-pointer"
-export LD_EXT="-fsanitize=address,undefined"
+export LD_EXT="-fsanitize=address,undefined -fuse-ld=lld"
# for core dumps:
# export ASAN_OPTIONS="abort_on_error=1:disable_coredump=0"
-make -e test
+make -e clean check
M src/components/buffer.c => src/components/buffer.c +4 -1
@@ 1,6 1,9 @@
+#include "src/components/buffer.h"
+
#include <string.h>
-#include "src/components/buffer.h"
+#include "config.h"
+#include "src/utils/utils.h"
#define BUFFER_MASK(X) ((X) & (BUFFER_LINES_MAX - 1))
M src/components/buffer.h => src/components/buffer.h +2 -5
@@ 1,11 1,8 @@
-#ifndef BUFFER_H
-#define BUFFER_H
+#ifndef RIRC_COMPONENTS_BUFFER_H
+#define RIRC_COMPONENTS_BUFFER_H
#include <time.h>
-#include "src/utils/utils.h"
-#include "config.h"
-
#define TEXT_LENGTH_MAX 510 /* FIXME: remove max lengths in favour of growable buffer */
#define FROM_LENGTH_MAX 100
M src/components/channel.c => src/components/channel.c +6 -1
@@ 1,8 1,9 @@
+#include "src/components/channel.h"
+
#include <errno.h>
#include <stdlib.h>
#include <string.h>
-#include "src/components/channel.h"
#include "src/utils/utils.h"
struct channel*
@@ 52,6 53,8 @@ channel_list_free(struct channel_list *cl)
void
channel_list_add(struct channel_list *cl, struct channel *c)
{
+ cl->count++;
+
if (cl->head == NULL) {
cl->head = c->next = c;
cl->tail = c->prev = c;
@@ 67,6 70,8 @@ channel_list_add(struct channel_list *cl, struct channel *c)
void
channel_list_del(struct channel_list *cl, struct channel *c)
{
+ cl->count--;
+
if (cl->head == c && cl->tail == c) {
cl->head = NULL;
cl->tail = NULL;
M src/components/channel.h => src/components/channel.h +4 -3
@@ 1,5 1,5 @@
-#ifndef CHANNEL_H
-#define CHANNEL_H
+#ifndef RIRC_COMPONENTS_CHANNEL_H
+#define RIRC_COMPONENTS_CHANNEL_H
#include "src/components/buffer.h"
#include "src/components/input.h"
@@ 19,7 19,7 @@ enum activity_t
enum channel_t
{
CHANNEL_T_INVALID,
- CHANNEL_T_OTHER, /* Default/all other buffers */
+ CHANNEL_T_RIRC, /* Default buffer */
CHANNEL_T_CHANNEL, /* Channel message buffer */
CHANNEL_T_SERVER, /* Server message buffer */
CHANNEL_T_PRIVATE, /* Private message buffer */
@@ 49,6 49,7 @@ struct channel_list
{
struct channel *head;
struct channel *tail;
+ unsigned count;
};
struct channel* channel(const char*, enum channel_t);
M src/components/input.c => src/components/input.c +2 -2
@@ 1,8 1,8 @@
+#include "src/components/input.h"
+
#include <errno.h>
#include <string.h>
-#include <stdlib.h>
-#include "src/components/input.h"
#include "src/utils/utils.h"
#define INPUT_MASK(X) ((X) & (INPUT_HIST_MAX - 1))
M src/components/input.h => src/components/input.h +3 -2
@@ 1,5 1,5 @@
-#ifndef INPUT_H
-#define INPUT_H
+#ifndef RIRC_COMPONENTS_INPUT_H
+#define RIRC_COMPONENTS_INPUT_H
/* Buffer input
*
@@ 13,6 13,7 @@
* copied into the working area when scrolling
*/
+#include <stddef.h>
#include <stdint.h>
/* 410 max characters for input should be sufficient given
M src/components/ircv3.h => src/components/ircv3.h +9 -3
@@ 1,13 1,19 @@
-#ifndef IRCV3_CAP_H
-#define IRCV3_CAP_H
+#ifndef RIRC_COMPONENTS_IRCV3_CAP_H
+#define RIRC_COMPONENTS_IRCV3_CAP_H
#define IRCV3_CAP_AUTO (1 << 0)
#define IRCV3_CAP_NO_DEL (1 << 1)
#define IRCV3_CAP_NO_REQ (1 << 2)
+
#define IRCV3_CAP_VERSION "302"
#define IRCV3_CAPS_DEF \
- X("multi-prefix", multi_prefix, IRCV3_CAP_AUTO)
+ X("account-notify", account_notify, IRCV3_CAP_AUTO) \
+ X("away-notify", away_notify, IRCV3_CAP_AUTO) \
+ X("chghost", chghost, IRCV3_CAP_AUTO) \
+ X("extended-join", extended_join, IRCV3_CAP_AUTO) \
+ X("invite-notify", invite_notify, IRCV3_CAP_AUTO) \
+ X("multi-prefix", multi_prefix, IRCV3_CAP_AUTO)
/* Extended by testcases */
#ifndef IRCV3_CAPS_TEST
M src/components/mode.c => src/components/mode.c +7 -12
@@ 1,9 1,8 @@
-/* TODO: safe channels ('!' prefix) (see RFC2811) */
+#include "src/components/mode.h"
#include <ctype.h>
#include <string.h>
-#include "src/components/mode.h"
#include "src/utils/utils.h"
#define MODE_ISLOWER(X) ((X) >= 'a' && (X) <= 'z')
@@ 33,6 32,7 @@ static enum mode_err_t mode_cfg_modes(struct mode_cfg*, const char*);
/* TODO: static inline void mode_bit_set(struct mode*, uint32_t); */
/* TODO: static inline void mode_bit_isset(struct mode*, uint32_t); */
/* TODO: aggregate errors with logging callback */
+/* TODO: safe channels ('!' prefix) (see RFC2811) */
static inline int
mode_isset(const struct mode *m, int flag)
@@ 380,15 380,14 @@ mode_prfxmode_prefix(struct mode *m, const struct mode_cfg *cfg, int flag)
const char *f = cfg->PREFIX.F,
*t = cfg->PREFIX.T;
- while (*t != flag) {
-
- if (*t == 0)
- return MODE_ERR_INVALID_PREFIX;
-
+ while (*t && *t != flag) {
f++;
t++;
}
+ if (*t == 0)
+ return MODE_ERR_INVALID_PREFIX;
+
bit = flag_bit(*f);
if (MODE_ISLOWER(*f))
@@ 399,11 398,7 @@ mode_prfxmode_prefix(struct mode *m, const struct mode_cfg *cfg, int flag)
f = cfg->PREFIX.F,
t = cfg->PREFIX.T;
- while (*f) {
-
- if (mode_isset(m, *f))
- break;
-
+ while (!mode_isset(m, *f)) {
f++;
t++;
}
M src/components/mode.h => src/components/mode.h +2 -2
@@ 1,5 1,5 @@
-#ifndef MODE_H
-#define MODE_H
+#ifndef RIRC_COMPONENTS_MODE_H
+#define RIRC_COMPONENTS_MODE_H
/* usermodes, chanmodes and prfxmode configuration
*
M src/components/server.c => src/components/server.c +9 -10
@@ 1,9 1,10 @@
+#include "src/components/server.h"
+
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
-#include "src/components/server.h"
#include "src/state.h"
#include "src/utils/utils.h"
@@ 75,9 76,7 @@ server_list_get(struct server_list *sl, const char *host, const char *port)
struct server*
server_list_add(struct server_list *sl, struct server *s)
{
- struct server *tmp;
-
- if ((tmp = server_list_get(sl, s->host, s->port)) != NULL)
+ if (server_list_get(sl, s->host, s->port) != NULL)
return s;
if (sl->head == NULL) {
@@ 166,15 165,15 @@ server_set_004(struct server *s, char *str)
{
/* <server_name> <version> <user_modes> <chan_modes> */
- const char *server_name; /* Not used */
- const char *version; /* Not used */
- const char *user_modes; /* Configure server usermodes */
- const char *chan_modes; /* Configure server chanmodes */
+ const char *user_modes;
+ const char *chan_modes;
- if (!(server_name = strsep(&str)))
+ /* Not used */
+ if (!strsep(&str))
server_error(s, "invalid numeric 004: server_name is null");
- if (!(version = strsep(&str)))
+ /* Not used */
+ if (!strsep(&str))
server_error(s, "invalid numeric 004: version is null");
if (!(user_modes = strsep(&str)))
M src/components/server.h => src/components/server.h +4 -3
@@ 1,5 1,5 @@
-#ifndef SERVER_H
-#define SERVER_H
+#ifndef RIRC_COMPONENTS_SERVER_H
+#define RIRC_COMPONENTS_SERVER_H
#include "src/components/buffer.h"
#include "src/components/channel.h"
@@ 35,7 35,8 @@ struct server
struct server *prev;
struct user_list ignore;
unsigned ping;
- unsigned quitting : 1;
+ unsigned connected : 1;
+ unsigned quitting : 1;
unsigned registered : 1;
void *connection;
// TODO: move this to utils
M src/components/user.c => src/components/user.c +13 -13
@@ 1,10 1,9 @@
+#include "src/components/user.h"
+
#include <errno.h>
#include <stdlib.h>
#include <string.h>
-#include "src/components/user.h"
-#include "src/utils/utils.h"
-
static struct user* user(const char*, struct mode);
static inline int user_cmp(struct user*, struct user*, void *arg);
static inline int user_ncmp(struct user*, struct user*, void *arg, size_t);
@@ 39,8 38,8 @@ user(const char *nick, struct mode prfxmodes)
if ((u = calloc(1, sizeof(*u) + len + 1)) == NULL)
fatal("calloc: %s", strerror(errno));
- u->nick_len = len;
u->nick = memcpy(u->_, nick, len + 1);
+ u->nick_len = len;
u->prfxmodes = prfxmodes;
return u;
@@ 81,20 80,24 @@ user_list_del(struct user_list *ul, enum casemapping_t cm, const char *nick)
enum user_err
user_list_rpl(struct user_list *ul, enum casemapping_t cm, const char *nick_old, const char *nick_new)
{
- /* Replace a user in a list by name, maintaining modes */
+ /* Replace a user by name, maintaining modes */
struct user *old, *new;
- if ((old = user_list_get(ul, cm, nick_old, 0)) == NULL)
+ old = user_list_get(ul, cm, nick_old, 0);
+ new = user_list_get(ul, cm, nick_new, 0);
+
+ if (old == NULL)
return USER_ERR_NOT_FOUND;
- if ((new = user_list_get(ul, cm, nick_new, 0)) != NULL)
+ /* allow nick to change case */
+ if (new != NULL && irc_strcmp(cm, old->nick, new->nick))
return USER_ERR_DUPLICATE;
new = user(nick_new, old->prfxmodes);
- AVL_ADD(user_list, ul, new, &cm);
AVL_DEL(user_list, ul, old, &cm);
+ AVL_ADD(user_list, ul, new, &cm);
user_free(old);
@@ 104,12 107,9 @@ user_list_rpl(struct user_list *ul, enum casemapping_t cm, const char *nick_old,
struct user*
user_list_get(struct user_list *ul, enum casemapping_t cm, const char *nick, size_t prefix_len)
{
- struct user u2 = { .nick = nick };
+ struct user u = { .nick = nick };
- if (prefix_len == 0)
- return AVL_GET(user_list, ul, &u2, &cm);
- else
- return AVL_NGET(user_list, ul, &u2, &cm, prefix_len);
+ return AVL_GET(user_list, ul, &u, &cm, prefix_len);
}
void
M src/components/user.h => src/components/user.h +2 -2
@@ 1,5 1,5 @@
-#ifndef NICKLIST_H
-#define NICKLIST_H
+#ifndef RIRC_COMPONENTS_USER_H
+#define RIRC_COMPONENTS_USER_H
#include "src/components/mode.h"
#include "src/utils/tree.h"
M src/draw.c => src/draw.c +35 -44
@@ 1,8 1,4 @@
-/* draw.c
- *
- * Draw the elements in state.c to the terminal.
- *
- * Assumes vt-100 compatible escape codes, as such YMMV */
+#include "src/draw.h"
#include <alloca.h>
#include <stdarg.h>
@@ 13,7 9,6 @@
#include "config.h"
#include "src/components/channel.h"
#include "src/components/input.h"
-#include "src/draw.h"
#include "src/io.h"
#include "src/state.h"
#include "src/utils/utils.h"
@@ 127,25 122,15 @@ draw(enum draw_bit bit)
case DRAW_ALL:
draw_state.bits.all = -1;
break;
+ case DRAW_CLEAR:
+ printf(RESET_ATTRIBUTES);
+ printf(CLEAR_FULL);
+ break;
default:
fatal("unknown draw bit");
}
}
-void
-draw_init(void)
-{
- draw(DRAW_ALL);
- draw(DRAW_FLUSH);
-}
-
-void
-draw_term(void)
-{
- printf(RESET_ATTRIBUTES);
- printf(CLEAR_FULL);
-}
-
static void
draw_bits(void)
{
@@ 158,7 143,7 @@ draw_bits(void)
struct coords coords;
struct channel *c = current_channel();
- if (io_tty_cols() < COLS_MIN || io_tty_rows() < ROWS_MIN) {
+ if (state_cols() < COLS_MIN || state_rows() < ROWS_MIN) {
printf(CLEAR_FULL MOVE(1, 1) "rirc");
fflush(stdout);
return;
@@ 167,26 152,32 @@ draw_bits(void)
printf(CURSOR_SAVE);
if (draw_state.bits.buffer) {
+ printf(RESET_ATTRIBUTES);
coords.c0 = 1;
- coords.cN = io_tty_cols();
+ coords.cN = state_cols();
coords.r0 = 3;
- coords.rN = io_tty_rows() - 2;
+ coords.rN = state_rows() - 2;
draw_buffer(&c->buffer, coords);
}
if (draw_state.bits.input) {
+ printf(RESET_ATTRIBUTES);
coords.c0 = 1;
- coords.cN = io_tty_cols();
- coords.r0 = io_tty_rows();
- coords.rN = io_tty_rows();
+ coords.cN = state_cols();
+ coords.r0 = state_rows();
+ coords.rN = state_rows();
draw_input(&c->input, coords);
}
- if (draw_state.bits.nav)
+ if (draw_state.bits.nav) {
+ printf(RESET_ATTRIBUTES);
draw_nav(c);
+ }
- if (draw_state.bits.status)
+ if (draw_state.bits.status) {
+ printf(RESET_ATTRIBUTES);
draw_status(c);
+ }
printf(RESET_ATTRIBUTES);
printf(CURSOR_RESTORE);
@@ 452,7 443,6 @@ draw_input(struct input *inp, struct coords coords)
unsigned cols_t = coords.cN - coords.c0 + 1,
cursor = coords.c0;
- printf(RESET_ATTRIBUTES);
printf(MOVE(%d, 1) CLEAR_LINE, coords.rN);
printf(CURSOR_SAVE);
@@ 479,21 469,24 @@ draw_input(struct input *inp, struct coords coords)
goto print_input;
}
- if (!draw_fmt(&input_ptr, &buff_n, &text_n, 0,
- "%s", draw_colour(INPUT_FG, INPUT_BG)))
- goto print_input;
+ if (action_message()) {
- if (action_message) {
+ if (!draw_fmt(&input_ptr, &buff_n, &text_n, 0,
+ "%s", draw_colour(ACTION_FG, ACTION_BG)))
+ goto print_input;
cursor = coords.cN;
- if (!draw_fmt(&input_ptr, &buff_n, &text_n, 1,
- "%s", action_message))
+ if (!draw_fmt(&input_ptr, &buff_n, &text_n, 1, "-- %s --", action_message()))
goto print_input;
cursor = cols_t - text_n + 1;
} else {
+ if (!draw_fmt(&input_ptr, &buff_n, &text_n, 0,
+ "%s", draw_colour(INPUT_FG, INPUT_BG)))
+ goto print_input;
+
cursor += input_frame(inp, input_ptr, text_n);
}
@@ 541,7 534,7 @@ draw_nav(struct channel *c)
size_t len, total_len = 0;
/* Bump the channel frames, if applicable */
- if ((total_len = (c->name_len + 2)) >= io_tty_cols())
+ if ((total_len = (c->name_len + 2)) >= state_cols())
return;
else if (c == frame_prev && frame_prev != c_first)
frame_prev = channel_get_prev(frame_prev);
@@ 560,7 553,7 @@ draw_nav(struct channel *c)
tmp = channel_get_next(tmp_next);
len = tmp->name_len;
- while ((total_len += (len + 2)) < io_tty_cols() && tmp != c_first) {
+ while ((total_len += (len + 2)) < state_cols() && tmp != c_first) {
tmp_next = tmp;
@@ 578,7 571,7 @@ draw_nav(struct channel *c)
tmp = channel_get_prev(tmp_prev);
len = tmp->name_len;
- while ((total_len += (len + 2)) < io_tty_cols() && tmp != c_last) {
+ while ((total_len += (len + 2)) < state_cols() && tmp != c_last) {
tmp_prev = tmp;
@@ 593,7 586,7 @@ draw_nav(struct channel *c)
len = tmp->name_len;
/* Next channel doesn't fit */
- if ((total_len += (len + 2)) >= io_tty_cols())
+ if ((total_len += (len + 2)) >= state_cols())
break;
if (nextward)
@@ 638,15 631,13 @@ draw_status(struct channel *c)
float sb;
int ret;
unsigned col = 0;
- unsigned cols = io_tty_cols();
- unsigned rows = io_tty_rows();
+ unsigned cols = state_cols();
+ unsigned rows = state_rows();
/* Insufficient columns for meaningful status */
if (cols < 3)
return;
- printf(RESET_ATTRIBUTES);
-
printf(MOVE(2, 1));
printf("%.*s", cols, (char *)(memset(alloca(cols), *HORIZONTAL_SEPARATOR, cols)));
@@ 776,7 767,7 @@ draw_colour(int fg, int bg)
}
if (bg >= 0 && bg <= 255) {
- if ((ret = snprintf(buf + len, sizeof(buf) - len, ESC"[48;5;%dm", bg)) < 0)
+ if ((snprintf(buf + len, sizeof(buf) - len, ESC"[48;5;%dm", bg)) < 0)
buf[len] = 0;
}
M src/draw.h => src/draw.h +3 -4
@@ 1,5 1,5 @@
-#ifndef DRAW_H
-#define DRAW_H
+#ifndef RIRC_DRAW_H
+#define RIRC_DRAW_H
enum draw_bit
{
@@ 11,10 11,9 @@ enum draw_bit
DRAW_NAV, /* set bit to draw nav */
DRAW_STATUS, /* set bit to draw status */
DRAW_ALL, /* set all draw bits aside from bell */
+ DRAW_CLEAR, /* clear the terminal */
};
void draw(enum draw_bit);
-void draw_init(void);
-void draw_term(void);
#endif
M src/handlers/irc_ctcp.c => src/handlers/irc_ctcp.c +15 -5
@@ 1,12 1,12 @@
+#include "src/handlers/irc_ctcp.h"
+
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>
#include "src/components/channel.h"
-#include "src/components/server.h"
#include "src/handlers/irc_ctcp.gperf.out"
-#include "src/handlers/irc_ctcp.h"
#include "src/io.h"
#include "src/state.h"
#include "src/utils/utils.h"
@@ 22,11 22,21 @@
failf((S), "Send fail: %s", io_err(ret)); \
} while (0)
+#define CTCP_CLIENTINFO \
+ "ACTION " \
+ "CLIENTINFO " \
+ "FINGER " \
+ "PING " \
+ "SOURCE " \
+ "TIME " \
+ "USERINFO " \
+ "VERSION"
+
static int
parse_ctcp(struct server *s, const char *from, char **args, const char **cmd)
{
- char *message = *args;
char *command;
+ char *message = *args;
char *p;
if (!from)
@@ 138,7 148,7 @@ ctcp_request_clientinfo(struct server *s, const char *from, const char *targ, ch
else
server_info(s, "CTCP CLIENTINFO from %s", from);
- sendf(s, "NOTICE %s :\001CLIENTINFO ACTION CLIENTINFO PING SOURCE TIME VERSION\001", from);
+ sendf(s, "NOTICE %s :\001CLIENTINFO " CTCP_CLIENTINFO "\001", from);
return 0;
}
@@ 211,7 221,7 @@ ctcp_request_source(struct server *s, const char *from, const char *targ, char *
else
server_info(s, "CTCP SOURCE from %s", from);
- sendf(s, "NOTICE %s :\001SOURCE rcr.io/rirc\001", from);
+ sendf(s, "NOTICE %s :\001SOURCE https://rcr.io/rirc\001", from);
return 0;
}
M src/handlers/irc_ctcp.h => src/handlers/irc_ctcp.h +4 -2
@@ 1,5 1,7 @@
-#ifndef IRC_CTCP_H
-#define IRC_CTCP_H
+#ifndef RIRC_HANDLERS_IRC_CTCP_H
+#define RIRC_HANDLERS_IRC_CTCP_H
+
+#include "src/components/server.h"
/* Summary of CTCP implementation:
*
M src/handlers/irc_recv.c => src/handlers/irc_recv.c +214 -113
@@ 1,3 1,5 @@
+#include "src/handlers/irc_recv.h"
+
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
@@ 5,24 7,13 @@
#include "src/components/server.h"
#include "src/handlers/irc_ctcp.h"
#include "src/handlers/irc_recv.gperf.out"
-#include "src/handlers/irc_recv.h"
#include "src/handlers/ircv3.h"
#include "src/draw.h"
#include "src/io.h"
#include "src/state.h"
#include "src/utils/utils.h"
-#ifndef JOIN_THRESHOLD
-#define JOIN_THRESHOLD 0
-#endif
-
-#ifndef PART_THRESHOLD
-#define PART_THRESHOLD 0
-#endif
-
-#ifndef QUIT_THRESHOLD
-#define QUIT_THRESHOLD 0
-#endif
+#include "config.h"
#define failf(S, ...) \
do { server_error((S), __VA_ARGS__); \
@@ 60,9 51,12 @@ static int irc_recv_numeric(struct server*, struct irc_message*);
static int recv_mode_chanmodes(struct irc_message*, const struct mode_cfg*, struct server*, struct channel*);
static int recv_mode_usermodes(struct irc_message*, const struct mode_cfg*, struct server*);
-static const unsigned quit_threshold = QUIT_THRESHOLD;
-static const unsigned join_threshold = JOIN_THRESHOLD;
-static const unsigned part_threshold = PART_THRESHOLD;
+static unsigned quit_threshold = FILTER_THRESHOLD_QUIT;
+static unsigned join_threshold = FILTER_THRESHOLD_JOIN;
+static unsigned part_threshold = FILTER_THRESHOLD_PART;
+static unsigned account_threshold = FILTER_THRESHOLD_ACCOUNT;
+static unsigned away_threshold = FILTER_THRESHOLD_AWAY;
+static unsigned chghost_threshold = FILTER_THRESHOLD_CHGHOST;
static const irc_recv_f irc_numerics[] = {
[1] = irc_001, /* RPL_WELCOME */
@@ 212,6 206,7 @@ static const irc_recv_f irc_numerics[] = {
[704] = irc_info, /* RPL_HELPSTART */
[705] = irc_info, /* RPL_HELP */
[706] = irc_ignore, /* RPL_ENDOFHELP */
+ [1000] = NULL /* Out of range */
};
int
@@ 419,7 414,7 @@ irc_329(struct server *s, struct irc_message *m)
static int
irc_332(struct server *s, struct irc_message *m)
{
- /* 332 <channel> <topic> */
+ /* 332 <channel> :<topic> */
char *chan;
char *topic;
@@ 489,6 484,7 @@ irc_353(struct server *s, struct irc_message *m)
char *chan;
char *nick;
char *nicks;
+ char *prfx;
char *type;
struct channel *c;
@@ 507,29 503,18 @@ irc_353(struct server *s, struct irc_message *m)
if (mode_chanmode_prefix(&(c->chanmodes), &(s->mode_cfg), *type) != MODE_ERR_NONE)
failf(s, "RPL_NAMEREPLY: invalid channel flag: '%c'", *type);
- if ((nick = strsep(&nicks))) {
- do {
- char prefix = 0;
- struct mode m = MODE_EMPTY;
-
- do {
- if (irc_isnickchar(*nick, 1))
- break;
-
- prefix = *nick++;
+ while ((prfx = nick = strsep(&nicks))) {
- if (mode_prfxmode_prefix(&m, &(s->mode_cfg), prefix) != MODE_ERR_NONE)
- failf(s, "RPL_NAMEREPLY: invalid user prefix: '%c'", prefix);
+ struct mode m = MODE_EMPTY;
- } while (s->ircv3_caps.multi_prefix.set);
+ while (mode_prfxmode_prefix(&m, &(s->mode_cfg), *nick) == MODE_ERR_NONE)
+ nick++;
- if (!irc_isnick(nick))
- failf(s, "RPL_NAMEREPLY: invalid nick: '%s'", nick);
+ if (*nick == 0)
+ failf(s, "RPL_NAMEREPLY: invalid nick: '%s'", prfx);
- if (user_list_add(&(c->users), s->casemapping, nick, m) == USER_ERR_DUPLICATE)
- failf(s, "RPL_NAMEREPLY: duplicate nick: '%s'", nick);
-
- } while ((nick = strsep(&nicks)));
+ if (user_list_add(&(c->users), s->casemapping, nick, m) == USER_ERR_DUPLICATE)
+ failf(s, "RPL_NAMEREPLY: duplicate nick: '%s'", nick);
}
draw(DRAW_STATUS);
@@ 564,55 549,40 @@ irc_recv_numeric(struct server *s, struct irc_message *m)
/* :server <code> <target> [args] */
char *targ;
- int code = 0;
- irc_recv_f handler = NULL;
-
- for (const char *p = m->command; *p; p++) {
-
- if (!isdigit(*p))
- failf(s, "NUMERIC: invalid");
-
- code *= 10;
- code += *p - '0';
-
- if (code > 999)
- failf(s, "NUMERIC: out of range");
+ unsigned code = 0;
+
+ if ((m->command[0] && isdigit(m->command[0]))
+ && (m->command[1] && isdigit(m->command[1]))
+ && (m->command[2] && isdigit(m->command[2]))
+ && (m->command[3] == 0))
+ {
+ code += (m->command[0] - '0') * 100;
+ code += (m->command[1] - '0') * 10;
+ code += (m->command[2] - '0');
}
- /* Message target is only used to establish s->nick when registering with a server */
- if (!(irc_message_param(m, &targ))) {
- io_dx(s->connection);
- failf(s, "NUMERIC: target is null");
- }
+ if (!code)
+ failf(s, "NUMERIC: '%s' invalid", m->command);
- /* Message target should match s->nick or '*' if unregistered, otherwise out of sync */
- if (strcmp(targ, s->nick) && strcmp(targ, "*") && code != 1) {
- io_dx(s->connection);
- failf(s, "NUMERIC: target mismatched, nick is '%s', received '%s'", s->nick, targ);
- }
+ if (!(irc_message_param(m, &targ)))
+ failf(s, "NUMERIC: target is null");
- if (ARR_ELEM(irc_numerics, code))
- handler = irc_numerics[code];
+ if (strcmp(targ, s->nick) && strcmp(targ, "*"))
+ failf(s, "NUMERIC: target '%s' is invalid", targ);
- if (handler)
- return (*handler)(s, m);
+ if (!irc_numerics[code] && (m->params && *m->params))
+ failf(s, "NUMERIC: %u unhandled: [%s]", code, m->params);
- if (m->params)
- failf(s, "Numeric type '%u' unknown: %s", code, m->params);
- else
- failf(s, "Numeric type '%u' unknown", code);
-}
+ if (!irc_numerics[code])
+ failf(s, "NUMERIC: %u unhandled", code);
-static int
-recv_cap(struct server *s, struct irc_message *m)
-{
- return ircv3_recv_CAP(s, m);
+ return (*irc_numerics[code])(s, m);
}
static int
recv_error(struct server *s, struct irc_message *m)
{
- /* ERROR <message> */
+ /* ERROR :<message> */
char *message;
@@ 631,20 601,27 @@ recv_invite(struct server *s, struct irc_message *m)
char *chan;
char *nick;
+ struct channel *c;
if (!m->from)
failf(s, "INVITE: sender's nick is null");
+ if (!irc_message_param(m, &nick))
+ failf(s, "INVITE: nick is null");
+
if (!irc_message_param(m, &chan))
- failf(s, "INVITE: target channel is null");
+ failf(s, "INVITE: channel is null");
- if (!irc_message_param(m, &nick))
- failf(s, "INVITE: target nick is null");
+ if (!strcmp(nick, s->nick)) {
+ newlinef(s->channel, 0, FROM_INFO, "%s invited you to %s", m->from, chan);
+ return 0;
+ }
- if (!strcmp(nick, s->nick))
- newlinef(s->channel, 0, FROM_INFO, "You invited %s to %s", nick, chan);
- else
- newlinef(s->channel, 0, FROM_INFO, "You've been invited to %s by %s", chan, m->from);
+ /* IRCv3 CAP invite-notify, sent to all users on the target channel */
+ if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL)
+ failf(s, "INVITE: channel '%s' not found", chan);
+
+ newlinef(c, 0, FROM_INFO, "%s invited %s to %s", m->from, nick, chan);
return 0;
}
@@ 652,7 629,8 @@ recv_invite(struct server *s, struct irc_message *m)
static int
recv_join(struct server *s, struct irc_message *m)
{
- /* :nick!user@host JOIN <channel> */
+ /* :nick!user@host JOIN <channel>
+ * :nick!user@host JOIN <channel> <account> :<realname> */
char *chan;
struct channel *c;
@@ 661,7 639,7 @@ recv_join(struct server *s, struct irc_message *m)
failf(s, "JOIN: sender's nick is null");
if (!irc_message_param(m, &chan))
- failf(s, "JOIN: target channel is null");
+ failf(s, "JOIN: channel is null");
if (!strcmp(m->from, s->nick)) {
if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL) {
@@ 682,10 660,27 @@ recv_join(struct server *s, struct irc_message *m)
failf(s, "JOIN: channel '%s' not found", chan);
if (user_list_add(&(c->users), s->casemapping, m->from, MODE_EMPTY) == USER_ERR_DUPLICATE)
- failf(s, "JOIN: user '%s' alread on channel '%s'", m->from, chan);
+ failf(s, "JOIN: user '%s' already on channel '%s'", m->from, chan);
+
+ if (!join_threshold || join_threshold > c->users.count) {
+
+ if (s->ircv3_caps.extended_join.set) {
+
+ char *account;
+ char *realname;
- if (!join_threshold || c->users.count <= join_threshold)
- newlinef(c, BUFFER_LINE_JOIN, FROM_JOIN, "%s!%s has joined", m->from, m->host);
+ if (!irc_message_param(m, &account))
+ failf(s, "JOIN: account is null");
+
+ if (!irc_message_param(m, &realname))
+ failf(s, "JOIN: realname is null");
+
+ newlinef(c, BUFFER_LINE_JOIN, FROM_JOIN, "%s!%s has joined [%s - %s]",
+ m->from, m->host, account, realname);
+ } else {
+ newlinef(c, BUFFER_LINE_JOIN, FROM_JOIN, "%s!%s has joined", m->from, m->host);
+ }
+ }
draw(DRAW_STATUS);
@@ 695,7 690,7 @@ recv_join(struct server *s, struct irc_message *m)
static int
recv_kick(struct server *s, struct irc_message *m)
{
- /* :nick!user@host KICK <channel> <user> [message] */
+ /* :nick!user@host KICK <channel> <user> [:message] */
char *chan;
char *message;
@@ 727,7 722,7 @@ recv_kick(struct server *s, struct irc_message *m)
channel_part(c);
- if (message)
+ if (message && *message)
newlinef(c, 0, FROM_INFO, "Kicked by %s (%s)", m->from, message);
else
newlinef(c, 0, FROM_INFO, "Kicked by %s", m->from);
@@ 737,7 732,7 @@ recv_kick(struct server *s, struct irc_message *m)
if (user_list_del(&(c->users), s->casemapping, user) == USER_ERR_NOT_FOUND)
failf(s, "KICK: nick '%s' not found in '%s'", user, chan);
- if (message)
+ if (message && *message)
newlinef(c, 0, FROM_INFO, "%s has kicked %s (%s)", m->from, user, message);
else
newlinef(c, 0, FROM_INFO, "%s has kicked %s", m->from, user);
@@ 998,7 993,7 @@ recv_nick(struct server *s, struct irc_message *m)
newlinef(c, BUFFER_LINE_NICK, FROM_NICK, "%s >> %s", m->from, nick);
else if (ret == USER_ERR_DUPLICATE)
- server_error(s, "NICK: user '%s' alread on channel '%s'", m->from, c->name);
+ server_error(s, "NICK: user '%s' already on channel '%s'", nick, c->name);
} while ((c = c->next) != s->channel);
@@ 1008,7 1003,7 @@ recv_nick(struct server *s, struct irc_message *m)
static int
recv_notice(struct server *s, struct irc_message *m)
{
- /* :nick!user@host NOTICE <target> <message> */
+ /* :nick!user@host NOTICE <target> :<message> */
char *message;
char *target;
@@ 1069,7 1064,7 @@ recv_notice(struct server *s, struct irc_message *m)
static int
recv_part(struct server *s, struct irc_message *m)
{
- /* :nick!user@host PART <channel> [message] */
+ /* :nick!user@host PART <channel> [:message] */
char *chan;
char *message;
@@ 1079,14 1074,16 @@ recv_part(struct server *s, struct irc_message *m)
failf(s, "PART: sender's nick is null");
if (!irc_message_param(m, &chan))
- failf(s, "PART: target channel is null");
+ failf(s, "PART: channel is null");
+
+ irc_message_param(m, &message);
if (!strcmp(m->from, s->nick)) {
/* If not found, assume channel was closed */
if ((c = channel_list_get(&s->clist, chan, s->casemapping)) != NULL) {
- if (irc_message_param(m, &message))
+ if (message && *message)
newlinef(c, BUFFER_LINE_PART, FROM_PART, "you have parted (%s)", message);
else
newlinef(c, BUFFER_LINE_PART, FROM_PART, "you have parted");
@@ 1101,8 1098,9 @@ recv_part(struct server *s, struct irc_message *m)
if (user_list_del(&(c->users), s->casemapping, m->from) == USER_ERR_NOT_FOUND)
failf(s, "PART: nick '%s' not found in '%s'", m->from, chan);
- if (!part_threshold || c->users.count <= part_threshold) {
- if (irc_message_param(m, &message))
+ if (!part_threshold || part_threshold > c->users.count) {
+
+ if (message && *message)
newlinef(c, 0, FROM_PART, "%s!%s has parted (%s)", m->from, m->host, message);
else
newlinef(c, 0, FROM_PART, "%s!%s has parted", m->from, m->host);
@@ 1132,7 1130,7 @@ recv_ping(struct server *s, struct irc_message *m)
static int
recv_pong(struct server *s, struct irc_message *m)
{
- /* PONG <server> [<server2>] */
+ /* PONG <server> [<server2>] */
UNUSED(s);
UNUSED(m);
@@ 1143,7 1141,7 @@ recv_pong(struct server *s, struct irc_message *m)
static int
recv_privmsg(struct server *s, struct irc_message *m)
{
- /* :nick!user@host PRIVMSG <target> <message> */
+ /* :nick!user@host PRIVMSG <target> :<message> */
char *message;
char *target;
@@ 1200,9 1198,42 @@ recv_privmsg(struct server *s, struct irc_message *m)
}
static int
+recv_quit(struct server *s, struct irc_message *m)
+{
+ /* :nick!user@host QUIT [:message] */
+
+ char *message;
+ struct channel *c = s->channel;
+
+ if (!m->from)
+ failf(s, "QUIT: sender's nick is null");
+
+ irc_message_param(m, &message);
+
+ do {
+ if (user_list_del(&(c->users), s->casemapping, m->from) == USER_ERR_NONE) {
+
+ if (quit_threshold && quit_threshold <= c->users.count)
+ continue;
+
+ if (message && *message)
+ newlinef(c, BUFFER_LINE_QUIT, FROM_QUIT, "%s!%s has quit (%s)",
+ m->from, m->host, message);
+ else
+ newlinef(c, BUFFER_LINE_QUIT, FROM_QUIT, "%s!%s has quit",
+ m->from, m->host);
+ }
+ } while ((c = c->next) != s->channel);
+
+ draw(DRAW_STATUS);
+
+ return 0;
+}
+
+static int
recv_topic(struct server *s, struct irc_message *m)
{
- /* :nick!user@host TOPIC <channel> [topic] */
+ /* :nick!user@host TOPIC <channel> [:topic] */
char *chan;
char *topic;
@@ 1212,16 1243,16 @@ recv_topic(struct server *s, struct irc_message *m)
failf(s, "TOPIC: sender's nick is null");
if (!irc_message_param(m, &chan))
- failf(s, "TOPIC: target channel is null");
+ failf(s, "TOPIC: channel is null");
if (!irc_message_param(m, &topic))
failf(s, "TOPIC: topic is null");
if ((c = channel_list_get(&s->clist, chan, s->casemapping)) == NULL)
- failf(s, "TOPIC: target channel '%s' not found", chan);
+ failf(s, "TOPIC: channel '%s' not found", chan);
if (*topic) {
- newlinef(c, 0, FROM_INFO, "%s has changed the topic:", m->from);
+ newlinef(c, 0, FROM_INFO, "%s has set the topic:", m->from);
newlinef(c, 0, FROM_INFO, "\"%s\"", topic);
} else {
newlinef(c, 0, FROM_INFO, "%s has unset the topic", m->from);
@@ 1231,30 1262,100 @@ recv_topic(struct server *s, struct irc_message *m)
}
static int
-recv_quit(struct server *s, struct irc_message *m)
+recv_ircv3_cap(struct server *s, struct irc_message *m)
+{
+ return ircv3_recv_CAP(s, m);
+}
+
+static int
+recv_ircv3_account(struct server *s, struct irc_message *m)
{
- /* :nick!user@host QUIT [message] */
+ /* :nick!user@host ACCOUNT <account> */
- char *message = NULL;
+ char *account;
struct channel *c = s->channel;
if (!m->from)
- failf(s, "QUIT: sender's nick is null");
+ failf(s, "ACCOUNT: sender's nick is null");
+
+ if (!irc_message_param(m, &account))
+ failf(s, "ACCOUNT: account is null");
+
+ do {
+ if (!user_list_get(&(c->users), s->casemapping, m->from, 0))
+ continue;
+
+ if (account_threshold && account_threshold <= c->users.count)
+ continue;
+
+ if (!strcmp(account, "*"))
+ newlinef(c, 0, FROM_INFO, "%s has logged out", m->from);
+ else
+ newlinef(c, 0, FROM_INFO, "%s has logged in as %s", m->from, account);
+
+ } while ((c = c->next) != s->channel);
+
+ return 0;
+}
+
+static int
+recv_ircv3_away(struct server *s, struct irc_message *m)
+{
+ /* :nick!user@host AWAY [:message] */
+
+ char *message;
+ struct channel *c = s->channel;
+
+ if (!m->from)
+ failf(s, "AWAY: sender's nick is null");
irc_message_param(m, &message);
do {
- if (user_list_del(&(c->users), s->casemapping, m->from) == USER_ERR_NONE) {
- if (!quit_threshold || c->users.count <= quit_threshold) {
- if (message)
- newlinef(c, BUFFER_LINE_QUIT, FROM_QUIT, "%s!%s has quit (%s)", m->from, m->host, message);
- else
- newlinef(c, BUFFER_LINE_QUIT, FROM_QUIT, "%s!%s has quit", m->from, m->host);
- }
- }
+ if (!user_list_get(&(c->users), s->casemapping, m->from, 0))
+ continue;
+
+ if (away_threshold && away_threshold <= c->users.count)
+ continue;
+
+ if (message)
+ newlinef(c, 0, FROM_INFO, "%s is now away: %s", m->from, message);
+ else
+ newlinef(c, 0, FROM_INFO, "%s is no longer away", m->from);
+
} while ((c = c->next) != s->channel);
- draw(DRAW_STATUS);
+ return 0;
+}
+
+static int
+recv_ircv3_chghost(struct server *s, struct irc_message *m)
+{
+ /* :nick!user@host CHGHOST new_user new_host */
+
+ char *user;
+ char *host;
+ struct channel *c = s->channel;
+
+ if (!m->from)
+ failf(s, "CHGHOST: sender's nick is null");
+
+ if (!irc_message_param(m, &user))
+ failf(s, "CHGHOST: user is null");
+
+ if (!irc_message_param(m, &host))
+ failf(s, "CHGHOST: host is null");
+
+ do {
+ if (!user_list_get(&(c->users), s->casemapping, m->from, 0))
+ continue;
+
+ if (chghost_threshold && chghost_threshold <= c->users.count)
+ continue;
+
+ newlinef(c, 0, FROM_INFO, "%s has changed user/host: %s/%s", m->from, user, host);
+
+ } while ((c = c->next) != s->channel);
return 0;
}
M src/handlers/irc_recv.gperf => src/handlers/irc_recv.gperf +9 -3
@@ 2,7 2,6 @@
#include <string.h>
#define RECV_HANDLERS \
- X(cap) \
X(error) \
X(invite) \
X(join) \
@@ 15,7 14,11 @@
X(pong) \
X(privmsg) \
X(quit) \
- X(topic)
+ X(topic) \
+ X(ircv3_cap) \
+ X(ircv3_account) \
+ X(ircv3_away) \
+ X(ircv3_chghost)
#define X(cmd) static int recv_##cmd(struct server*, struct irc_message*);
RECV_HANDLERS
@@ 41,7 44,6 @@ struct recv_handler
%define initializer-suffix ,(irc_recv_f)0
struct recv_handler;
%%
-CAP, recv_cap
ERROR, recv_error
INVITE, recv_invite
JOIN, recv_join
@@ 55,4 57,8 @@ PONG, recv_pong
PRIVMSG, recv_privmsg
QUIT, recv_quit
TOPIC, recv_topic
+CAP, recv_ircv3_cap
+ACCOUNT, recv_ircv3_account
+AWAY, recv_ircv3_away
+CHGHOST, recv_ircv3_chghost
%%
M src/handlers/irc_recv.h => src/handlers/irc_recv.h +2 -2
@@ 1,5 1,5 @@
-#ifndef IRC_RECV_H
-#define IRC_RECV_H
+#ifndef RIRC_HANDLERS_IRC_RECV_H
+#define RIRC_HANDLERS_IRC_RECV_H
/* Summary of irc protocol implementation:
*
M src/handlers/irc_send.c => src/handlers/irc_send.c +69 -41
@@ 1,13 1,12 @@
+#include "src/handlers/irc_send.h"
+
#include <ctype.h>
#include <sys/time.h>
#include "config.h"
#include "src/components/buffer.h"
-#include "src/components/channel.h"
#include "src/components/ircv3.h"
-#include "src/components/server.h"
#include "src/handlers/irc_send.gperf.out"
-#include "src/handlers/irc_send.h"
#include "src/io.h"
#include "src/state.h"
#include "src/utils/utils.h"
@@ 23,12 22,14 @@
failf((C), "Send fail: %s", io_err(ret)); \
} while (0)
-static const char* targ_or_type(struct channel*, char*, enum channel_t type);
+static const char* nick_or_priv(struct channel*, char*);
int
irc_send_command(struct server *s, struct channel *c, char *m)
{
- char *command, *command_args, *p;
+ char *command;
+ char *command_args;
+ char *p;
const struct send_handler *send;
if (!s)
@@ 82,20 83,31 @@ irc_send_privmsg(struct server *s, struct channel *c, char *m)
}
static const char*
-targ_or_type(struct channel *c, char *m, enum channel_t type)
+nick_or_priv(struct channel *c, char *m)
{
- const char *targ;
+ const char *nick;
- if ((targ = strsep(&m)))
- return targ;
+ if ((nick = strsep(&m)))
+ return nick;
- if (c->type == type)
+ if (c->type == CHANNEL_T_PRIVATE)
return c->name;
return NULL;
}
static int
+send_away(struct server *s, struct channel *c, char *m)
+{
+ if (strtrim(&m))
+ sendf(s, c, "AWAY :%s", m);
+ else
+ sendf(s, c, "AWAY");
+
+ return 0;
+}
+
+static int
send_notice(struct server *s, struct channel *c, char *m)
{
const char *targ;
@@ 169,12 181,28 @@ send_topic(struct server *s, struct channel *c, char *m)
}
static int
-send_ctcp_action(struct server *s, struct channel *c, char *m)
+send_topic_unset(struct server *s, struct channel *c, char *m)
{
- if (!(c->type == CHANNEL_T_CHANNEL || c->type == CHANNEL_T_PRIVATE))
+ if (c->type != CHANNEL_T_CHANNEL)
failf(c, "This is not a channel");
- sendf(s, c, "PRIVMSG %s :\001ACTION %s\001", c->name, m);
+ if (strtrim(&m))
+ failf(c, "Usage: /topic-unset");
+
+ sendf(s, c, "TOPIC %s :", c->name);
+
+ return 0;
+}
+
+static int
+send_ctcp_action(struct server *s, struct channel *c, char *m)
+{
+ const char *nick;
+
+ if (!(nick = strsep(&m)) || !strtrim(&m))
+ failf(c, "Usage: /ctcp-action <nick> <text>");
+
+ sendf(s, c, "PRIVMSG %s :\001ACTION %s\001", nick, m);
return 0;
}
@@ 182,12 210,12 @@ send_ctcp_action(struct server *s, struct channel *c, char *m)
static int
send_ctcp_clientinfo(struct server *s, struct channel *c, char *m)
{
- const char *targ;
+ const char *nick;
- if (!(targ = targ_or_type(c, m, CHANNEL_T_PRIVATE)))
- failf(c, "Usage: /ctcp-clientinfo <target>");
+ if (!(nick = nick_or_priv(c, m)))
+ failf(c, "Usage: /ctcp-clientinfo <nick>");
- sendf(s, c, "PRIVMSG %s :\001CLIENTINFO\001", targ);
+ sendf(s, c, "PRIVMSG %s :\001CLIENTINFO\001", nick);
return 0;
}
@@ 195,12 223,12 @@ send_ctcp_clientinfo(struct server *s, struct channel *c, char *m)
static int
send_ctcp_finger(struct server *s, struct channel *c, char *m)
{
- const char *targ;
+ const char *nick;
- if (!(targ = targ_or_type(c, m, CHANNEL_T_PRIVATE)))
- failf(c, "Usage: /ctcp-finger <target>");
+ if (!(nick = nick_or_priv(c, m)))
+ failf(c, "Usage: /ctcp-finger <nick>");
- sendf(s, c, "PRIVMSG %s :\001FINGER\001", targ);
+ sendf(s, c, "PRIVMSG %s :\001FINGER\001", nick);
return 0;
}
@@ 208,15 236,15 @@ send_ctcp_finger(struct server *s, struct channel *c, char *m)
static int
send_ctcp_ping(struct server *s, struct channel *c, char *m)
{
- const char *targ;
+ const char *nick;
struct timeval t;
- if (!(targ = targ_or_type(c, m, CHANNEL_T_PRIVATE)))
- failf(c, "Usage: /ctcp-ping <target>");
+ if (!(nick = nick_or_priv(c, m)))
+ failf(c, "Usage: /ctcp-ping <nick>");
(void) gettimeofday(&t, NULL);
- sendf(s, c, "PRIVMSG %s :\001PING %llu %llu\001", targ,
+ sendf(s, c, "PRIVMSG %s :\001PING %llu %llu\001", nick,
(unsigned long long)t.tv_sec,
(unsigned long long)t.tv_usec);
@@ 226,12 254,12 @@ send_ctcp_ping(struct server *s, struct channel *c, char *m)
static int
send_ctcp_source(struct server *s, struct channel *c, char *m)
{
- const char *targ;
+ const char *nick;
- if (!(targ = targ_or_type(c, m, CHANNEL_T_PRIVATE)))
- failf(c, "Usage: /ctcp-source <target>");
+ if (!(nick = nick_or_priv(c, m)))
+ failf(c, "Usage: /ctcp-source <nick>");
- sendf(s, c, "PRIVMSG %s :\001SOURCE\001", targ);
+ sendf(s, c, "PRIVMSG %s :\001SOURCE\001", nick);
return 0;
}
@@ 239,12 267,12 @@ send_ctcp_source(struct server *s, struct channel *c, char *m)
static int
send_ctcp_time(struct server *s, struct channel *c, char *m)
{
- const char *targ;
+ const char *nick;
- if (!(targ = targ_or_type(c, m, CHANNEL_T_PRIVATE)))
- failf(c, "Usage: /ctcp-time <target>");
+ if (!(nick = nick_or_priv(c, m)))
+ failf(c, "Usage: /ctcp-time <nick>");
- sendf(s, c, "PRIVMSG %s :\001TIME\001", targ);
+ sendf(s, c, "PRIVMSG %s :\001TIME\001", nick);
return 0;
}
@@ 252,12 280,12 @@ send_ctcp_time(struct server *s, struct channel *c, char *m)
static int
send_ctcp_userinfo(struct server *s, struct channel *c, char *m)
{
- const char *targ;
+ const char *nick;
- if (!(targ = targ_or_type(c, m, CHANNEL_T_PRIVATE)))
- failf(c, "Usage: /ctcp-userinfo <target>");
+ if (!(nick = nick_or_priv(c, m)))
+ failf(c, "Usage: /ctcp-userinfo <nick>");
- sendf(s, c, "PRIVMSG %s :\001USERINFO\001", targ);
+ sendf(s, c, "PRIVMSG %s :\001USERINFO\001", nick);
return 0;
}
@@ 265,12 293,12 @@ send_ctcp_userinfo(struct server *s, struct channel *c, char *m)
static int
send_ctcp_version(struct server *s, struct channel *c, char *m)
{
- const char *targ;
+ const char *nick;
- if (!(targ = targ_or_type(c, m, CHANNEL_T_PRIVATE)))
- failf(c, "Usage: /ctcp-version <target>");
+ if (!(nick = nick_or_priv(c, m)))
+ failf(c, "Usage: /ctcp-version <nick>");
- sendf(s, c, "PRIVMSG %s :\001VERSION\001", targ);
+ sendf(s, c, "PRIVMSG %s :\001VERSION\001", nick);
return 0;
}
M src/handlers/irc_send.gperf => src/handlers/irc_send.gperf +5 -1
@@ 2,11 2,13 @@
#include <string.h>
#define SEND_HANDLERS \
+ X(away) \
X(notice) \
X(part) \
X(privmsg) \
X(quit) \
- X(topic)
+ X(topic) \
+ X(topic_unset)
#define SEND_CTCP_HANDLERS \
X(action) \
@@ 64,9 66,11 @@ CTCP-SOURCE, send_ctcp_source
CTCP-TIME, send_ctcp_time
CTCP-USERINFO, send_ctcp_userinfo
CTCP-VERSION, send_ctcp_version
+AWAY, send_away
NOTICE, send_notice
PART, send_part
PRIVMSG, send_privmsg
QUIT, send_quit
TOPIC, send_topic
+TOPIC-UNSET, send_topic_unset
%%
M src/handlers/irc_send.h => src/handlers/irc_send.h +2 -2
@@ 1,5 1,5 @@
-#ifndef IRC_SEND_H
-#define IRC_SEND_H
+#ifndef RIRC_HANDLERS_IRC_SEND_H
+#define RIRC_HANDLERS_IRC_SEND_H
#include "src/components/channel.h"
#include "src/components/server.h"
M src/handlers/ircv3.c => src/handlers/ircv3.c +2 -1
@@ 1,6 1,7 @@
+#include "src/handlers/ircv3.h"
+
#include <string.h>
-#include "src/handlers/ircv3.h"
#include "src/io.h"
#include "src/state.h"
M src/handlers/ircv3.h => src/handlers/ircv3.h +2 -2
@@ 1,5 1,5 @@
-#ifndef IRCV3_H
-#define IRCV3_H
+#ifndef RIRC_HANDLERS_IRCV3_H
+#define RIRC_HANDLERS_IRCV3_H
#include "src/components/server.h"
#include "src/utils/utils.h"
M src/io.c => src/io.c +132 -159
@@ 3,6 3,7 @@
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
+#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
@@ 14,8 15,8 @@
#include <unistd.h>
#include "config.h"
-#include "rirc.h"
-#include "utils/utils.h"
+#include "src/rirc.h"
+#include "src/utils/utils.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/entropy.h"
@@ 25,9 26,7 @@
#include "mbedtls/x509_crt.h"
/* RFC 2812, section 2.3 */
-#ifndef IO_MESG_LEN
#define IO_MESG_LEN 510
-#endif
#ifndef IO_PING_MIN
#define IO_PING_MIN 150
@@ 72,27 71,23 @@
io_fatal((#X), _ptcf); \
} \
} while (0)
+
#define PT_LK(X) PT_CF(pthread_mutex_lock((X)))
#define PT_UL(X) PT_CF(pthread_mutex_unlock((X)))
-#define PT_CB(...) \
- do { \
- PT_LK(&io_cb_mutex); \
- io_cb(__VA_ARGS__); \
- PT_UL(&io_cb_mutex); \
- } while (0)
+
+/* IO callback */
+#define IO_CB(X) \
+ do { PT_LK(&io_cb_mutex); (X); PT_UL(&io_cb_mutex); } while (0)
+
+#define io_cxed(C) IO_CB(io_cb_cxed((C)->obj))
+#define io_dxed(C) IO_CB(io_cb_dxed((C)->obj))
+#define io_error(C, ...) IO_CB(io_cb_error((C)->obj, __VA_ARGS__))
+#define io_info(C, ...) IO_CB(io_cb_info((C)->obj, __VA_ARGS__))
+#define io_ping(C, P) IO_CB(io_cb_ping((C)->obj, P))
/* state transition */
#define ST_X(OLD, NEW) (((OLD) << 3) | (NEW))
-#define io_cb_cxed(C) PT_CB(IO_CB_CXED, (C)->obj)
-#define io_cb_dxed(C) PT_CB(IO_CB_DXED, (C)->obj)
-#define io_cb_err(C, ...) PT_CB(IO_CB_ERR, (C)->obj, __VA_ARGS__)
-#define io_cb_info(C, ...) PT_CB(IO_CB_INFO, (C)->obj, __VA_ARGS__)
-#define io_cb_ping_0(C, ...) PT_CB(IO_CB_PING_0, (C)->obj, __VA_ARGS__)
-#define io_cb_ping_1(C, ...) PT_CB(IO_CB_PING_1, (C)->obj, __VA_ARGS__)
-#define io_cb_ping_n(C, ...) PT_CB(IO_CB_PING_N, (C)->obj, __VA_ARGS__)
-#define io_cb_signal(S) PT_CB(IO_CB_SIGNAL, NULL, (S))
-
enum io_err_t
{
IO_ERR_NONE,
@@ 119,8 114,7 @@ struct connection
IO_ST_PING, /* Socket connected, network state in question */
} st_cur, /* current thread state */
st_new; /* new thread state */
- int soc;
- mbedtls_net_context tls_fd;
+ mbedtls_net_context net_ctx;
mbedtls_ssl_config tls_conf;
mbedtls_ssl_context tls_ctx;
pthread_mutex_t mtx;
@@ 134,7 128,7 @@ static enum io_state_t io_state_cxed(struct connection*);
static enum io_state_t io_state_cxng(struct connection*);
static enum io_state_t io_state_ping(struct connection*);
static enum io_state_t io_state_rxng(struct connection*);
-static int io_cx_read(struct connection*, unsigned);
+static int io_cx_read(struct connection*, uint32_t);
static void io_fatal(const char*, int);
static void io_sig_handle(int);
static void io_sig_init(void);
@@ 149,10 143,7 @@ static mbedtls_entropy_context tls_entropy;
static mbedtls_x509_crt tls_x509_crt;
static pthread_mutex_t io_cb_mutex = PTHREAD_MUTEX_INITIALIZER;
static struct termios term;
-static unsigned io_cols;
-static unsigned io_rows;
static volatile sig_atomic_t flag_sigwinch_cb; /* sigwinch callback */
-static volatile sig_atomic_t flag_tty_resized; /* sigwinch ws resize */
static const char* io_strerror(char*, size_t);
static int io_net_connect(struct connection*);
@@ 197,13 188,12 @@ int
io_cx(struct connection *cx)
{
enum io_err_t err = IO_ERR_NONE;
- enum io_state_t st;
sigset_t sigset;
sigset_t sigset_old;
PT_LK(&(cx->mtx));
- switch ((st = cx->st_cur)) {
+ switch (cx->st_cur) {
case IO_ST_DXED:
PT_CF(sigfillset(&sigset));
PT_CF(pthread_sigmask(SIG_BLOCK, &sigset, &sigset_old));
@@ 280,34 270,27 @@ io_sendf(struct connection *cx, const char *fmt, ...)
ret = 0;
written = 0;
- if (cx->flags & IO_TLS_ENABLED) {
- do {
- if ((ret = mbedtls_ssl_write(&(cx->tls_ctx), sendbuf + ret, len - ret)) < 0) {
- switch (ret) {
- case MBEDTLS_ERR_SSL_WANT_READ:
- case MBEDTLS_ERR_SSL_WANT_WRITE:
- ret = 0;
- continue;
- default:
- io_dx(cx);
- io_cx(cx);
- return IO_ERR_SSL_WRITE;
- }
- }
- } while ((written += ret) < len);
- } else {
- do {
- if ((ret = send(cx->soc, sendbuf + ret, len - ret, 0)) == -1) {
- switch (errno) {
- case EINTR:
- ret = 0;
- continue;
- default:
- return IO_ERR_SSL_WRITE;
- }
- }
- } while ((written += ret) < len);
- }
+ do {
+ if (cx->flags & IO_TLS_ENABLED) {
+ ret = mbedtls_ssl_write(&(cx->tls_ctx), sendbuf + ret, len - ret);
+ } else {
+ ret = mbedtls_net_send(&(cx->net_ctx), sendbuf + ret, len - ret);
+ }
+
+ if (ret >= 0)
+ continue;
+
+ switch (ret) {
+ case MBEDTLS_ERR_SSL_WANT_READ:
+ case MBEDTLS_ERR_SSL_WANT_WRITE:
+ ret = 0;
+ continue;
+ default:
+ io_dx(cx);
+ io_cx(cx);
+ return IO_ERR_SSL_WRITE;
+ }
+ } while ((written += ret) < len);
return IO_ERR_NONE;
}
@@ 331,14 314,12 @@ io_start(void)
ssize_t ret = read(STDIN_FILENO, buf, sizeof(buf));
if (ret > 0) {
- PT_LK(&io_cb_mutex);
- io_cb_read_inp(buf, ret);
- PT_UL(&io_cb_mutex);
+ IO_CB(io_cb_read_inp(buf, ret));
} else {
if (errno == EINTR) {
if (flag_sigwinch_cb) {
flag_sigwinch_cb = 0;
- io_cb_signal(IO_SIGWINCH);
+ io_tty_winsize();
}
} else {
fatal("read: %s", ret ? strerror(errno) : "EOF");
@@ 356,31 337,12 @@ io_stop(void)
static void
io_tty_winsize(void)
{
- static struct winsize tty_ws;
-
- if (flag_tty_resized == 0) {
- flag_tty_resized = 1;
+ struct winsize tty_ws;
- if (ioctl(0, TIOCGWINSZ, &tty_ws) < 0)
- fatal("ioctl: %s", strerror(errno));
+ if (ioctl(0, TIOCGWINSZ, &tty_ws) < 0)
+ fatal("ioctl: %s", strerror(errno));
- io_rows = tty_ws.ws_row;
- io_cols = tty_ws.ws_col;
- }
-}
-
-unsigned
-io_tty_cols(void)
-{
- io_tty_winsize();
- return io_cols;
-}
-
-unsigned
-io_tty_rows(void)
-{
- io_tty_winsize();
- return io_rows;
+ IO_CB(io_cb_sigwinch(tty_ws.ws_col, tty_ws.ws_row));
}
const char*
@@ 412,7 374,7 @@ io_state_rxng(struct connection *cx)
);
}
- io_cb_info(cx, "Attemping reconnect in %02u:%02u",
+ io_info(cx, "Attemping reconnect in %02u:%02u",
(cx->rx_sleep / 60),
(cx->rx_sleep % 60));
@@ 424,7 386,7 @@ io_state_rxng(struct connection *cx)
static enum io_state_t
io_state_cxng(struct connection *cx)
{
- if ((cx->soc = io_net_connect(cx)) < 0)
+ if ((io_net_connect(cx)) < 0)
return IO_ST_RXNG;
if ((cx->flags & IO_TLS_ENABLED) && io_tls_establish(cx) < 0)
@@ 438,7 400,7 @@ io_state_cxed(struct connection *cx)
{
int ret;
- while ((ret = io_cx_read(cx, IO_PING_MIN)) > 0)
+ while ((ret = io_cx_read(cx, SEC_IN_MS(IO_PING_MIN))) > 0)
continue;
if (ret == MBEDTLS_ERR_SSL_TIMEOUT)
@@ 446,23 408,25 @@ io_state_cxed(struct connection *cx)
switch (ret) {
case MBEDTLS_ERR_SSL_WANT_READ:
- case MBEDTLS_ERR_SSL_WANT_WRITE:
break;
case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY:
- io_cb_info(cx, "connection closed gracefully");
+ io_info(cx, "connection closed gracefully");
break;
case MBEDTLS_ERR_NET_CONN_RESET:
case 0:
- io_cb_err(cx, "connection reset by peer");
+ io_error(cx, "connection reset by peer");
break;
default:
- io_cb_err(cx, "connection tls error");
+ io_error(cx, "connection error");
break;
}
- mbedtls_net_free(&(cx->tls_fd));
- mbedtls_ssl_config_free(&(cx->tls_conf));
- mbedtls_ssl_free(&(cx->tls_ctx));
+ mbedtls_net_free(&(cx->net_ctx));
+
+ if (cx->flags & IO_TLS_ENABLED) {
+ mbedtls_ssl_config_free(&(cx->tls_conf));
+ mbedtls_ssl_free(&(cx->tls_ctx));
+ }
return IO_ST_CXNG;
}
@@ 475,7 439,7 @@ io_state_ping(struct connection *cx)
if (cx->ping >= IO_PING_MAX)
return IO_ST_CXNG;
- if ((ret = io_cx_read(cx, IO_PING_REFRESH)) > 0)
+ if ((ret = io_cx_read(cx, SEC_IN_MS(IO_PING_REFRESH))) > 0)
return IO_ST_CXED;
if (ret == MBEDTLS_ERR_SSL_TIMEOUT)
@@ 483,23 447,25 @@ io_state_ping(struct connection *cx)
switch (ret) {
case MBEDTLS_ERR_SSL_WANT_READ:
- case MBEDTLS_ERR_SSL_WANT_WRITE:
break;
case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY:
- io_cb_info(cx, "connection closed gracefully");
+ io_info(cx, "connection closed gracefully");
break;
case MBEDTLS_ERR_NET_CONN_RESET:
case 0:
- io_cb_err(cx, "connection reset by peer");
+ io_error(cx, "connection reset by peer");
break;
default:
- io_cb_err(cx, "connection ssl error");
+ io_error(cx, "connection error");
break;
}
- mbedtls_net_free(&(cx->tls_fd));
- mbedtls_ssl_config_free(&(cx->tls_conf));
- mbedtls_ssl_free(&(cx->tls_ctx));
+ mbedtls_net_free(&(cx->net_ctx));
+
+ if (cx->flags & IO_TLS_ENABLED) {
+ mbedtls_ssl_config_free(&(cx->tls_conf));
+ mbedtls_ssl_free(&(cx->tls_ctx));
+ }
return IO_ST_CXNG;
}
@@ 519,6 485,8 @@ io_thread(void *arg)
cx->st_cur = IO_ST_CXNG;
+ io_info(cx, "Connecting to %s:%s", cx->host, cx->port);
+
do {
enum io_state_t st_cur;
enum io_state_t st_new;
@@ 547,43 515,40 @@ io_thread(void *arg)
switch (ST_X(st_cur, st_new)) {
case ST_X(IO_ST_DXED, IO_ST_CXNG): /* A1 */
case ST_X(IO_ST_RXNG, IO_ST_CXNG): /* A2,C */
- io_cb_info(cx, "Connecting to %s:%s", cx->host, cx->port);
+ io_info(cx, "Connecting to %s:%s", cx->host, cx->port);
break;
case ST_X(IO_ST_CXED, IO_ST_CXNG): /* F1 */
- io_cb_dxed(cx);
+ io_dxed(cx);
break;
case ST_X(IO_ST_PING, IO_ST_CXNG): /* F2 */
- io_cb_err(cx, "Connection timeout (%u)", cx->ping);
- io_cb_dxed(cx);
+ io_error(cx, "Connection timeout (%u)", cx->ping);
+ io_dxed(cx);
break;
case ST_X(IO_ST_RXNG, IO_ST_DXED): /* B1 */
case ST_X(IO_ST_CXNG, IO_ST_DXED): /* B2 */
- io_cb_info(cx, "Connection cancelled");
+ io_info(cx, "Connection cancelled");
break;
case ST_X(IO_ST_CXED, IO_ST_DXED): /* B3 */
case ST_X(IO_ST_PING, IO_ST_DXED): /* B4 */
- io_cb_info(cx, "Connection closed");
- io_cb_dxed(cx);
+ io_info(cx, "Connection closed");
+ io_dxed(cx);
break;
case ST_X(IO_ST_CXNG, IO_ST_CXED): /* D */
- io_cb_info(cx, " .. Connection successful");
- io_cb_cxed(cx);
+ io_info(cx, " .. Connection successful");
+ io_cxed(cx);
cx->rx_sleep = 0;
break;
case ST_X(IO_ST_CXNG, IO_ST_RXNG): /* E */
- io_cb_err(cx, " .. Connection failed -- retrying");
+ io_error(cx, " .. Connection failed -- retrying");
break;
case ST_X(IO_ST_CXED, IO_ST_PING): /* G */
- cx->ping = IO_PING_MIN;
- io_cb_ping_1(cx, cx->ping);
+ io_ping(cx, (cx->ping = IO_PING_MIN));
break;
case ST_X(IO_ST_PING, IO_ST_PING): /* H */
- cx->ping += IO_PING_REFRESH;
- io_cb_ping_n(cx, cx->ping);
+ io_ping(cx, (cx->ping += IO_PING_REFRESH));
break;
case ST_X(IO_ST_PING, IO_ST_CXED): /* I */
- cx->ping = 0;
- io_cb_ping_0(cx, cx->ping);
+ io_ping(cx, (cx->ping = 0));
break;
default:
fatal("BAD ST_X from: %d to: %d", st_cur, st_new);
@@ 595,24 560,35 @@ io_thread(void *arg)
}
static int
-io_cx_read(struct connection *cx, unsigned timeout)
+io_cx_read(struct connection *cx, uint32_t timeout)
{
int ret;
+ struct pollfd fd[1];
unsigned char buf[1024];
+ fd[0].fd = cx->net_ctx.fd;
+ fd[0].events = POLLIN;
+
+ while ((ret = poll(fd, 1, timeout)) < 0 && errno == EAGAIN)
+ continue;
+
+ if (ret == 0)
+ return MBEDTLS_ERR_SSL_TIMEOUT;
+
+ if (ret < 0 && errno == EINTR)
+ return MBEDTLS_ERR_SSL_WANT_READ;
+
+ if (ret < 0)
+ fatal("poll: %s", strerror(errno));
+
if (cx->flags & IO_TLS_ENABLED) {
- mbedtls_ssl_conf_read_timeout(&(cx->tls_conf), SEC_IN_MS(timeout));
- if ((ret = mbedtls_ssl_read(&(cx->tls_ctx), buf, sizeof(buf))) > 0) {
- PT_LK(&io_cb_mutex);
- io_cb_read_soc((char *)buf, (size_t)ret, cx->obj);
- PT_UL(&io_cb_mutex);
- }
+ ret = mbedtls_ssl_read(&(cx->tls_ctx), buf, sizeof(buf));
} else {
- while ((ret = recv(cx->soc, buf, sizeof(buf), 0)) > 0) {
- PT_LK(&io_cb_mutex);
- io_cb_read_soc((char *)buf, (size_t)ret, cx->obj);
- PT_UL(&io_cb_mutex);
- }
+ ret = mbedtls_net_recv(&(cx->net_ctx), buf, sizeof(buf));
+ }
+
+ if (ret > 0) {
+ IO_CB(io_cb_read_soc((char *)buf, (size_t)ret, cx->obj));
}
return ret;
@@ 633,10 609,8 @@ io_fatal(const char *f, int errnum)
static void
io_sig_handle(int sig)
{
- if (sig == SIGWINCH) {
+ if (sig == SIGWINCH)
flag_sigwinch_cb = 1;
- flag_tty_resized = 0;
- }
}
static void
@@ 676,6 650,8 @@ io_tty_init(void)
if (atexit(io_tty_term))
fatal("atexit");
+
+ io_tty_winsize();
}
static void
@@ 716,10 692,10 @@ io_net_connect(struct connection *cx)
return -1;
if (ret == EAI_SYSTEM) {
- io_cb_err(cx, " .. Failed to resolve host: %s",
+ io_error(cx, " .. Failed to resolve host: %s",
io_strerror(buf, sizeof(buf)));
} else {
- io_cb_err(cx, " .. Failed to resolve host: %s",
+ io_error(cx, " .. Failed to resolve host: %s",
gai_strerror(ret));
}
@@ 742,13 718,13 @@ io_net_connect(struct connection *cx)
goto err;
}
- if (!p && soc < -1) {
- io_cb_err(cx, " .. Failed to obtain socket: %s", io_strerror(buf, sizeof(buf)));
+ if (!p && soc < 0) {
+ io_error(cx, " .. Failed to obtain socket: %s", io_strerror(buf, sizeof(buf)));
goto err;
}
if (!p && soc >= 0) {
- io_cb_err(cx, " .. Failed to connect: %s", io_strerror(buf, sizeof(buf)));
+ io_error(cx, " .. Failed to connect: %s", io_strerror(buf, sizeof(buf)));
goto err;
}
@@ 758,14 734,14 @@ io_net_connect(struct connection *cx)
addr = &(((struct sockaddr_in6*)p->ai_addr)->sin6_addr);
if (inet_ntop(p->ai_family, addr, buf, sizeof(buf)))
- io_cb_info(cx, " .. Connected [%s]", buf);
+ io_info(cx, " .. Connected [%s]", buf);
ret = soc;
err:
freeaddrinfo(res);
- return ret;
+ return (cx->net_ctx.fd = ret);
}
static void
@@ 793,20 769,17 @@ io_tls_establish(struct connection *cx)
{
int ret;
- io_cb_info(cx, " .. Establishing TLS connection");
+ io_info(cx, " .. Establishing TLS connection");
- mbedtls_net_init(&(cx->tls_fd));
mbedtls_ssl_init(&(cx->tls_ctx));
mbedtls_ssl_config_init(&(cx->tls_conf));
- cx->tls_fd.fd = cx->soc;
-
if ((ret = mbedtls_ssl_config_defaults(
&(cx->tls_conf),
MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT))) {
- io_cb_err(cx, " .. %s ", io_tls_err(ret));
+ io_error(cx, " .. %s ", io_tls_err(ret));
goto err;
}
@@ 834,27 807,27 @@ io_tls_establish(struct connection *cx)
mbedtls_ssl_conf_authmode(&(cx->tls_conf), MBEDTLS_SSL_VERIFY_REQUIRED);
}
- if ((ret = mbedtls_net_set_block(&(cx->tls_fd)))) {
- io_cb_err(cx, " .. %s ", io_tls_err(ret));
+ if ((ret = mbedtls_net_set_block(&(cx->net_ctx)))) {
+ io_error(cx, " .. %s ", io_tls_err(ret));
goto err;
}
if ((ret = mbedtls_ssl_setup(&(cx->tls_ctx), &(cx->tls_conf)))) {
- io_cb_err(cx, " .. %s ", io_tls_err(ret));
+ io_error(cx, " .. %s ", io_tls_err(ret));
goto err;
}
if ((ret = mbedtls_ssl_set_hostname(&(cx->tls_ctx), cx->host))) {
- io_cb_err(cx, " .. %s ", io_tls_err(ret));
+ io_error(cx, " .. %s ", io_tls_err(ret));
goto err;
}
mbedtls_ssl_set_bio(
&(cx->tls_ctx),
- &(cx->tls_fd),
+ &(cx->net_ctx),
mbedtls_net_send,
- NULL,
- mbedtls_net_recv_timeout);
+ mbedtls_net_recv,
+ NULL);
while ((ret = mbedtls_ssl_handshake(&(cx->tls_ctx)))) {
if (ret != MBEDTLS_ERR_SSL_WANT_READ
@@ 863,31 836,31 @@ io_tls_establish(struct connection *cx)
}
if (ret && cx->flags & IO_TLS_VRFY_DISABLED) {
- io_cb_err(cx, " .. %s ", io_tls_err(ret));
+ io_error(cx, " .. %s ", io_tls_err(ret));
goto err;
}
if (io_tls_x509_vrfy(cx) < 0)
- io_cb_err(cx, " .... Unknown x509 error");
+ io_error(cx, " .... Unknown x509 error");
if (ret) {
- io_cb_err(cx, " .. %s ", io_tls_err(ret));
+ io_error(cx, " .. %s ", io_tls_err(ret));
goto err;
}
- io_cb_info(cx, " .. TLS connection established");
- io_cb_info(cx, " .. - version: %s", mbedtls_ssl_get_version(&(cx->tls_ctx)));
- io_cb_info(cx, " .. - ciphersuite: %s", mbedtls_ssl_get_ciphersuite(&(cx->tls_ctx)));
+ io_info(cx, " .. TLS connection established");
+ io_info(cx, " .. - version: %s", mbedtls_ssl_get_version(&(cx->tls_ctx)));
+ io_info(cx, " .. - ciphersuite: %s", mbedtls_ssl_get_ciphersuite(&(cx->tls_ctx)));
return 0;
err:
- io_cb_err(cx, " .. TLS connection failure");
+ io_error(cx, " .. TLS connection failure");
mbedtls_ssl_config_free(&(cx->tls_conf));
mbedtls_ssl_free(&(cx->tls_ctx));
- mbedtls_net_free(&(cx->tls_fd));
+ mbedtls_net_free(&(cx->net_ctx));
return -1;
}
@@ 914,7 887,7 @@ io_tls_x509_vrfy(struct connection *cx)
if ((p = strchr(buf, '\n')))
*p++ = 0;
- io_cb_err(cx, " .... %s", s);
+ io_error(cx, " .... %s", s);
} while ((s = p));
M src/io.h => src/io.h +23 -57
@@ 1,5 1,5 @@
-#ifndef IO_H
-#define IO_H
+#ifndef RIRC_IO_H
+#define RIRC_IO_H
/* Handling off all network io, user input and signals
*
@@ 49,20 49,17 @@
* (B) io_dx: close network connection
*
* Network state implicit transitions result in informational callback types:
- * (C) on connection attempt: IO_CB_INFO
- * (E) on connection failure: IO_CB_ERROR
- * (D) on connection success: IO_CB_CXED
- * (F) on connection loss: IO_CB_DXED
- * (G) on ping timeout start: IO_CB_PING_1
- * (H) on ping timeout update: IO_CB_PING_N
- * (I) on ping normal: IO_CB_PING_0
+ * (D) on connection success: io_cb_cxed
+ * (F) on connection loss: io_cb_dxed
+ * (G) on ping timeout start: io_cb_ping
+ * (H) on ping timeout update: io_cb_ping
+ * (I) on ping normal: io_cb_ping
*
* Successful reads on stdin and connected sockets result in data callbacks:
* from stdin: io_cb_read_inp
* from socket: io_cb_read_soc
*
- * Signals registered to be caught result in non-signal handler context
- * callback with type IO_CB_SIGNAL
+ * SIGWINCH results in a non signal-handler context callback io_cb_singwinch
*
* Failed connection attempts enter a retry cycle with exponential
* backoff time given by:
@@ 77,37 74,6 @@
#include <stddef.h>
#include <stdint.h>
-struct connection;
-
-enum io_cb_t
-{
- IO_CB_INVALID,
- IO_CB_CXED, /* no args */
- IO_CB_DXED, /* no args */
- IO_CB_ERR, /* <const char *fmt>, [args, ...] */
- IO_CB_INFO, /* <const char *fmt>, [args, ...] */
- IO_CB_PING_0, /* <unsigned ping> */
- IO_CB_PING_1, /* <unsigned ping> */
- IO_CB_PING_N, /* <unsigned ping> */
- IO_CB_SIGNAL, /* <io_sig_t sig> */
- IO_CB_SIZE
-};
-
-enum io_log_level
-{
- IO_LOG_ERROR,
- IO_LOG_WARN,
- IO_LOG_INFO,
- IO_LOG_DEBUG,
-};
-
-enum io_sig_t
-{
- IO_SIG_INVALID,
- IO_SIGWINCH,
- IO_SIG_SIZE
-};
-
#define IO_IPV_UNSPEC (1 << 1)
#define IO_IPV_4 (1 << 2)
#define IO_IPV_6 (1 << 3)
@@ 117,7 83,8 @@ enum io_sig_t
#define IO_TLS_VRFY_OPTIONAL (1 << 7)
#define IO_TLS_VRFY_REQUIRED (1 << 8)
-/* Returns a connection, or NULL if limit is reached */
+struct connection;
+
struct connection* connection(
const void*, /* callback object */
const char*, /* host */
@@ 133,26 100,25 @@ int io_dx(struct connection*);
/* Formatted write to connection */
int io_sendf(struct connection*, const char*, ...);
-/* Init/start/stop IO context */
-void io_init(void);
-void io_start(void);
-void io_stop(void);
-
-/* Get tty dimensions */
-unsigned io_tty_cols(void);
-unsigned io_tty_rows(void);
-
/* IO error string */
const char* io_err(int);
-/* IO state callback */
-void io_cb(enum io_cb_t, const void*, ...);
-
/* IO data callback */
void io_cb_read_inp(char*, size_t);
void io_cb_read_soc(char*, size_t, const void*);
-/* Log message callback */
-void io_cb_log(const void*, enum io_log_level, const char*, ...);
+/* IO event callbacks */
+void io_cb_cxed(const void*);
+void io_cb_dxed(const void*);
+void io_cb_ping(const void*, unsigned);
+void io_cb_sigwinch(unsigned, unsigned);
+
+/* IO informational callbacks */
+void io_cb_error(const void*, const char*, ...);
+void io_cb_info(const void*, const char*, ...);
+
+void io_init(void);
+void io_start(void);
+void io_stop(void);
#endif
M src/rirc.c => src/rirc.c +3 -2
@@ 1,3 1,5 @@
+#include "src/rirc.h"
+
#include <errno.h>
#include <getopt.h>
#include <pwd.h>
@@ 287,7 289,6 @@ parse_args(int argc, char **argv)
default_realname = getpwuid_pw_name();
state_init();
- draw_init();
for (size_t i = 0; i < n_servers; i++) {
@@ 344,8 345,8 @@ main(int argc, char **argv)
if ((ret = parse_args(argc, argv)) == 0) {
io_start();
- draw_term();
state_term();
+ draw(DRAW_CLEAR);
}
return ret;
M src/rirc.h => src/rirc.h +2 -2
@@ 1,5 1,5 @@
-#ifndef RIRC_H
-#define RIRC_H
+#ifndef RIRC_RIRC_H
+#define RIRC_RIRC_H
/* Default config values obtained at runtime */
M src/state.c => src/state.c +258 -367
@@ 1,8 1,4 @@
-/**
- * state.c
- *
- * All manipulation of global program state
- */
+#include "src/state.h"
#include <ctype.h>
#include <stdlib.h>
@@ 11,22 7,19 @@
#include <stdarg.h>
#include <stdio.h>
+#include "config.h"
+#include "src/components/channel.h"
#include "src/draw.h"
#include "src/handlers/irc_recv.h"
#include "src/handlers/irc_send.h"
#include "src/io.h"
#include "src/rirc.h"
-#include "src/state.h"
#include "src/utils/utils.h"
/* See: https://vt100.net/docs/vt100-ug/chapter3.html */
#define CTRL(k) ((k) & 0x1f)
static void _newline(struct channel*, enum buffer_line_t, const char*, const char*, va_list);
-static void state_io_cxed(struct server*);
-static void state_io_dxed(struct server*);
-static void state_io_ping(struct server*, unsigned int);
-static void state_io_signal(enum io_sig_t);
static int state_input_linef(struct channel*);
static int state_input_ctrlch(const char*, size_t);
@@ 36,6 29,15 @@ static uint16_t state_complete(char*, uint16_t, uint16_t, int);
static uint16_t state_complete_list(char*, uint16_t, uint16_t, const char**);
static uint16_t state_complete_user(char*, uint16_t, uint16_t, int);
+static void state_channel_clear(int);
+static void state_channel_close(int);
+
+static int action_clear(char);
+static int action_close(char);
+static int action_error(char);
+static int (*action_handler)(char);
+static char action_buff[256];
+
static void command(struct channel*, char*);
static struct
@@ 45,6 47,9 @@ static struct
struct server_list servers;
} state;
+static unsigned state_tty_cols;
+static unsigned state_tty_rows;
+
struct server_list*
state_server_list(void)
{
@@ 69,6 74,8 @@ static const char *irc_list[] = {
"ctcp-time",
"ctcp-userinfo",
"ctcp-version",
+ "away",
+ "topic-unset",
"admin", "connect", "info", "invite", "join",
"kick", "kill", "links", "list", "lusers",
"mode", "motd", "names", "nick", "notice",
@@ 77,15 84,14 @@ static const char *irc_list[] = {
"time", "topic", "trace", "user", "version",
"who", "whois", "whowas", NULL };
-// TODO: from command handler list
/* List of rirc commands for tab completeion */
static const char *cmd_list[] = {
- "clear", "close", "connect", "disconnect", "quit", "set", NULL};
+ "clear", "close", "connect", "disconnect", "quit", NULL};
void
state_init(void)
{
- state.default_channel = state.current_channel = channel("rirc", CHANNEL_T_OTHER);
+ state.default_channel = state.current_channel = channel("rirc", CHANNEL_T_RIRC);
newline(state.default_channel, 0, "--", " _");
newline(state.default_channel, 0, "--", " _ __(_)_ __ ___");
@@ 103,12 109,17 @@ state_init(void)
void
state_term(void)
{
- /* Exit handler; must return normally */
-
- struct server *s1, *s2;
+ struct server *s1;
+ struct server *s2;
channel_free(state.default_channel);
+ state.current_channel = NULL;
+ state.default_channel = NULL;
+
+ action_handler = NULL;
+ action_buff[0] = 0;
+
if ((s1 = state_server_list()->head) == NULL)
return;
@@ 118,6 129,21 @@ state_term(void)
connection_free(s2->connection);
server_free(s2);
} while (s1 != state_server_list()->head);
+
+ state.servers.head = NULL;
+ state.servers.tail = NULL;
+}
+
+unsigned
+state_cols(void)
+{
+ return state_tty_cols;
+}
+
+unsigned
+state_rows(void)
+{
+ return state_tty_rows;
}
void
@@ 230,106 256,56 @@ state_server_set_chans(struct server *s, const char *chans)
return 0;
}
-void
-channel_clear(struct channel *c)
-{
- memset(&(c->buffer), 0, sizeof(c->buffer));
- draw(DRAW_BUFFER);
-}
-
-/* WIP:
- *
- * removed action subsystem from input.c
- *
- * eventually should go in action.{h,c}
- *
- */
-/* Max length of user action message */
-#define MAX_ACTION_MESG 256
-char *action_message;
-static int action_close_server(char);
-/* Action handling */
-static int (*action_handler)(char);
-static char action_buff[MAX_ACTION_MESG];
-/* Incremental channel search */
-static int action_find_channel(char);
-/* TODO: This is a first draft for simple channel searching functionality.
- *
- * It can be cleaned up, and input.c is probably not the most ideal place for this */
-#define MAX_SEARCH 128
-struct channel *search_cptr; /* Used for iterative searching, before setting the current channel */
-static char search_buf[MAX_SEARCH];
-static size_t search_i;
-
-static struct channel* search_channels(struct channel*, char*);
-static struct channel*
-search_channels(struct channel *start, char *search)
-{
- if (start == NULL || *search == '\0')
- return NULL;
-
- /* Start the search one past the input */
- struct channel *c = channel_get_next(start);
-
- while (c != start) {
-
- if (strstr(c->name, search))
- return c;
-
- c = channel_get_next(c);
- }
-
- return NULL;
-}
static int
state_input_action(const char *input, size_t len)
{
/* Waiting for user confirmation */
+ /* ^c canceled the action, or the action was resolved */
if (len == 1 && (*input == CTRL('c') || action_handler(*input))) {
- /* ^c canceled the action, or the action was resolved */
-
- action_message = NULL;
action_handler = NULL;
-
return 1;
}
return 0;
}
+
static int
-action_close_server(char c)
+action_error(char c)
{
- /* Confirm closing a server */
-
- if (c == 'n' || c == 'N')
- return 1;
+ UNUSED(c);
- if (c == 'y' || c == 'Y') {
-
- int ret;
- struct channel *c = current_channel();
- struct server *s = c->server;
+ return 1;
+}
- /* If closing the last server */
- if ((state.current_channel = c->server->next->channel) == c->server->channel)
- state.current_channel = state.default_channel;
+static int
+action_clear(char c)
+{
+ if (toupper(c) == 'N')
+ return 1;
- if ((ret = io_sendf(s->connection, "QUIT :%s", DEFAULT_QUIT_MESG)))
- newlinef(s->channel, 0, "-!!-", "sendf fail: %s", io_err(ret));
+ if (toupper(c) == 'Y') {
+ state_channel_clear(0);
+ return 1;
+ }
- io_dx(s->connection);
- connection_free(s->connection);
- server_list_del(state_server_list(), s);
- server_free(s);
+ return 0;
+}
- draw(DRAW_ALL);
+static int
+action_close(char c)
+{
+ if (toupper(c) == 'N')
+ return 1;
+ if (toupper(c) == 'Y') {
+ state_channel_close(0);
return 1;
}
return 0;
}
+
void
action(int (*a_handler)(char), const char *fmt, ...)
{
@@ 343,130 319,84 @@ action(int (*a_handler)(char), const char *fmt, ...)
va_list ap;
va_start(ap, fmt);
- len = vsnprintf(action_buff, MAX_ACTION_MESG, fmt, ap);
+ len = vsnprintf(action_buff, sizeof(action_buff), fmt, ap);
va_end(ap);
if (len < 0) {
debug("vsnprintf failed");
} else {
action_handler = a_handler;
- action_message = action_buff;
draw(DRAW_INPUT);
}
}
-/* Action line should be:
- *
- *
- * Find: [current result]/[(server if not current server[socket if not 6697])] : <search input> */
-static int
-action_find_channel(char c)
-{
- /* Incremental channel search */
- /* \n, Esc, ^C cancels a search if no results are found */
- if (c == '\n' || c == 0x1b || c == CTRL('c')) {
+const char*
+action_message(void)
+{
+ return (action_handler ? action_buff : NULL);
+}
- /* Confirm non-empty match */
- if (c == '\n' && search_cptr)
- channel_set_current(search_cptr);
+static void
+state_channel_clear(int action_confirm)
+{
+ struct channel *c = current_channel();
- search_buf[0] = 0;
- search_i = 0;
- search_cptr = NULL;
- return 1;
+ if (action_confirm) {
+ action(action_clear, "Clear buffer '%s'? [y/n]", c->name);
+ } else {
+ memset(&(c->buffer), 0, sizeof(c->buffer));
+ draw(DRAW_BUFFER);
}
+}
- /* ^F repeats the search forward from the current result,
- * or resets search criteria if no match */
- if (c == CTRL('f')) {
- if (search_cptr == NULL) {
- search_buf[0] = 0;
- search_i = 0;
- action(action_find_channel, "Find: ");
- return 0;
- }
+static void
+state_channel_close(int action_confirm)
+{
+ /* Close the current channel */
- search_cptr = search_channels(search_cptr, search_buf);
- } else if (c == 0x7f && search_i) {
- /* Backspace */
- search_buf[--search_i] = 0;
- search_cptr = search_channels(current_channel(), search_buf);
-
- } else if (isprint(c) && search_i < (sizeof(search_buf) - 1)) {
- /* All other input */
- search_buf[search_i++] = c;
- search_buf[search_i] = 0;
- search_cptr = search_channels(current_channel(), search_buf);
- }
+ struct channel *c = current_channel();
+ struct server *s = c->server;
- /* Reprint the action message */
- if (search_cptr == NULL) {
- if (*search_buf)
- action(action_find_channel, "Find: NO MATCH -- %s", search_buf);
- else
- action(action_find_channel, "Find: ");
- } else {
- /* Found a channel */
- if (search_cptr->server == current_channel()->server) {
- action(action_find_channel, "Find: %s -- %s",
- search_cptr->name, search_buf);
- } else {
- if (!strcmp(search_cptr->server->port, "6697"))
- action(action_find_channel, "Find: %s/%s -- %s",
- search_cptr->server->host, search_cptr->name, search_buf);
- else
- action(action_find_channel, "Find: %s:%s/%s -- %s",
- search_cptr->server->host, search_cptr->server->port,
- search_cptr->name, search_buf);
- }
+ if (c->type == CHANNEL_T_RIRC) {
+ action(action_error, "Type :quit to exit rirc");
+ return;
}
- return 0;
-}
+ if (action_confirm) {
-void
-channel_close(struct channel *c)
-{
- /* Close a channel. If the current channel is being
- * closed, update state appropriately */
+ if (c->type == CHANNEL_T_CHANNEL || c->type == CHANNEL_T_PRIVATE)
+ action(action_close, "Close '%s'? [y/n]", c->name);
+
+ if (c->type == CHANNEL_T_SERVER)
+ action(action_close, "Close server '%s'? [%d channels] [y/n])",
+ c->name, (s->clist.count - 1));
- if (c == state.default_channel) {
- newline(c, 0, "--", "Type :quit to exit rirc");
return;
}
- if (c->type == CHANNEL_T_SERVER) {
- /* Closing a server, confirm the number of channels being closed */
+ if (c->type == CHANNEL_T_CHANNEL || c->type == CHANNEL_T_PRIVATE) {
- int num_chans = 0;
+ if (s->connected && c->type == CHANNEL_T_CHANNEL && !c->parted)
+ io_sendf(s->connection, "PART %s :%s", c->name, DEFAULT_PART_MESG);
- while ((c = c->next)->type != CHANNEL_T_SERVER)
- num_chans++;
+ channel_set_current(c->next);
+ channel_list_del(&(s->clist), c);
+ channel_free(c);
+ return;
+ }
- if (num_chans)
- action(action_close_server, "Close server '%s'? Channels: %d [y/n]",
- c->server->host, num_chans);
- else
- action(action_close_server, "Close server '%s'? [y/n]", c->server->host);
- } else {
- /* Closing a channel */
- if (c->type == CHANNEL_T_CHANNEL && !c->parted) {
- int ret;
- if (0 != (ret = io_sendf(c->server->connection, "PART %s", c->name))) {
- // FIXME: closing a parted channel when server is disconnected isnt an error
- newlinef(c->server->channel, 0, "sendf fail", "%s", io_err(ret));
- }
- }
+ if (c->type == CHANNEL_T_SERVER) {
- /* If closing the current channel, update state to a new channel */
- if (c == current_channel()) {
- channel_set_current(c->next);
- } else {
- draw(DRAW_NAV);
+ if (s->connected) {
+ io_sendf(s->connection, "QUIT :%s", DEFAULT_QUIT_MESG);
+ io_dx(s->connection);
}
- channel_list_del(&c->server->clist, c);
- channel_free(c);
+ channel_set_current((s->next != s ? s->next->channel : state.default_channel));
+ connection_free(s->connection);
+ server_list_del(state_server_list(), s);
+ server_free(s);
+ return;
}
}
@@ 482,8 412,8 @@ buffer_scrollback_back(struct channel *c)
unsigned int buffer_i = b->scrollback,
count = 0,
text_w = 0,
- cols = io_tty_cols(),
- rows = io_tty_rows() - 4;
+ cols = state_tty_cols,
+ rows = state_tty_rows - 4;
struct buffer_line *line = buffer_line(b, buffer_i);
@@ 524,8 454,8 @@ buffer_scrollback_forw(struct channel *c)
unsigned int count = 0,
text_w = 0,
- cols = io_tty_cols(),
- rows = io_tty_rows() - 4;
+ cols = state_tty_cols,
+ rows = state_tty_rows - 4;
struct buffer *b = &c->buffer;
@@ 691,183 621,80 @@ state_complete(char *str, uint16_t len, uint16_t max, int first)
}
static void
-state_io_cxed(struct server *s)
-{
- int ret;
- server_reset(s);
- server_nicks_next(s);
-
- if ((ret = io_sendf(s->connection, "CAP LS " IRCV3_CAP_VERSION)))
- newlinef(s->channel, 0, "-!!-", "sendf fail: %s", io_err(ret));
-
- if (s->pass && (ret = io_sendf(s->connection, "PASS %s", s->pass)))
- newlinef(s->channel, 0, "-!!-", "sendf fail: %s", io_err(ret));
-
- if ((ret = io_sendf(s->connection, "NICK %s", s->nick)))
- newlinef(s->channel, 0, "-!!-", "sendf fail: %s", io_err(ret));
-
- if ((ret = io_sendf(s->connection, "USER %s 8 * :%s", s->username, s->realname)))
- newlinef(s->channel, 0, "-!!-", "sendf fail: %s", io_err(ret));
-
- draw(DRAW_STATUS);
-}
-
-static void
-state_io_dxed(struct server *s)
-{
- struct channel *c = s->channel;
-
- do {
- newline(c, 0, "-!!-", " -- disconnected --");
- channel_reset(c);
- c = c->next;
- } while (c != s->channel);
-}
-
-static void
-state_io_ping(struct server *s, unsigned int ping)
+command(struct channel *c, char *buf)
{
- int ret;
+ const char *arg;
+ const char *cmd;
+ int err;
- s->ping = ping;
+ if (!(cmd = strsep(&buf)))
+ return;
- if (ping != IO_PING_MIN)
- draw(DRAW_STATUS);
- else if ((ret = io_sendf(s->connection, "PING :%s", s->host)))
- newlinef(s->channel, 0, "-!!-", "sendf fail: %s", io_err(ret));
-}
+ if (!strcasecmp(cmd, "clear")) {
+ if ((arg = strsep(&buf))) {
+ action(action_error, "clear: Unknown arg '%s'", arg);
+ return;
+ }
-static void
-state_io_signal(enum io_sig_t sig)
-{
- switch (sig) {
- case IO_SIGWINCH:
- draw(DRAW_ALL);
- break;
- default:
- newlinef(state.default_channel, 0, "-!!-", "unhandled signal %d", sig);
+ state_channel_clear(0);
+ return;
}
-}
-
-void
-io_cb(enum io_cb_t type, const void *cb_obj, ...)
-{
- struct server *s = (struct server *)cb_obj;
- va_list ap;
- va_start(ap, cb_obj);
+ if (!strcasecmp(cmd, "close")) {
+ if ((arg = strsep(&buf))) {
+ action(action_error, "close: Unknown arg '%s'", arg);
+ return;
+ }
- switch (type) {
- case IO_CB_CXED:
- state_io_cxed(s);
- break;
- case IO_CB_DXED:
- state_io_dxed(s);
- break;
- case IO_CB_PING_0:
- case IO_CB_PING_1:
- case IO_CB_PING_N:
- state_io_ping(s, va_arg(ap, unsigned int));
- break;
- case IO_CB_ERR:
- _newline(s->channel, 0, "-!!-", va_arg(ap, const char *), ap);
- break;
- case IO_CB_INFO:
- _newline(s->channel, 0, "--", va_arg(ap, const char *), ap);
- break;
- case IO_CB_SIGNAL:
- state_io_signal(va_arg(ap, enum io_sig_t));
- break;
- default:
- fatal("unhandled io_cb_t: %d", type);
+ state_channel_close(0);
+ return;
}
- va_end(ap);
+ if (!strcasecmp(cmd, "connect")) {
+ if (!c->server) {
+ action(action_error, "connect: This is not a server");
+ return;
+ }
- draw(DRAW_FLUSH);
-}
+ if ((arg = strsep(&buf))) {
+ action(action_error, "connect: Unknown arg '%s'", arg);
+ return;
+ }
-static void
-command(struct channel *c, char *buf)
-{
- const char *cmnd;
- int err;
+ if ((err = io_cx(c->server->connection)))
+ action(action_error, "connect: %s", io_err(err));
- if (!(cmnd = strsep(&buf))) {
- newline(c, 0, "-!!-", "Messages beginning with ':' require a command");
return;
}
- if (!strcasecmp(cmnd, "quit")) {
- io_stop();
- }
+ if (!strcasecmp(cmd, "disconnect")) {
+ if (!c->server) {
+ action(action_error, "disconnect: This is not a server");
+ return;
+ }
- if (!strcasecmp(cmnd, "connect")) {
- // TODO: parse --args
- const char *host = strsep(&buf);
- const char *port = strsep(&buf);
- const char *pass = strsep(&buf);
- const char *user = strsep(&buf);
- const char *real = strsep(&buf);
- const char *help = ":connect [host [port] [pass] [user] [real]]";
- struct server *s;
-
- if (host == NULL) {
- if (c->server == NULL) {
- newlinef(c, 0, "-!!-", "%s", help);
- } else if ((err = io_cx(c->server->connection))) {
- newlinef(c, 0, "-!!-", "%s", io_err(err));
- }
- } else {
- port = (port ? port : "6697");
- user = (user ? user : default_username);
- real = (real ? real : default_realname);
-
- if ((s = server_list_get(&state.servers, host, port)) != NULL) {
- channel_set_current(s->channel);
- newlinef(s->channel, 0, "-!!-", "already connected to %s:%s", host, port);
- } else {
- s = server(host, port, pass, user, real);
- s->connection = connection(s, host, port, 0);
- server_list_add(state_server_list(), s);
- channel_set_current(s->channel);
- io_cx(s->connection);
- draw(DRAW_ALL);
- }
+ if ((arg = strsep(&buf))) {
+ action(action_error, "disconnect: Unknown arg '%s'", arg);
+ return;
}
- return;
- }
- if (!strcasecmp(cmnd, "disconnect")) {
- io_dx(c->server->connection);
- return;
- }
+ if ((err = io_dx(c->server->connection)))
+ action(action_error, "disconnect: %s", io_err(err));
- if (!strcasecmp(cmnd, "clear")) {
- channel_clear(c);
return;
}
- if (!strcasecmp(cmnd, "close")) {
- channel_close(c);
- return;
- }
+ if (!strcasecmp(cmd, "quit")) {
+ if ((arg = strsep(&buf))) {
+ action(action_error, "quit: Unknown arg '%s'", arg);
+ return;
+ }
- if (!strcasecmp(cmnd, "set")) {
- /* TODO user, real, nicks, pass, key */
+ io_stop();
return;
}
- /* TODO:
- * help
- * ignore
- * unignore
- * version
- * find
- * buffers
- * b#
- * b<num>
- */
+ action(action_error, "Unknown command '%s'", cmd);
}
static int
@@ 929,16 756,9 @@ state_input_ctrlch(const char *c, size_t len)
/* Cancel current input */
return input_reset(&(current_channel()->input));
- case CTRL('f'):
- /* Find channel */
- if (current_channel()->server)
- action(action_find_channel, "Find: ");
- break;
-
case CTRL('l'):
/* Clear current channel */
- /* TODO: as action with confirmation */
- channel_clear(current_channel());
+ state_channel_clear(1);
break;
case CTRL('p'):
@@ 952,8 772,7 @@ state_input_ctrlch(const char *c, size_t len)
break;
case CTRL('x'):
- /* Close current channel */
- channel_close(current_channel());
+ state_channel_close(1);
break;
case CTRL('u'):
@@ 981,6 800,8 @@ state_input_linef(struct channel *c)
if ((len = input_write(&(c->input), buf, sizeof(buf), 0)) == 0)
return 0;
+ input_hist_push(&(c->input));
+
switch (buf[0]) {
case ':':
if (len > 1 && buf[1] == ':')
@@ 998,8 819,6 @@ state_input_linef(struct channel *c)
irc_send_privmsg(current_channel()->server, current_channel(), buf);
}
- input_hist_push(&(c->input));
-
return 1;
}
@@ 1010,7 829,7 @@ io_cb_read_inp(char *buf, size_t len)
if (len == 0)
fatal("zero length message");
- else if (action_message)
+ else if (action_handler)
redraw_input = state_input_action(buf, len);
else if (iscntrl(*buf))
redraw_input = state_input_ctrlch(buf, len);
@@ 1061,25 880,97 @@ io_cb_read_soc(char *buf, size_t len, const void *cb_obj)
}
void
-io_cb_log(const void *cb_obj, enum io_log_level lvl, const char *fmt, ...)
+io_cb_cxed(const void *cb_obj)
{
struct server *s = (struct server *)cb_obj;
+ int ret;
+ server_reset(s);
+ server_nicks_next(s);
+
+ s->connected = 1;
+
+ if ((ret = io_sendf(s->connection, "CAP LS " IRCV3_CAP_VERSION)))
+ newlinef(s->channel, 0, "-!!-", "sendf fail: %s", io_err(ret));
+
+ if (s->pass && (ret = io_sendf(s->connection, "PASS %s", s->pass)))
+ newlinef(s->channel, 0, "-!!-", "sendf fail: %s", io_err(ret));
+
+ if ((ret = io_sendf(s->connection, "NICK %s", s->nick)))
+ newlinef(s->channel, 0, "-!!-", "sendf fail: %s", io_err(ret));
+
+ if ((ret = io_sendf(s->connection, "USER %s 8 * :%s", s->username, s->realname)))
+ newlinef(s->channel, 0, "-!!-", "sendf fail: %s", io_err(ret));
+
+ draw(DRAW_STATUS);
+ draw(DRAW_FLUSH);
+}
+
+void
+io_cb_dxed(const void *cb_obj)
+{
+ struct server *s = (struct server *)cb_obj;
+ struct channel *c = s->channel;
+
+ s->connected = 0;
+
+ do {
+ newline(c, 0, "-!!-", " -- disconnected --");
+ channel_reset(c);
+ c = c->next;
+ } while (c != s->channel);
+
+ draw(DRAW_FLUSH);
+}
+
+void
+io_cb_ping(const void *cb_obj, unsigned ping)
+{
+ int ret;
+ struct server *s = (struct server *)cb_obj;
+
+ s->ping = ping;
+
+ if (ping != IO_PING_MIN)
+ draw(DRAW_STATUS);
+ else if ((ret = io_sendf(s->connection, "PING :%s", s->host)))
+ newlinef(s->channel, 0, "-!!-", "sendf fail: %s", io_err(ret));
+
+ draw(DRAW_FLUSH);
+}
+
+void
+io_cb_sigwinch(unsigned cols, unsigned rows)
+{
+ state_tty_cols = cols;
+ state_tty_rows = rows;
+
+ draw(DRAW_ALL);
+ draw(DRAW_FLUSH);
+}
+
+void
+io_cb_info(const void *cb_obj, const char *fmt, ...)
+{
va_list ap;
va_start(ap, fmt);
- switch (lvl) {
- case IO_LOG_ERROR:
- _newline(s->channel, 0, "-!!-", fmt, ap);
- break;
- case IO_LOG_WARN:
- case IO_LOG_INFO:
- case IO_LOG_DEBUG:
- _newline(s->channel, 0, "--", fmt, ap);
- break;
- default:
- fatal("invalid log level");
- }
+ _newline(((struct server *)cb_obj)->channel, 0, "--", fmt, ap);
+
+ va_end(ap);
+
+ draw(DRAW_FLUSH);
+}
+
+void
+io_cb_error(const void *cb_obj, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+
+ _newline(((struct server *)cb_obj)->channel, 0, "-!!-", fmt, ap);
va_end(ap);
+
+ draw(DRAW_FLUSH);
}
M src/state.h => src/state.h +7 -4
@@ 1,5 1,5 @@
-#ifndef STATE_H
-#define STATE_H
+#ifndef RIRC_STATE_H
+#define RIRC_STATE_H
#include "src/components/buffer.h"
#include "src/components/channel.h"
@@ 31,6 31,10 @@ struct server_list* state_server_list(void);
void state_init(void);
void state_term(void);
+/* Get tty dimensions */
+unsigned state_cols(void);
+unsigned state_rows(void);
+
// TODO: most of this stuff can be static
//TODO: move to channel.c, function of server's channel list
/* Useful state retrieval abstractions */
@@ 42,7 46,6 @@ struct channel* channel_get_prev(struct channel*);
/* FIXME: */
void buffer_scrollback_back(struct channel*);
void buffer_scrollback_forw(struct channel*);
-void channel_clear(struct channel*);
void channel_close(struct channel*);
void channel_move_prev(void);
@@ 52,6 55,6 @@ void channel_set_current(struct channel*);
void newlinef(struct channel*, enum buffer_line_t, const char*, const char*, ...);
void newline(struct channel*, enum buffer_line_t, const char*, const char*);
-extern char *action_message;
+const char *action_message(void);
#endif
M src/utils/list.h => src/utils/list.h +2 -2
@@ 1,5 1,5 @@
-#ifndef LIST_H
-#define LIST_H
+#ifndef RIRC_UTILS_LIST_H
+#define RIRC_UTILS_LIST_H
/* TODO: abstract the list implementations in server/channel */
M src/utils/tree.h => src/utils/tree.h +9 -21
@@ 1,5 1,5 @@
-#ifndef TREE_H
-#define TREE_H
+#ifndef RIRC_UTILS_TREE_H
+#define RIRC_UTILS_TREE_H
#include <stddef.h>
@@ 10,11 10,10 @@
#define TREE_RIGHT(elm, field) (elm)->field.tree_right
#define TREE_ROOT(head) (head)->tree_root
-#define AVL_ADD(name, x, y, z) name##_AVL_ADD(x, y, z)
-#define AVL_DEL(name, x, y, z) name##_AVL_DEL(x, y, z)
-#define AVL_GET(name, x, y, z) name##_AVL_GET(x, y, z)
-#define AVL_NGET(name, x, y, z, n) name##_AVL_NGET(x, y, z, n)
-#define AVL_FOREACH(name, x, y) name##_AVL_FOREACH(x, y)
+#define AVL_ADD(name, x, y, z) name##_AVL_ADD(x, y, z)
+#define AVL_DEL(name, x, y, z) name##_AVL_DEL(x, y, z)
+#define AVL_GET(name, x, y, z, n) name##_AVL_GET(x, y, z, n)
+#define AVL_FOREACH(name, x, y) name##_AVL_FOREACH(x, y)
#define TREE_HEAD(type) \
struct type *tree_root
@@ 114,24 113,13 @@ name##_AVL_FOREACH(struct name *head, void (*f)(struct type*))
} \
\
static struct type* \
-name##_AVL_GET(struct name *head, struct type *elm, void *arg) \
+name##_AVL_GET(struct name *head, struct type *elm, void *arg, size_t n) \
{ \
int comp; \
struct type *tmp = TREE_ROOT(head); \
\
- while (tmp && (comp = cmp(elm, tmp, arg))) \
- tmp = (comp > 0) ? TREE_RIGHT(tmp, field) : TREE_LEFT(tmp, field); \
- \
- return tmp; \
-} \
- \
-static struct type* \
-name##_AVL_NGET(struct name *head, struct type *elm, void *arg, size_t n) \
-{ \
- int comp; \
- struct type *tmp = TREE_ROOT(head); \
- \
- while (tmp && (comp = ncmp(elm, tmp, arg, n))) \
+ /* TODO: this can all be one func, with a1, a2, a3 as arguments */ \
+ while (tmp && (comp = (n ? ncmp(elm, tmp, arg, n) : cmp(elm, tmp, arg)))) \
tmp = (comp > 0) ? TREE_RIGHT(tmp, field) : TREE_LEFT(tmp, field); \
\
return tmp; \
M src/utils/utils.c => src/utils/utils.c +49 -50
@@ 1,3 1,5 @@
+#include "src/utils/utils.h"
+
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
@@ 5,45 7,11 @@
#include <string.h>
#include <strings.h>
-#include "src/utils/utils.h"
-
+static inline int irc_ischanchar(char, int);
+static inline int irc_isnickchar(char, int);
static inline int irc_toupper(enum casemapping_t, int);
int
-irc_isnickchar(char c, int first)
-{
- /* RFC 2812, section 2.3.1
- *
- * nickname = ( letter / special ) *8( letter / digit / special / "-" )
- * letter = %x41-5A / %x61-7A ; A-Z / a-z
- * digit = %x30-39 ; 0-9
- * special = %x5B-60 / %x7B-7D ; "[", "]", "\", "`", "_", "^", "{", "|", "}"
- */
-
- return ((c >= 0x41 && c <= 0x7D) || (!first && ((c >= 0x30 && c <= 0x39) || c == '-')));
-}
-
-int
-irc_ischanchar(char c, int first)
-{
- /* RFC 2812, section 2.3.1
- *
- * channel = ( "#" / "+" / ( "!" channelid ) / "&" ) chanstring
- * [ ":" chanstring ]
- * chanstring = %x01-07 / %x08-09 / %x0B-0C / %x0E-1F / %x21-2B
- * chanstring =/ %x2D-39 / %x3B-FF
- * ; any octet except NUL, BELL, CR, LF, " ", "," and ":"
- * channelid = 5( %x41-5A / digit ) ; 5( A-Z / 0-9 )
- */
-
- /* TODO: CHANTYPES */
- (void)c;
- (void)first;
-
- return 1;
-}
-
-int
irc_isnick(const char *str)
{
if (!irc_isnickchar(*str++, 1))
@@ 72,21 40,18 @@ irc_ischan(const char *str)
}
int
-irc_pinged(enum casemapping_t casemapping, const char *mesg, const char *nick)
+irc_pinged(enum casemapping_t cm, const char *mesg, const char *nick)
{
size_t len = strlen(nick);
while (*mesg) {
- /* skip any prefixing characters that wouldn't match a valid nick */
- while (!(*mesg >= 0x41 && *mesg <= 0x7D))
+ while (*mesg && *mesg != *nick && !irc_isnickchar(*mesg, 1))
mesg++;
- /* nick prefixes the word, following character is space or symbol */
- if (!irc_strncmp(casemapping, mesg, nick, len) && !irc_isnickchar(*(mesg + len), 0))
+ if (!irc_strncmp(cm, mesg, nick, len) && !irc_isnickchar(*(mesg + len), 0))
return 1;
- /* skip to end of word */
while (*mesg && *mesg != ' ')
mesg++;
}
@@ 95,7 60,7 @@ irc_pinged(enum casemapping_t casemapping, const char *mesg, const char *nick)
}
int
-irc_strcmp(enum casemapping_t casemapping, const char *s1, const char *s2)
+irc_strcmp(enum casemapping_t cm, const char *s1, const char *s2)
{
/* Case insensitive comparison of strings s1, s2 in accordance
* with RFC 2812, section 2.2 */
@@ 104,8 69,8 @@ irc_strcmp(enum casemapping_t casemapping, const char *s1, const char *s2)
for (;;) {
- c1 = irc_toupper(casemapping, *s1++);
- c2 = irc_toupper(casemapping, *s2++);
+ c1 = irc_toupper(cm, *s1++);
+ c2 = irc_toupper(cm, *s2++);
if ((c1 -= c2))
return -c1;
@@ 118,7 83,7 @@ irc_strcmp(enum casemapping_t casemapping, const char *s1, const char *s2)
}
int
-irc_strncmp(enum casemapping_t casemapping, const char *s1, const char *s2, size_t n)
+irc_strncmp(enum casemapping_t cm, const char *s1, const char *s2, size_t n)
{
/* Case insensitive comparison of strings s1, s2 in accordance
* with RFC 2812, section 2.2, up to n characters */
@@ 127,8 92,8 @@ irc_strncmp(enum casemapping_t casemapping, const char *s1, const char *s2, size
while (n > 0) {
- c1 = irc_toupper(casemapping, *s1++);
- c2 = irc_toupper(casemapping, *s2++);
+ c1 = irc_toupper(cm, *s1++);
+ c2 = irc_toupper(cm, *s2++);
if ((c1 -= c2))
return -c1;
@@ 440,7 405,41 @@ memdup(const void *mem, size_t len)
}
static inline int
-irc_toupper(enum casemapping_t casemapping, int c)
+irc_ischanchar(char c, int first)
+{
+ /* RFC 2812, section 2.3.1
+ *
+ * channel = ( "#" / "+" / ( "!" channelid ) / "&" ) chanstring
+ * [ ":" chanstring ]
+ * chanstring = %x01-07 / %x08-09 / %x0B-0C / %x0E-1F / %x21-2B
+ * chanstring =/ %x2D-39 / %x3B-FF
+ * ; any octet except NUL, BELL, CR, LF, " ", "," and ":"
+ * channelid = 5( %x41-5A / digit ) ; 5( A-Z / 0-9 )
+ */
+
+ /* TODO: CHANTYPES */
+ (void)c;
+ (void)first;
+
+ return 1;
+}
+
+static inline int
+irc_isnickchar(char c, int first)
+{
+ /* RFC 2812, section 2.3.1
+ *
+ * nickname = ( letter / special ) *8( letter / digit / special / "-" )
+ * letter = %x41-5A / %x61-7A ; A-Z / a-z
+ * digit = %x30-39 ; 0-9
+ * special = %x5B-60 / %x7B-7D ; "[", "]", "\", "`", "_", "^", "{", "|", "}"
+ */
+
+ return ((c >= 0x41 && c <= 0x7D) || (!first && ((c >= 0x30 && c <= 0x39) || c == '-')));
+}
+
+static inline int
+irc_toupper(enum casemapping_t cm, int c)
{
/* RFC 2812, section 2.2
*
@@ 449,7 448,7 @@ irc_toupper(enum casemapping_t casemapping, int c)
* respectively. This is a critical issue when determining the
* equivalence of two nicknames or channel names. */
- switch (casemapping) {
+ switch (cm) {
case CASEMAPPING_RFC1459:
if (c == '^') return '~';
/* FALLTHROUGH */
M src/utils/utils.h => src/utils/utils.h +2 -7
@@ 1,5 1,5 @@
-#ifndef UTILS_H
-#define UTILS_H
+#ifndef RIRC_UTILS_UTILS_H
+#define RIRC_UTILS_UTILS_H
#include <stdio.h>
#include <stdlib.h>
@@ 7,9 7,6 @@
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#define MIN(A, B) ((A) > (B) ? (B) : (A))
-#define ELEMS(X) (sizeof((X)) / sizeof((X)[0]))
-#define ARR_ELEM(A, E) ((E) >= 0 && (size_t)(E) < ELEMS((A)))
-
#define SEC_IN_MS(X) ((X) * 1000)
#define SEC_IN_US(X) ((X) * 1000 * 1000)
@@ 66,9 63,7 @@ struct irc_message
};
int irc_ischan(const char*);
-int irc_ischanchar(char, int);
int irc_isnick(const char*);
-int irc_isnickchar(char, int);
int irc_pinged(enum casemapping_t, const char*, const char*);
int irc_strcmp(enum casemapping_t, const char*, const char*);
int irc_strncmp(enum casemapping_t, const char*, const char*, size_t);
M test/components/channel.c => test/components/channel.c +3 -3
@@ 19,9 19,9 @@ test_channel_list(void)
memset(&clist, 0, sizeof(clist));
- c1 = channel("aaa", CHANNEL_T_OTHER);
- c2 = channel("bbb", CHANNEL_T_OTHER);
- c3 = channel("ccc", CHANNEL_T_OTHER);
+ c1 = channel("aaa", CHANNEL_T_RIRC);
+ c2 = channel("bbb", CHANNEL_T_RIRC);
+ c3 = channel("ccc", CHANNEL_T_RIRC);
channel_list_add(&clist, c1);
channel_list_add(&clist, c2);
M test/components/mode.c => test/components/mode.c +4 -1
@@ 286,12 286,15 @@ test_prfxmode_prefix(void)
mode_cfg_chanmodes(&cfg, "abc");
/* Test setting invalid prfxmode prefix */
+ assert_eq(mode_prfxmode_prefix(&m, &cfg, 0), MODE_ERR_INVALID_PREFIX);
+ assert_eq(mode_prfxmode_prefix(&m, &cfg, '0'), MODE_ERR_INVALID_PREFIX);
assert_eq(mode_prfxmode_prefix(&m, &cfg, '4'), MODE_ERR_INVALID_PREFIX);
/* Test setting valid prfxmode prefix */
assert_eq(mode_prfxmode_prefix(&m, &cfg, '2'), MODE_ERR_NONE);
+ assert_eq(mode_prfxmode_prefix(&m, &cfg, '3'), MODE_ERR_NONE);
- assert_strcmp(mode_str(&m, &str), "b");
+ assert_strcmp(mode_str(&m, &str), "bc");
assert_eq(m.prefix, '2');
}
M test/components/user.c => test/components/user.c +23 -7
@@ 102,18 102,34 @@ test_user_list_casemapping(void)
memset(&ulist, 0, sizeof(ulist));
assert_eq(user_list_add(&ulist, CASEMAPPING_RFC1459, "aaa", MODE_EMPTY), USER_ERR_NONE);
- assert_eq(user_list_add(&ulist, CASEMAPPING_RFC1459, "aAa", MODE_EMPTY), USER_ERR_DUPLICATE);
+ assert_eq(user_list_add(&ulist, CASEMAPPING_RFC1459, "AaA", MODE_EMPTY), USER_ERR_DUPLICATE);
+ assert_eq(user_list_add(&ulist, CASEMAPPING_RFC1459, "{}^", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&ulist, CASEMAPPING_RFC1459, "[}~", MODE_EMPTY), USER_ERR_DUPLICATE);
+ assert_eq(user_list_add(&ulist, CASEMAPPING_RFC1459, "zzz", MODE_EMPTY), USER_ERR_NONE);
- if ((u = user_list_get(&ulist, CASEMAPPING_RFC1459, "a", 1)) == NULL)
+ assert_eq(ulist.count, 3);
+
+ if ((u = user_list_get(&ulist, CASEMAPPING_RFC1459, "AAA", 0)) == NULL)
test_abort("Failed to retrieve u");
- assert_ptr_eq(user_list_get(&ulist, CASEMAPPING_RFC1459, "Aaa", 0), u);
- assert_ptr_eq(user_list_get(&ulist, CASEMAPPING_RFC1459, "A", 1), u);
+ assert_ptr_eq(user_list_get(&ulist, CASEMAPPING_RFC1459, "aAa", 3), u);
+ assert_ptr_eq(user_list_get(&ulist, CASEMAPPING_RFC1459, "A", 1), u);
+
+ assert_eq(user_list_rpl(&ulist, CASEMAPPING_RFC1459, "AaA", "bbb"), USER_ERR_NONE);
+ assert_eq(user_list_del(&ulist, CASEMAPPING_RFC1459, "aaa"), USER_ERR_NOT_FOUND);
+ assert_eq(user_list_del(&ulist, CASEMAPPING_RFC1459, "BBB"), USER_ERR_NONE);
+
+ assert_eq(ulist.count, 2);
- assert_eq(user_list_rpl(&ulist, CASEMAPPING_RFC1459, "aaa", "aAa"), USER_ERR_DUPLICATE);
- assert_eq(user_list_rpl(&ulist, CASEMAPPING_RFC1459, "aAa", "zzz"), USER_ERR_NONE);
+ assert_eq(user_list_rpl(&ulist, CASEMAPPING_RFC1459, "{}^", "[}~"), USER_ERR_NONE);
+ assert_eq(user_list_rpl(&ulist, CASEMAPPING_RFC1459, "zzz", "ZzZ"), USER_ERR_NONE);
- assert_eq(user_list_del(&ulist, CASEMAPPING_RFC1459, "ZZZ"), USER_ERR_NONE);
+ assert_eq(ulist.count, 2);
+
+ assert_ptr_not_null(user_list_get(&ulist, CASEMAPPING_RFC1459, "zZz", 0));
+ assert_ptr_not_null(user_list_get(&ulist, CASEMAPPING_RFC1459, "{]^", 0));
+
+ user_list_free(&ulist);
}
static void
M test/handlers/irc_ctcp.c => test/handlers/irc_ctcp.c +36 -28
@@ 1,5 1,7 @@
#include "test/test.h"
+#include <ctype.h>
+
#include "src/components/buffer.c"
#include "src/components/channel.c"
#include "src/components/input.c"
@@ 60,26 62,6 @@ static struct irc_message m;
static struct server *s;
static void
-test_case_insensitive(void)
-{
- /* Requests */
- CHECK_REQUEST(":nick!user@host PRIVMSG me :\001clientinfo", 0, 1, 1,
- "CTCP CLIENTINFO from nick",
- "NOTICE nick :\001CLIENTINFO ACTION CLIENTINFO PING SOURCE TIME VERSION\001");
-
- CHECK_REQUEST(":nick!user@host PRIVMSG me :\001clientinfo\001", 0, 1, 1,
- "CTCP CLIENTINFO from nick",
- "NOTICE nick :\001CLIENTINFO ACTION CLIENTINFO PING SOURCE TIME VERSION\001");
-
- /* Response */
- CHECK_RESPONSE(":nick!user@host NOTICE me :\001clientinfo FOO BAR BAZ", 0,
- "CTCP CLIENTINFO response from nick: FOO BAR BAZ");
-
- CHECK_RESPONSE(":nick!user@host NOTICE me :\001clientinfo 123 456 789\001", 0,
- "CTCP CLIENTINFO response from nick: 123 456 789");
-}
-
-static void
test_recv_ctcp_request(void)
{
/* test malformed */
@@ 123,6 105,15 @@ test_recv_ctcp_request(void)
CHECK_REQUEST(":nick!user@host PRIVMSG me :\001TEST3 arg1 arg2\001", 1, 1, 0,
"Received unsupported CTCP request 'TEST3' from nick", "");
+
+ /* test case insensitive */
+ CHECK_REQUEST(":nick!user@host PRIVMSG me :\001clientinfo", 0, 1, 1,
+ "CTCP CLIENTINFO from nick",
+ "NOTICE nick :\001CLIENTINFO " CTCP_CLIENTINFO "\001");
+
+ CHECK_REQUEST(":nick!user@host PRIVMSG me :\001clientinfo\001", 0, 1, 1,
+ "CTCP CLIENTINFO from nick",
+ "NOTICE nick :\001CLIENTINFO " CTCP_CLIENTINFO "\001");
}
static void
@@ 169,6 160,13 @@ test_recv_ctcp_response(void)
CHECK_RESPONSE(":nick!user@host NOTICE me :\001TEST3 arg1 arg2\001", 1,
"Received unsupported CTCP response 'TEST3' from nick");
+
+ /* test case insensitive */
+ CHECK_RESPONSE(":nick!user@host NOTICE me :\001clientinfo FOO BAR BAZ", 0,
+ "CTCP CLIENTINFO response from nick: FOO BAR BAZ");
+
+ CHECK_RESPONSE(":nick!user@host NOTICE me :\001clientinfo 123 456 789\001", 0,
+ "CTCP CLIENTINFO response from nick: 123 456 789");
}
static void
@@ 190,7 188,7 @@ test_recv_ctcp_request_action(void)
assert_eq(ctcp_request(s, (F), (T), (M)), (R)); \
assert_eq(mock_line_n, 1); \
assert_eq(mock_send_n, 0); \
- assert_strcmp(mock_chan, (C)); \
+ assert_strcmp(mock_chan[0], (C)); \
assert_strcmp(mock_line[0], (L)); \
} while (0)
@@ 222,15 220,26 @@ test_recv_ctcp_request_clientinfo(void)
{
CHECK_REQUEST(":nick!user@host PRIVMSG me :\001CLIENTINFO", 0, 1, 1,
"CTCP CLIENTINFO from nick",
- "NOTICE nick :\001CLIENTINFO ACTION CLIENTINFO PING SOURCE TIME VERSION\001");
+ "NOTICE nick :\001CLIENTINFO " CTCP_CLIENTINFO "\001");
CHECK_REQUEST(":nick!user@host PRIVMSG me :\001CLIENTINFO\001", 0, 1, 1,
"CTCP CLIENTINFO from nick",
- "NOTICE nick :\001CLIENTINFO ACTION CLIENTINFO PING SOURCE TIME VERSION\001");
+ "NOTICE nick :\001CLIENTINFO " CTCP_CLIENTINFO "\001");
CHECK_REQUEST(":nick!user@host PRIVMSG me :\001CLIENTINFO unused args\001", 0, 1, 1,
"CTCP CLIENTINFO from nick (unused args)",
- "NOTICE nick :\001CLIENTINFO ACTION CLIENTINFO PING SOURCE TIME VERSION\001");
+ "NOTICE nick :\001CLIENTINFO " CTCP_CLIENTINFO "\001");
+
+ char *p, clientinfo[] = CTCP_CLIENTINFO;
+
+ for (p = clientinfo; *p; p++)
+ *p = tolower(*p);
+
+ #define X(cmd) assert_ptr_not_null(strstr(clientinfo, #cmd));
+ CTCP_EXTENDED_FORMATTING
+ CTCP_EXTENDED_QUERY
+ CTCP_METADATA_QUERY
+ #undef X
}
static void
@@ 270,15 279,15 @@ test_recv_ctcp_request_source(void)
{
CHECK_REQUEST(":nick!user@host PRIVMSG me :\001SOURCE", 0, 1, 1,
"CTCP SOURCE from nick",
- "NOTICE nick :\001SOURCE rcr.io/rirc\001");
+ "NOTICE nick :\001SOURCE https://rcr.io/rirc\001");
CHECK_REQUEST(":nick!user@host PRIVMSG me :\001SOURCE\001", 0, 1, 1,
"CTCP SOURCE from nick",
- "NOTICE nick :\001SOURCE rcr.io/rirc\001");
+ "NOTICE nick :\001SOURCE https://rcr.io/rirc\001");
CHECK_REQUEST(":nick!user@host PRIVMSG me :\001SOURCE unused args\001", 0, 1, 1,
"CTCP SOURCE from nick (unused args)",
- "NOTICE nick :\001SOURCE rcr.io/rirc\001");
+ "NOTICE nick :\001SOURCE https://rcr.io/rirc\001");
}
static void
@@ 488,7 497,6 @@ main(void)
server_nick_set(s, "me");
struct testcase tests[] = {
- TESTCASE(test_case_insensitive),
TESTCASE(test_recv_ctcp_request),
TESTCASE(test_recv_ctcp_response),
#define X(cmd) TESTCASE(test_recv_ctcp_request_##cmd),
M test/handlers/irc_recv.c => test/handlers/irc_recv.c +736 -54
@@ 2,6 2,7 @@
#include "src/components/buffer.c"
#include "src/components/channel.c"
+#include "src/components/channel.h"
#include "src/components/input.c"
#include "src/components/ircv3.c"
#include "src/components/mode.c"
@@ 20,7 21,7 @@
char TOKEN(buf, __LINE__)[] = S; \
assert_eq(irc_message_parse(&m, TOKEN(buf, __LINE__)), 0);
-#define CHECK_REQUEST(M, RET, LINE_N, SEND_N, LINE, SEND) \
+#define CHECK_RECV(M, RET, LINE_N, SEND_N) \
do { \
mock_reset_io(); \
mock_reset_state(); \
@@ 28,80 29,69 @@
assert_eq(irc_recv(s, &m), (RET)); \
assert_eq(mock_line_n, (LINE_N)); \
assert_eq(mock_send_n, (SEND_N)); \
- assert_strcmp(mock_line[0], (LINE)); \
- assert_strcmp(mock_send[0], (SEND)); \
} while (0)
static struct irc_message m;
+static struct channel *c1;
+static struct channel *c2;
+static struct channel *c3;
+static struct server *s;
+
static void
-test_353(void)
+test_irc_353(void)
{
/* 353 <nick> <type> <channel> 1*(<modes><nick>) */
- struct channel *c = channel("#chan", CHANNEL_T_CHANNEL);
- struct server *s = server("host", "post", NULL, "user", "real");
struct user *u1;
struct user *u2;
struct user *u3;
struct user *u4;
- channel_list_add(&s->clist, c);
- server_nick_set(s, "me");
+ channel_reset(c1);
+ server_reset(s);
/* test errors */
- channel_reset(c);
- CHECK_REQUEST("353 me", 1, 1, 0,
- "RPL_NAMEREPLY: type is null", "");
-
- channel_reset(c);
- CHECK_REQUEST("353 me =", 1, 1, 0,
- "RPL_NAMEREPLY: channel is null", "");
-
- channel_reset(c);
- CHECK_REQUEST("353 me = #chan", 1, 1, 0,
- "RPL_NAMEREPLY: nicks is null", "");
+ CHECK_RECV("353 me", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "RPL_NAMEREPLY: type is null");
+ assert_strcmp(mock_send[0], "");
- channel_reset(c);
- CHECK_REQUEST("353 me = #x :n1", 1, 1, 0,
- "RPL_NAMEREPLY: channel '#x' not found", "");
+ CHECK_RECV("353 me =", 1, 1, 0);
+ assert_strcmp(mock_line[0], "RPL_NAMEREPLY: channel is null");
- channel_reset(c);
- CHECK_REQUEST("353 me x #chan :n1", 1, 1, 0,
- "RPL_NAMEREPLY: invalid channel flag: 'x'", "");
+ CHECK_RECV("353 me = #c1", 1, 1, 0);
+ assert_strcmp(mock_line[0], "RPL_NAMEREPLY: nicks is null");
- channel_reset(c);
- CHECK_REQUEST("353 me = #chan :!n1", 1, 1, 0,
- "RPL_NAMEREPLY: invalid user prefix: '!'", "");
+ CHECK_RECV("353 me = #x :n1", 1, 1, 0);
+ assert_strcmp(mock_line[0], "RPL_NAMEREPLY: channel '#x' not found");
- channel_reset(c);
- CHECK_REQUEST("353 me = #chan :+@n1", 1, 1, 0,
- "RPL_NAMEREPLY: invalid nick: '@n1'", "");
+ CHECK_RECV("353 me x #c1 :n1", 1, 1, 0);
+ assert_strcmp(mock_line[0], "RPL_NAMEREPLY: invalid channel flag: 'x'");
- channel_reset(c);
- CHECK_REQUEST("353 me = #chan :n1 n2 n1", 1, 1, 0,
- "RPL_NAMEREPLY: duplicate nick: 'n1'", "");
+ CHECK_RECV("353 me = #c1 :+@", 1, 1, 0);
+ assert_strcmp(mock_line[0], "RPL_NAMEREPLY: invalid nick: '+@'");
/* test single nick */
- channel_reset(c);
- CHECK_REQUEST("353 me = #chan n1", 0, 0, 0, "", "");
+ channel_reset(c1);
+ CHECK_RECV("353 me = #c1 n1", 0, 0, 0);
- if (user_list_get(&(c->users), s->casemapping, "n1", 0) == NULL)
+ if (user_list_get(&(c1->users), s->casemapping, "n1", 0) == NULL)
test_fail("Failed to retrieve user n1");
- channel_reset(c);
- CHECK_REQUEST("353 me = #chan :@n1", 0, 0, 0, "", "");
+ channel_reset(c1);
+ CHECK_RECV("353 me = #c1 :@n1", 0, 0, 0);
- if (user_list_get(&(c->users), s->casemapping, "n1", 0) == NULL)
+ if (user_list_get(&(c1->users), s->casemapping, "n1", 0) == NULL)
test_fail("Failed to retrieve user n1");
/* test multiple nicks */
- channel_reset(c);
- CHECK_REQUEST("353 me = #chan :@n1 +n2 n3", 0, 0, 0, "", "");
+ channel_reset(c1);
+ CHECK_RECV("353 me = #c1 :@n1 +n2 n3", 0, 0, 0);
- if (!(u1 = user_list_get(&(c->users), CASEMAPPING_RFC1459, "n1", 0))
- || !(u2 = user_list_get(&(c->users), CASEMAPPING_RFC1459, "n2", 0))
- || !(u3 = user_list_get(&(c->users), CASEMAPPING_RFC1459, "n3", 0)))
+ if (!(u1 = user_list_get(&(c1->users), CASEMAPPING_RFC1459, "n1", 0))
+ || !(u2 = user_list_get(&(c1->users), CASEMAPPING_RFC1459, "n2", 0))
+ || !(u3 = user_list_get(&(c1->users), CASEMAPPING_RFC1459, "n3", 0)))
test_abort("Failed to retrieve users");
assert_eq(u1->prfxmodes.lower, (flag_bit('o')));
@@ 110,13 100,14 @@ test_353(void)
/* test multiple nicks, multiprefix enabled */
s->ircv3_caps.multi_prefix.set = 1;
- channel_reset(c);
- CHECK_REQUEST("353 me = #chan :@n1 +n2 @+n3 +@n4", 0, 0, 0, "", "");
- if (!(u1 = user_list_get(&(c->users), CASEMAPPING_RFC1459, "n1", 0))
- || !(u2 = user_list_get(&(c->users), CASEMAPPING_RFC1459, "n2", 0))
- || !(u3 = user_list_get(&(c->users), CASEMAPPING_RFC1459, "n3", 0))
- || !(u4 = user_list_get(&(c->users), CASEMAPPING_RFC1459, "n4", 0)))
+ channel_reset(c1);
+ CHECK_RECV("353 me = #c1 :@n1 +n2 @+n3 +@n4", 0, 0, 0);
+
+ if (!(u1 = user_list_get(&(c1->users), CASEMAPPING_RFC1459, "n1", 0))
+ || !(u2 = user_list_get(&(c1->users), CASEMAPPING_RFC1459, "n2", 0))
+ || !(u3 = user_list_get(&(c1->users), CASEMAPPING_RFC1459, "n3", 0))
+ || !(u4 = user_list_get(&(c1->users), CASEMAPPING_RFC1459, "n4", 0)))
test_abort("Failed to retrieve users");
assert_eq(u1->prfxmodes.prefix, '@');
@@ 127,16 118,707 @@ test_353(void)
assert_eq(u2->prfxmodes.lower, (flag_bit('v')));
assert_eq(u3->prfxmodes.lower, (flag_bit('o') | flag_bit('v')));
assert_eq(u4->prfxmodes.lower, (flag_bit('o') | flag_bit('v')));
+}
- server_free(s);
+static void
+test_recv_error(void)
+{
+ /* ERROR :<message> */
+
+ server_reset(s);
+
+ CHECK_RECV("ERROR", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "ERROR: message is null");
+
+ CHECK_RECV("ERROR error", 0, 1, 0);
+ assert_strcmp(mock_line[0], "error");
+
+ s->quitting = 0;
+ CHECK_RECV("ERROR :error message", 0, 1, 0);
+ assert_strcmp(mock_line[0], "error message");
+
+ s->quitting = 1;
+ CHECK_RECV("ERROR :error message", 0, 1, 0);
+ assert_strcmp(mock_line[0], "error message");
+}
+
+static void
+test_recv_invite(void)
+{
+ /* :nick!user@host INVITE <nick> <channel> */
+
+ channel_reset(c1);
+ server_reset(s);
+
+ CHECK_RECV("INVITE nick channel", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "INVITE: sender's nick is null");
+
+ CHECK_RECV(":nick1!user@host INVITE", 1, 1, 0);
+ assert_strcmp(mock_line[0], "INVITE: nick is null");
+
+ CHECK_RECV(":nick1!user@host INVITE nick", 1, 1, 0);
+ assert_strcmp(mock_line[0], "INVITE: channel is null");
+
+ CHECK_RECV(":nick1!user@host INVITE nick #notfound", 1, 1, 0);
+ assert_strcmp(mock_line[0], "INVITE: channel '#notfound' not found");
+
+ CHECK_RECV(":nick1!user@host INVITE me #c1", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "nick1 invited you to #c1");
+
+ CHECK_RECV(":nick1!user@host INVITE invitee #c1", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick1 invited invitee to #c1");
+}
+
+static void
+test_recv_join(void)
+{
+ /* :nick!user@host JOIN <channel>
+ * :nick!user@host JOIN <channel> <account> :<realname> */
+
+ channel_reset(c1);
+ channel_reset(c2);
+ server_reset(s);
+
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c2->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
+
+ join_threshold = 0;
+
+ CHECK_RECV("JOIN #c1", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "JOIN: sender's nick is null");
+
+ CHECK_RECV(":nick!user@host JOIN", 1, 1, 0);
+ assert_strcmp(mock_line[0], "JOIN: channel is null");
+
+ CHECK_RECV(":nick!user@host JOIN #notfound", 1, 1, 0);
+ assert_strcmp(mock_line[0], "JOIN: channel '#notfound' not found");
+
+ CHECK_RECV(":nick1!user@host JOIN #c1", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "JOIN: user 'nick1' already on channel '#c1'");
+
+ CHECK_RECV(":nick2!user@host JOIN #c1", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick2!user@host has joined");
+
+ s->ircv3_caps.extended_join.set = 0;
+
+ CHECK_RECV(":nick3!user@host JOIN #c1 account :real name", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick3!user@host has joined");
+
+ s->ircv3_caps.extended_join.set = 1;
+
+ CHECK_RECV(":nick4!user@host JOIN #c1", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "JOIN: account is null");
+
+ CHECK_RECV(":nick5!user@host JOIN #c1 account", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "JOIN: realname is null");
+
+ CHECK_RECV(":nick6!user@host JOIN #c1 account :real name", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick6!user@host has joined [account - real name]");
+
+ join_threshold = 2;
+
+ CHECK_RECV(":nick2!user@host JOIN #c2", 0, 0, 0);
+
+ CHECK_RECV(":me!user@host JOIN #new", 0, 1, 1);
+ assert_strcmp(mock_chan[0], "#new");
+ assert_strcmp(mock_line[0], "Joined #new");
+ assert_strcmp(mock_send[0], "MODE #new");
+ assert_ptr_not_null(channel_list_get(&s->clist, "#new", s->casemapping));
+}
+
+static void
+test_recv_kick(void)
+{
+ /* :nick!user@host KICK <channel> <user> [:message] */
+
+ channel_reset(c1);
+ channel_reset(c2);
+ channel_reset(c3);
+ server_reset(s);
+
+ c1->parted = 0;
+ c2->parted = 0;
+ c3->parted = 0;
+
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick2", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick3", MODE_EMPTY), USER_ERR_NONE);
+
+ CHECK_RECV("KICK #c1", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "KICK: sender's nick is null");
+
+ CHECK_RECV(":nick!user@host KICK", 1, 1, 0);
+ assert_strcmp(mock_line[0], "KICK: channel is null");
+
+ CHECK_RECV(":nick!user@host KICK #c1", 1, 1, 0);
+ assert_strcmp(mock_line[0], "KICK: user is null");
+
+ CHECK_RECV(":nick!user@host KICK #notfound nick1", 1, 1, 0);
+ assert_strcmp(mock_line[0], "KICK: channel '#notfound' not found");
+
+ /* no message */
+ CHECK_RECV(":nick!user@host KICK #c1 nick1", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick has kicked nick1");
+ assert_ptr_null(user_list_get(&(c1->users), s->casemapping, "nick1", 0));
+
+ /* empty message */
+ CHECK_RECV(":nick!user@host KICK #c1 nick2 :", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick has kicked nick2");
+ assert_ptr_null(user_list_get(&(c1->users), s->casemapping, "nick2", 0));
+
+ CHECK_RECV(":nick!user@host KICK #c1 nick3 :kick message", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick has kicked nick3 (kick message)");
+ assert_ptr_null(user_list_get(&(c1->users), s->casemapping, "nick3", 0));
+
+ /* no message */
+ CHECK_RECV(":nick!user@host KICK #c1 me", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "Kicked by nick");
+ assert_eq(c1->parted, 1);
+
+ /* empty message */
+ CHECK_RECV(":nick!user@host KICK #c2 me :", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c2");
+ assert_strcmp(mock_line[0], "Kicked by nick");
+ assert_eq(c2->parted, 1);
+
+ CHECK_RECV(":nick!user@host KICK #c3 me :kick message", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c3");
+ assert_strcmp(mock_line[0], "Kicked by nick (kick message)");
+ assert_eq(c3->parted, 1);
+}
+
+static void
+test_recv_mode(void)
+{
+ /* TODO */
+}
+
+static void
+test_recv_mode_chanmodes(void)
+{
+ /* TODO */
+}
+
+static void
+test_recv_mode_usermodes(void)
+{
+ /* TODO */
+}
+
+static void
+test_recv_nick(void)
+{
+ /* :nick!user@host NICK <nick> */
+
+ channel_reset(c1);
+ channel_reset(c2);
+ channel_reset(c3);
+ server_reset(s);
+
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick2", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c3->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c3->users), CASEMAPPING_RFC1459, "nick2", MODE_EMPTY), USER_ERR_NONE);
+
+ CHECK_RECV("NICK new_nick", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "NICK: old nick is null");
+
+ CHECK_RECV(":nick1!user@host NICK", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "NICK: new nick is null");
+
+ /* user not on channels */
+ CHECK_RECV(":nick3!user@host NICK new_nick", 0, 0, 0);
+
+ CHECK_RECV(":nick1!user@host NICK new_nick", 0, 2, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick1 >> new_nick");
+ assert_strcmp(mock_chan[1], "#c3");
+ assert_strcmp(mock_line[1], "nick1 >> new_nick");
+ assert_ptr_null(user_list_get(&(c1->users), s->casemapping, "nick1", 0));
+ assert_ptr_null(user_list_get(&(c3->users), s->casemapping, "nick1", 0));
+
+ CHECK_RECV(":nick2!user@host NICK new_nick", 0, 2, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "NICK: user 'new_nick' already on channel '#c1'");
+ assert_strcmp(mock_chan[1], "host");
+ assert_strcmp(mock_line[1], "NICK: user 'new_nick' already on channel '#c3'");
+
+ CHECK_RECV(":me!user@host NICK new_me", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "Your nick is now 'new_me'");
+
+ /* user can change own nick case */
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "abc{}|^", MODE_EMPTY), USER_ERR_NONE);
+
+ CHECK_RECV(":abc{}|^!user@host NICK AbC{]|~", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "abc{}|^ >> AbC{]|~");
+
+ server_nick_set(s, "me");
+}
+
+static void
+test_recv_notice(void)
+{
+ /* TODO */
+}
+
+static void
+test_recv_numeric(void)
+{
+ server_reset(s);
+
+ CHECK_RECV(":hostname 0 * arg :trailing arg", 1, 1, 0);
+ assert_strcmp(mock_line[0], "NUMERIC: '0' invalid");
+
+ CHECK_RECV(":hostname 00 * arg :trailing arg", 1, 1, 0);
+ assert_strcmp(mock_line[0], "NUMERIC: '00' invalid");
+
+ CHECK_RECV(":hostname 0a * arg :trailing arg", 1, 1, 0);
+ assert_strcmp(mock_line[0], "NUMERIC: '0a' invalid");
+
+ CHECK_RECV(":hostname 000 * arg :trailing arg", 1, 1, 0);
+ assert_strcmp(mock_line[0], "NUMERIC: '000' invalid");
+
+ CHECK_RECV(":hostname 00a * arg :trailing arg", 1, 1, 0);
+ assert_strcmp(mock_line[0], "NUMERIC: '00a' invalid");
+
+ CHECK_RECV(":hostname 0000 * arg :trailing arg", 1, 1, 0);
+ assert_strcmp(mock_line[0], "NUMERIC: '0000' invalid");
+
+ CHECK_RECV(":hostname 1000 * arg :trailing arg", 1, 1, 0);
+ assert_strcmp(mock_line[0], "NUMERIC: '1000' invalid");
+
+ CHECK_RECV(":hostname 001", 1, 1, 0);
+ assert_strcmp(mock_line[0], "NUMERIC: target is null");
+
+ CHECK_RECV(":hostname 001 test", 1, 1, 0);
+ assert_strcmp(mock_line[0], "NUMERIC: target 'test' is invalid");
+
+ CHECK_RECV(":hostname 666 me arg1 arg2 :trailing arg", 1, 1, 0);
+ assert_strcmp(mock_line[0], "NUMERIC: 666 unhandled: [arg1 arg2 :trailing arg]");
+
+ CHECK_RECV(":hostname 666 me", 1, 1, 0);
+ assert_strcmp(mock_line[0], "NUMERIC: 666 unhandled");
+
+ /* Numeric 375 (RPL_MOTDSTART) is ignored */
+ CHECK_RECV(":hostname 375 me :trailing arg", 0, 0, 0);
+}
+
+static void
+test_recv_part(void)
+{
+ /* :nick!user@host PART <channel> [:message] */
+
+ channel_reset(c1);
+ channel_reset(c2);
+ channel_reset(c3);
+ server_reset(s);
+
+ c1->parted = 0;
+ c2->parted = 0;
+ c3->parted = 0;
+
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick2", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick3", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick4", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick5", MODE_EMPTY), USER_ERR_NONE);
+
+ part_threshold = 0;
+
+ CHECK_RECV("PART #c1 :part message", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "PART: sender's nick is null");
+
+ CHECK_RECV(":nick1!user@host PART", 1, 1, 0);
+ assert_strcmp(mock_line[0], "PART: channel is null");
+
+ CHECK_RECV(":nick1!user@host PART #notfound :quit message", 1, 1, 0);
+ assert_strcmp(mock_line[0], "PART: channel '#notfound' not found");
+
+ CHECK_RECV(":nick6!user@host PART #c1 :part message", 1, 1, 0);
+ assert_strcmp(mock_line[0], "PART: nick 'nick6' not found in '#c1'");
+
+ CHECK_RECV(":nick1!user@host PART #c1 :part message", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick1!user@host has parted (part message)");
+ assert_ptr_null(user_list_get(&(c1->users), s->casemapping, "nick1", 0));
+
+ /* no message */
+ CHECK_RECV(":nick2!user@host PART #c1", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick2!user@host has parted");
+ assert_ptr_null(user_list_get(&(c1->users), s->casemapping, "nick2", 0));
+
+ /* empty message */
+ CHECK_RECV(":nick3!user@host PART #c1 :", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick3!user@host has parted");
+ assert_ptr_null(user_list_get(&(c1->users), s->casemapping, "nick3", 0));
+
+ part_threshold = 1;
+
+ CHECK_RECV(":nick4!user@host PART #c1", 0, 0, 0);
+ assert_ptr_null(user_list_get(&(c1->users), s->casemapping, "nick4", 0));
+
+ /* channel not found, assume closed */
+ CHECK_RECV(":me!user@host PART #notfound", 0, 0, 0);
+
+ /* no message */
+ CHECK_RECV(":me!user@host PART #c1", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "you have parted");
+ assert_eq(c1->parted, 1);
+
+ /* empty message */
+ CHECK_RECV(":me!user@host PART #c2 :", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c2");
+ assert_strcmp(mock_line[0], "you have parted");
+ assert_eq(c2->parted, 1);
+
+ CHECK_RECV(":me!user@host PART #c3 message", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c3");
+ assert_strcmp(mock_line[0], "you have parted (message)");
+ assert_eq(c3->parted, 1);
+}
+
+static void
+test_recv_ping(void)
+{
+ /* PING <server> */
+
+ server_reset(s);
+
+ CHECK_RECV("PING", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "PING: server is null");
+
+ CHECK_RECV("PING server", 0, 0, 1);
+ assert_strcmp(mock_send[0], "PONG server");
+}
+
+static void
+test_recv_pong(void)
+{
+ /* PONG <server> [<server2>] */
+
+ server_reset(s);
+
+ CHECK_RECV("PONG", 0, 0, 0);
+ CHECK_RECV("PONG s1", 0, 0, 0);
+ CHECK_RECV("PONG s1 s2", 0, 0, 0);
+}
+
+static void
+test_recv_privmsg(void)
+{
+ /* TODO */
+}
+
+static void
+test_recv_quit(void)
+{
+ /* :nick!user@host QUIT [:message] */
+
+ channel_reset(c1);
+ channel_reset(c2);
+ channel_reset(c3);
+ server_reset(s);
+
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick2", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick3", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick4", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick5", MODE_EMPTY), USER_ERR_NONE);
+
+ assert_eq(user_list_add(&(c3->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c3->users), CASEMAPPING_RFC1459, "nick2", MODE_EMPTY), USER_ERR_NONE);
+
+ quit_threshold = 0;
+
+ CHECK_RECV("QUIT message", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "QUIT: sender's nick is null");
+
+ /* user not on channels */
+ CHECK_RECV(":nick6!user@host QUIT :quit message", 0, 0, 0);
+
+ CHECK_RECV(":nick2!user@host QUIT :quit message", 0, 2, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick2!user@host has quit (quit message)");
+ assert_strcmp(mock_chan[1], "#c3");
+ assert_strcmp(mock_line[1], "nick2!user@host has quit (quit message)");
+ assert_ptr_null(user_list_get(&(c1->users), s->casemapping, "nick2", 0));
+ assert_ptr_null(user_list_get(&(c3->users), s->casemapping, "nick2", 0));
+
+ /* no message */
+ CHECK_RECV(":nick3!user@host QUIT", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick3!user@host has quit");
+ assert_ptr_null(user_list_get(&(c1->users), s->casemapping, "nick3", 0));
+
+ /* empty message */
+ CHECK_RECV(":nick4!user@host QUIT :", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick4!user@host has quit");
+ assert_ptr_null(user_list_get(&(c1->users), s->casemapping, "nick4", 0));
+
+ quit_threshold = 1;
+
+ /* c1 = {nick1, nick5}, c3 = {nick1} */
+ CHECK_RECV(":nick1!user@host QUIT", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c3");
+ assert_strcmp(mock_line[0], "nick1!user@host has quit");
+ assert_ptr_null(user_list_get(&(c1->users), s->casemapping, "nick1", 0));
+ assert_ptr_null(user_list_get(&(c3->users), s->casemapping, "nick1", 0));
+}
+
+static void
+test_recv_topic(void)
+{
+ /* :nick!user@host TOPIC <channel> [:topic] */
+
+ channel_reset(c1);
+ server_reset(s);
+
+ CHECK_RECV("TOPIC #c1 message", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "TOPIC: sender's nick is null");
+
+ CHECK_RECV(":nick1!user@host TOPIC", 1, 1, 0);
+ assert_strcmp(mock_line[0], "TOPIC: channel is null");
+
+ CHECK_RECV(":nick1!user@host TOPIC #c1", 1, 1, 0);
+ assert_strcmp(mock_line[0], "TOPIC: topic is null");
+
+ CHECK_RECV(":nick1!user@host TOPIC #notfound message", 1, 1, 0);
+ assert_strcmp(mock_line[0], "TOPIC: channel '#notfound' not found");
+
+ CHECK_RECV(":nick1!user@host TOPIC #c1 message", 0, 2, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_chan[1], "#c1");
+ assert_strcmp(mock_line[0], "nick1 has set the topic:");
+ assert_strcmp(mock_line[1], "\"message\"");
+
+ CHECK_RECV(":nick1!user@host TOPIC #c1 :topic message", 0, 2, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_chan[1], "#c1");
+ assert_strcmp(mock_line[0], "nick1 has set the topic:");
+ assert_strcmp(mock_line[1], "\"topic message\"");
+
+ CHECK_RECV(":nick1!user@host TOPIC #c1 :", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick1 has unset the topic");
+}
+
+static void
+test_recv_ircv3_cap(void)
+{
+ /* Full IRCv3 CAP coverage in test/handlers/ircv3.c */
+
+ server_reset(s);
+
+ s->registered = 1;
+
+ CHECK_RECV("CAP * LS :cap-1 cap-2 cap-3", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "CAP LS: cap-1 cap-2 cap-3");
+}
+
+static void
+test_recv_ircv3_account(void)
+{
+ /* :nick!user@host ACCOUNT <account> */
+
+ channel_reset(c1);
+ channel_reset(c2);
+ channel_reset(c3);
+ server_reset(s);
+
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick2", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c3->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
+
+ account_threshold = 0;
+
+ CHECK_RECV("ACCOUNT *", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "ACCOUNT: sender's nick is null");
+
+ CHECK_RECV(":nick1!user@host ACCOUNT", 1, 1, 0);
+ assert_strcmp(mock_line[0], "ACCOUNT: account is null");
+
+ /* user not on channels */
+ CHECK_RECV(":nick3!user@host ACCOUNT account", 0, 0, 0);
+
+ /* logging in */
+ CHECK_RECV(":nick1!user@host ACCOUNT account", 0, 2, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick1 has logged in as account");
+ assert_strcmp(mock_chan[1], "#c3");
+ assert_strcmp(mock_line[1], "nick1 has logged in as account");
+
+ /* logging out */
+ CHECK_RECV(":nick1!user@host ACCOUNT *", 0, 2, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick1 has logged out");
+ assert_strcmp(mock_chan[1], "#c3");
+ assert_strcmp(mock_line[1], "nick1 has logged out");
+
+ account_threshold = 2;
+
+ CHECK_RECV(":nick1!user@host ACCOUNT *", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c3");
+}
+
+static void
+test_recv_ircv3_away(void)
+{
+ /* :nick!user@host AWAY [:message] */
+
+ channel_reset(c1);
+ channel_reset(c2);
+ channel_reset(c3);
+ server_reset(s);
+
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick2", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c3->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
+
+ away_threshold = 0;
+
+ CHECK_RECV("AWAY *", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "AWAY: sender's nick is null");
+
+ /* user not on channels */
+ CHECK_RECV(":nick3!user@host AWAY :away message", 0, 0, 0);
+
+ /* away set */
+ CHECK_RECV(":nick1!user@host AWAY :away message", 0, 2, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick1 is now away: away message");
+ assert_strcmp(mock_chan[1], "#c3");
+ assert_strcmp(mock_line[1], "nick1 is now away: away message");
+
+ /* away unset */
+ CHECK_RECV(":nick1!user@host AWAY", 0, 2, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick1 is no longer away");
+ assert_strcmp(mock_chan[1], "#c3");
+ assert_strcmp(mock_line[1], "nick1 is no longer away");
+
+ away_threshold = 2;
+
+ CHECK_RECV(":nick1!user@host AWAY", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c3");
+}
+
+static void
+test_recv_ircv3_chghost(void)
+{
+ /* :nick!user@host CHGHOST new_user new_host */
+
+ channel_reset(c1);
+ channel_reset(c2);
+ channel_reset(c3);
+ server_reset(s);
+
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c1->users), CASEMAPPING_RFC1459, "nick2", MODE_EMPTY), USER_ERR_NONE);
+ assert_eq(user_list_add(&(c3->users), CASEMAPPING_RFC1459, "nick1", MODE_EMPTY), USER_ERR_NONE);
+
+ chghost_threshold = 0;
+
+ CHECK_RECV("CHGHOST new_user new_host", 1, 1, 0);
+ assert_strcmp(mock_chan[0], "host");
+ assert_strcmp(mock_line[0], "CHGHOST: sender's nick is null");
+
+ CHECK_RECV(":nick1!user@host CHGHOST", 1, 1, 0);
+ assert_strcmp(mock_line[0], "CHGHOST: user is null");
+
+ CHECK_RECV(":nick1!user@host CHGHOST new_user", 1, 1, 0);
+ assert_strcmp(mock_line[0], "CHGHOST: host is null");
+
+ /* user not on channels */
+ CHECK_RECV(":nick3!user@host CHGHOST new_user new_host", 0, 0, 0);
+
+ CHECK_RECV(":nick1!user@host CHGHOST new_user new_host", 0, 2, 0);
+ assert_strcmp(mock_chan[0], "#c1");
+ assert_strcmp(mock_line[0], "nick1 has changed user/host: new_user/new_host");
+ assert_strcmp(mock_chan[1], "#c3");
+ assert_strcmp(mock_line[1], "nick1 has changed user/host: new_user/new_host");
+
+ chghost_threshold = 2;
+
+ CHECK_RECV(":nick1!user@host CHGHOST new_user new_host", 0, 1, 0);
+ assert_strcmp(mock_chan[0], "#c3");
+ assert_strcmp(mock_line[0], "nick1 has changed user/host: new_user/new_host");
}
int
main(void)
{
+ c1 = channel("#c1", CHANNEL_T_CHANNEL);
+ c2 = channel("#c2", CHANNEL_T_CHANNEL);
+ c3 = channel("#c3", CHANNEL_T_CHANNEL);
+ s = server("host", "port", NULL, "user", "real");
+
+ if (!s || !c1 || !c2 || !c3)
+ test_abort_main("Failed test setup");
+
+ channel_list_add(&s->clist, c1);
+ channel_list_add(&s->clist, c2);
+ channel_list_add(&s->clist, c3);
+
+ server_nick_set(s, "me");
+
struct testcase tests[] = {
- TESTCASE(test_353)
+ TESTCASE(test_irc_353),
+ TESTCASE(test_recv_error),
+ TESTCASE(test_recv_invite),
+ TESTCASE(test_recv_join),
+ TESTCASE(test_recv_kick),
+ TESTCASE(test_recv_mode),
+ TESTCASE(test_recv_mode_chanmodes),
+ TESTCASE(test_recv_mode_usermodes),
+ TESTCASE(test_recv_nick),
+ TESTCASE(test_recv_notice),
+ TESTCASE(test_recv_numeric),
+ TESTCASE(test_recv_part),
+ TESTCASE(test_recv_ping),
+ TESTCASE(test_recv_pong),
+ TESTCASE(test_recv_privmsg),
+ TESTCASE(test_recv_quit),
+ TESTCASE(test_recv_topic),
+ TESTCASE(test_recv_ircv3_cap),
+ TESTCASE(test_recv_ircv3_account),
+ TESTCASE(test_recv_ircv3_away),
+ TESTCASE(test_recv_ircv3_chghost)
};
- return run_tests(tests);
+ int ret = run_tests(tests);
+
+ server_free(s);
+
+ return ret;
}
M test/handlers/irc_send.c => test/handlers/irc_send.c +51 -21
@@ 110,6 110,18 @@ test_irc_send_privmsg(void)
}
static void
+test_send_away(void)
+{
+ char m1[] = "away";
+ char m2[] = "away ";
+ char m3[] = "away testing away message";
+
+ CHECK_SEND_COMMAND(c_chan, m1, 0, 0, 1, "", "AWAY");
+ CHECK_SEND_COMMAND(c_chan, m2, 0, 0, 1, "", "AWAY");
+ CHECK_SEND_COMMAND(c_chan, m3, 0, 0, 1, "", "AWAY :testing away message");
+}
+
+static void
test_send_notice(void)
{
char m1[] = "notice";
@@ 184,15 196,33 @@ test_send_topic(void)
}
static void
-test_send_ctcp_action(void)
+test_send_topic_unset(void)
{
- char m1[] = "ctcp-action test action";
- char m2[] = "ctcp-action test action";
- char m3[] = "ctcp-action test action";
+ char m1[] = "topic-unset";
+ char m2[] = "topic-unset";
+ char m3[] = "topic-unset test";
+ char m4[] = "topic-unset";
+ char m5[] = "topic-unset ";
- CHECK_SEND_COMMAND(c_chan, m1, 0, 0, 1, "", "PRIVMSG chan :\001ACTION test action\001");
- CHECK_SEND_COMMAND(c_priv, m2, 0, 0, 1, "", "PRIVMSG priv :\001ACTION test action\001");
- CHECK_SEND_COMMAND(c_serv, m3, 1, 1, 0, "This is not a channel", "");
+ CHECK_SEND_COMMAND(c_serv, m1, 1, 1, 0, "This is not a channel", "");
+ CHECK_SEND_COMMAND(c_priv, m2, 1, 1, 0, "This is not a channel", "");
+ CHECK_SEND_COMMAND(c_chan, m3, 1, 1, 0, "Usage: /topic-unset", "");
+ CHECK_SEND_COMMAND(c_chan, m4, 0, 0, 1, "", "TOPIC chan :");
+ CHECK_SEND_COMMAND(c_chan, m5, 0, 0, 1, "", "TOPIC chan :");
+}
+
+static void
+test_send_ctcp_action(void)
+{
+ char m1[] = "ctcp-action";
+ char m2[] = "ctcp-action target";
+ char m3[] = "ctcp-action target ";
+ char m4[] = "ctcp-action target action message";
+
+ CHECK_SEND_COMMAND(c_chan, m1, 1, 1, 0, "Usage: /ctcp-action <nick> <text>", "");
+ CHECK_SEND_COMMAND(c_chan, m2, 1, 1, 0, "Usage: /ctcp-action <nick> <text>", "");
+ CHECK_SEND_COMMAND(c_chan, m3, 1, 1, 0, "Usage: /ctcp-action <nick> <text>", "");
+ CHECK_SEND_COMMAND(c_chan, m4, 0, 0, 1, "", "PRIVMSG target :\001ACTION action message\001");
}
static void
@@ 203,8 233,8 @@ test_send_ctcp_clientinfo(void)
char m3[] = "ctcp-clientinfo";
char m4[] = "ctcp-clientinfo targ";
- CHECK_SEND_COMMAND(c_chan, m1, 1, 1, 0, "Usage: /ctcp-clientinfo <target>", "");
- CHECK_SEND_COMMAND(c_serv, m2, 1, 1, 0, "Usage: /ctcp-clientinfo <target>", "");
+ CHECK_SEND_COMMAND(c_chan, m1, 1, 1, 0, "Usage: /ctcp-clientinfo <nick>", "");
+ CHECK_SEND_COMMAND(c_serv, m2, 1, 1, 0, "Usage: /ctcp-clientinfo <nick>", "");
CHECK_SEND_COMMAND(c_priv, m3, 0, 0, 1, "", "PRIVMSG priv :\001CLIENTINFO\001");
CHECK_SEND_COMMAND(c_priv, m4, 0, 0, 1, "", "PRIVMSG targ :\001CLIENTINFO\001");
}
@@ 217,8 247,8 @@ test_send_ctcp_finger(void)
char m3[] = "ctcp-finger";
char m4[] = "ctcp-finger targ";
- CHECK_SEND_COMMAND(c_chan, m1, 1, 1, 0, "Usage: /ctcp-finger <target>", "");
- CHECK_SEND_COMMAND(c_serv, m2, 1, 1, 0, "Usage: /ctcp-finger <target>", "");
+ CHECK_SEND_COMMAND(c_chan, m1, 1, 1, 0, "Usage: /ctcp-finger <nick>", "");
+ CHECK_SEND_COMMAND(c_serv, m2, 1, 1, 0, "Usage: /ctcp-finger <nick>", "");
CHECK_SEND_COMMAND(c_priv, m3, 0, 0, 1, "", "PRIVMSG priv :\001FINGER\001");
CHECK_SEND_COMMAND(c_priv, m4, 0, 0, 1, "", "PRIVMSG targ :\001FINGER\001");
}
@@ 237,8 267,8 @@ test_send_ctcp_ping(void)
const char *arg2;
const char *arg3;
- CHECK_SEND_COMMAND(c_chan, m1, 1, 1, 0, "Usage: /ctcp-ping <target>", "");
- CHECK_SEND_COMMAND(c_serv, m2, 1, 1, 0, "Usage: /ctcp-ping <target>", "");
+ CHECK_SEND_COMMAND(c_chan, m1, 1, 1, 0, "Usage: /ctcp-ping <nick>", "");
+ CHECK_SEND_COMMAND(c_serv, m2, 1, 1, 0, "Usage: /ctcp-ping <nick>", "");
/* test send to channel */
errno = 0;
@@ 307,8 337,8 @@ test_send_ctcp_source(void)
char m3[] = "ctcp-source";
char m4[] = "ctcp-source targ";
- CHECK_SEND_COMMAND(c_chan, m1, 1, 1, 0, "Usage: /ctcp-source <target>", "");
- CHECK_SEND_COMMAND(c_serv, m2, 1, 1, 0, "Usage: /ctcp-source <target>", "");
+ CHECK_SEND_COMMAND(c_chan, m1, 1, 1, 0, "Usage: /ctcp-source <nick>", "");
+ CHECK_SEND_COMMAND(c_serv, m2, 1, 1, 0, "Usage: /ctcp-source <nick>", "");
CHECK_SEND_COMMAND(c_priv, m3, 0, 0, 1, "", "PRIVMSG priv :\001SOURCE\001");
CHECK_SEND_COMMAND(c_priv, m4, 0, 0, 1, "", "PRIVMSG targ :\001SOURCE\001");
}
@@ 321,8 351,8 @@ test_send_ctcp_time(void)
char m3[] = "ctcp-time";
char m4[] = "ctcp-time targ";
- CHECK_SEND_COMMAND(c_chan, m1, 1, 1, 0, "Usage: /ctcp-time <target>", "");
- CHECK_SEND_COMMAND(c_serv, m2, 1, 1, 0, "Usage: /ctcp-time <target>", "");
+ CHECK_SEND_COMMAND(c_chan, m1, 1, 1, 0, "Usage: /ctcp-time <nick>", "");
+ CHECK_SEND_COMMAND(c_serv, m2, 1, 1, 0, "Usage: /ctcp-time <nick>", "");
CHECK_SEND_COMMAND(c_priv, m3, 0, 0, 1, "", "PRIVMSG priv :\001TIME\001");
CHECK_SEND_COMMAND(c_priv, m4, 0, 0, 1, "", "PRIVMSG targ :\001TIME\001");
}
@@ 335,8 365,8 @@ test_send_ctcp_userinfo(void)
char m3[] = "ctcp-userinfo";
char m4[] = "ctcp-userinfo targ";
- CHECK_SEND_COMMAND(c_chan, m1, 1, 1, 0, "Usage: /ctcp-userinfo <target>", "");
- CHECK_SEND_COMMAND(c_serv, m2, 1, 1, 0, "Usage: /ctcp-userinfo <target>", "");
+ CHECK_SEND_COMMAND(c_chan, m1, 1, 1, 0, "Usage: /ctcp-userinfo <nick>", "");
+ CHECK_SEND_COMMAND(c_serv, m2, 1, 1, 0, "Usage: /ctcp-userinfo <nick>", "");
CHECK_SEND_COMMAND(c_priv, m3, 0, 0, 1, "", "PRIVMSG priv :\001USERINFO\001");
CHECK_SEND_COMMAND(c_priv, m4, 0, 0, 1, "", "PRIVMSG targ :\001USERINFO\001");
}
@@ 349,8 379,8 @@ test_send_ctcp_version(void)
char m3[] = "ctcp-version";
char m4[] = "ctcp-version targ";
- CHECK_SEND_COMMAND(c_chan, m1, 1, 1, 0, "Usage: /ctcp-version <target>", "");
- CHECK_SEND_COMMAND(c_serv, m2, 1, 1, 0, "Usage: /ctcp-version <target>", "");
+ CHECK_SEND_COMMAND(c_chan, m1, 1, 1, 0, "Usage: /ctcp-version <nick>", "");
+ CHECK_SEND_COMMAND(c_serv, m2, 1, 1, 0, "Usage: /ctcp-version <nick>", "");
CHECK_SEND_COMMAND(c_priv, m3, 0, 0, 1, "", "PRIVMSG priv :\001VERSION\001");
CHECK_SEND_COMMAND(c_priv, m4, 0, 0, 1, "", "PRIVMSG targ :\001VERSION\001");
}
M test/io.mock.c => test/io.mock.c +35 -3
@@ 6,6 6,7 @@
static char mock_send[MOCK_SEND_N][MOCK_SEND_LEN];
static unsigned mock_send_i;
static unsigned mock_send_n;
+static int cxed;
void
mock_reset_io(void)
@@ 13,6 14,7 @@ mock_reset_io(void)
mock_send_i = 0;
mock_send_n = 0;
memset(mock_send, 0, MOCK_SEND_LEN * MOCK_SEND_N);
+ cxed = 0;
}
int
@@ 44,9 46,39 @@ connection(const void *o, const char *h, const char *p, uint32_t f)
return NULL;
}
-const char* io_err(int err) { UNUSED(err); return "err"; }
-int io_cx(struct connection *c) { UNUSED(c); return 0; }
-int io_dx(struct connection *c) { UNUSED(c); return 0; }
+int
+io_cx(struct connection *c)
+{
+ UNUSED(c);
+
+ if (cxed)
+ return -1;
+
+ cxed = 1;
+ return 0;
+}
+
+int
+io_dx(struct connection *c)
+{
+ UNUSED(c);
+
+ if (cxed) {
+ cxed = 0;
+ return 0;
+ }
+
+ return -1;
+}
+
+const char*
+io_err(int err)
+{
+ UNUSED(err);
+
+ return (cxed ? "cxed" : "dxed");
+}
+
unsigned io_tty_cols(void) { return 0; }
unsigned io_tty_rows(void) { return 0; }
void connection_free(struct connection *c) { UNUSED(c); }
M test/state.c => test/state.c +264 -9
@@ 18,10 18,254 @@
#define INP_S(S) io_cb_read_inp((S), strlen(S))
#define INP_C(C) io_cb_read_inp((char[]){(C)}, 1)
-#define CURRENT_LINE (buffer_head(¤t_channel()->buffer)->text)
-// TODO: tests for
-// sending certain /commands to private buffer, server buffer
+#define CURRENT_LINE \
+ (buffer_head(¤t_channel()->buffer) ? \
+ buffer_head(¤t_channel()->buffer)->text : NULL)
+
+static void
+test_command(void)
+{
+ state_init();
+
+ INP_S(":unknown command with args");
+ INP_C(0x0A);
+
+ assert_strcmp(action_message(), "Unknown command 'unknown'");
+
+ /* clear error */
+ INP_C(0x0A);
+
+ state_term();
+}
+
+static void
+test_command_clear(void)
+{
+ state_init();
+
+ assert_strcmp(CURRENT_LINE, " - compiled with DEBUG flags");
+
+ INP_S(":clear with args");
+ INP_C(0x0A);
+
+ assert_strcmp(action_message(), "clear: Unknown arg 'with'");
+
+ /* clear error */
+ INP_C(0x0A);
+
+ assert_strcmp(CURRENT_LINE, " - compiled with DEBUG flags");
+
+ INP_S(":clear");
+ INP_C(0x0A);
+
+ assert_ptr_null(CURRENT_LINE);
+
+ state_term();
+}
+
+static void
+test_command_close(void)
+{
+ static struct channel *c1;
+ static struct channel *c2;
+ struct server *s;
+
+ state_init();
+
+ assert_strcmp(CURRENT_LINE, " - compiled with DEBUG flags");
+
+ INP_S(":close");
+ INP_C(0x0A);
+
+ assert_strcmp(action_message(), "Type :quit to exit rirc");
+
+ /* clear error */
+ INP_C(0x0A);
+
+ c1 = channel("#c1", CHANNEL_T_CHANNEL);
+ c2 = channel("#c2", CHANNEL_T_CHANNEL);
+ s = server("host", "port", NULL, "user", "real");
+
+ if (!s || !c1 || !c2)
+ test_abort("Failed to create server and channels");
+
+ c1->server = s;
+ c2->server = s;
+ channel_list_add(&s->clist, c1);
+ channel_list_add(&s->clist, c2);
+
+ if (server_list_add(state_server_list(), s))
+ test_abort("Failed to add server");
+
+ channel_set_current(c1);
+
+ INP_S(":close with args");
+ INP_C(0x0A);
+
+ assert_strcmp(action_message(), "close: Unknown arg 'with'");
+
+ /* clear error */
+ INP_C(0x0A);
+
+ INP_S(":close");
+ INP_C(0x0A);
+
+ assert_ptr_null(action_handler);
+ assert_ptr_null(action_message());
+ assert_ptr_null(CURRENT_LINE);
+ assert_strcmp(current_channel()->name, "#c2");
+