Compare commits
33 Commits
ci/separat
...
fix/functi
| Author | SHA1 | Date | |
|---|---|---|---|
|
737309c888
|
|||
|
0a37dd3fe8
|
|||
|
8e8d715734
|
|||
| 730d46da16 | |||
|
70de0123b0
|
|||
|
569decbbd9
|
|||
|
caac08a7d7
|
|||
|
a179b02f5d
|
|||
|
5e4e28cd13
|
|||
|
4c327bcb0c
|
|||
|
3a77bb1014
|
|||
|
fff902d204
|
|||
|
3307e0eac2
|
|||
|
b02582b19e
|
|||
|
96acaa322d
|
|||
|
c33884ddc4
|
|||
|
b4dd64a0c9
|
|||
| ecf78b26fd | |||
| 0a531ca819 | |||
| 9472662c05 | |||
| 6f94c4bae2 | |||
| e4b6daddf0 | |||
| 036fab364a | |||
| 57e5f30f1a | |||
| ecdf299113 | |||
| 70a1d93821 | |||
| 3685676b0a | |||
| 78bfe2d439 | |||
| d823ca57ce | |||
| 1437aedb2a | |||
| 6f61e5b26c | |||
| c628c2acd5 | |||
| 1305c59b5e |
19
.github/workflows/ci-code.yml
vendored
19
.github/workflows/ci-code.yml
vendored
@@ -29,10 +29,10 @@ jobs:
|
|||||||
name: Linux
|
name: Linux
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Build
|
|
||||||
run: docker build -f Dockerfile.${{ matrix.flavor }} -t profanity .
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: docker run profanity ./ci-build.sh
|
run: |
|
||||||
|
docker build -f Dockerfile.${{ matrix.flavor }} -t profanity .
|
||||||
|
docker run profanity ./ci-build.sh
|
||||||
|
|
||||||
code-style:
|
code-style:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -50,9 +50,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
grep -P 'auto_(char|gchar|gcharv|guchar|jid|sqlite|gfd|FILE)[\w *]*;$' -r src && exit -1 || true
|
grep -P 'auto_(char|gchar|gcharv|guchar|jid|sqlite|gfd|FILE)[\w *]*;$' -r src && exit -1 || true
|
||||||
|
|
||||||
- name: Check CWE-134 format string vulnerabilities
|
|
||||||
run: ./check-cwe134.sh
|
|
||||||
|
|
||||||
- name: Install clang-format
|
- name: Install clang-format
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
@@ -101,13 +98,3 @@ jobs:
|
|||||||
- name: Check spelling
|
- name: Check spelling
|
||||||
run: |
|
run: |
|
||||||
codespell
|
codespell
|
||||||
|
|
||||||
coverage:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Code Coverage
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Build
|
|
||||||
run: docker build -f Dockerfile.arch -t profanity-cov .
|
|
||||||
- name: Run coverage
|
|
||||||
run: docker run profanity-cov ./ci-build.sh --coverage-only
|
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -62,8 +62,6 @@ tests/unittests/unittests
|
|||||||
tests/unittests/unittests.log
|
tests/unittests/unittests.log
|
||||||
tests/unittests/unittests.trs
|
tests/unittests/unittests.trs
|
||||||
test-suite.log
|
test-suite.log
|
||||||
test-files/
|
|
||||||
test-logs/
|
|
||||||
|
|
||||||
# valgrind output
|
# valgrind output
|
||||||
profval*
|
profval*
|
||||||
@@ -109,8 +107,3 @@ breaks
|
|||||||
*.tar.*
|
*.tar.*
|
||||||
*.zip
|
*.zip
|
||||||
*.log*
|
*.log*
|
||||||
coverage/
|
|
||||||
*.gcno
|
|
||||||
*.gcda
|
|
||||||
*.gcov
|
|
||||||
coverage.info
|
|
||||||
|
|||||||
@@ -90,24 +90,6 @@ set -e
|
|||||||
```
|
```
|
||||||
|
|
||||||
This will run the same tests that the CI runs and refuse the push if it fails.
|
This will run the same tests that the CI runs and refuse the push if it fails.
|
||||||
The CI script runs 4 parallel builds with different configurations:
|
|
||||||
- **Full** — all features enabled (+ coverage in `--coverage-only` mode)
|
|
||||||
- **Minimal** — all optional features disabled
|
|
||||||
- **NoEncrypt** — no encryption (OTR, PGP, OMEMO disabled)
|
|
||||||
- **Default** — default ./configure options
|
|
||||||
|
|
||||||
Each build runs Valgrind and functional tests on Linux.
|
|
||||||
Use `./ci-build.sh --coverage-only` to run only the Full build with coverage collection.
|
|
||||||
|
|
||||||
Output shows test results per build:
|
|
||||||
```
|
|
||||||
✓ Full PASSED
|
|
||||||
Unit tests: 437 passed, 0 failed
|
|
||||||
Functional tests: 69 passed, 0 failed
|
|
||||||
Coverage: Lines: 27.5% | Functions: 36.2% | Branches: 18.1%
|
|
||||||
Duration: 5m39s
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that it will run on the actual content of the repository directory and not
|
Note that it will run on the actual content of the repository directory and not
|
||||||
what may have been staged/committed.
|
what may have been staged/committed.
|
||||||
|
|
||||||
@@ -144,16 +126,6 @@ scan-build make
|
|||||||
scan-view ...
|
scan-view ...
|
||||||
```
|
```
|
||||||
|
|
||||||
### Security checks
|
|
||||||
|
|
||||||
We have a static analyzer `check-cwe134.sh` that detects CWE-134 format string vulnerabilities. It runs automatically in CI but you can also run it locally:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./check-cwe134.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This checks for unsafe patterns where data could be passed directly as a format string to functions like `printf`, `cons_show`, etc. Never pass a raw string for formatting; use `"%s"` format specifier instead.
|
|
||||||
|
|
||||||
### Finding typos
|
### Finding typos
|
||||||
|
|
||||||
We include a `.codespellrc` configuration file for `codespell` in the root directory.
|
We include a `.codespellrc` configuration file for `codespell` in the root directory.
|
||||||
@@ -166,54 +138,37 @@ You can run the `make spell` command for this.
|
|||||||
`make doublecheck` will run the code formatter, spell checker and unit tests.
|
`make doublecheck` will run the code formatter, spell checker and unit tests.
|
||||||
|
|
||||||
|
|
||||||
### Functional tests
|
### Functional tests: moving away from brittle ID hooks
|
||||||
|
|
||||||
The functional test suite uses [stabber](https://git.jabber.space/devs/stabber) as a mock XMPP server. Tests are located in `tests/functionaltests/`.
|
Historically the functional test suite relied on stabber's id based helpers like `stbbr_for_id("prof_presence_1", ...)` to register canned responses that would be sent once Profanity emitted a stanza carrying that exact `id` attribute. This made the tests fragile:
|
||||||
|
|
||||||
#### Running functional tests
|
* Changes to stanza id generation (sequence, format) broke tests unexpectedly.
|
||||||
|
* Reordering internal requests produced hard-to-debug race conditions when an `id` no longer matched.
|
||||||
|
* Parallel additions of new features could shift which stanzas received a given id causing unrelated test failures.
|
||||||
|
|
||||||
Functional tests require stabber to be installed. Once installed, tests run as part of `make check`:
|
We have migrated to content based stubbing using direct sends (`stbbr_send`) and query hooks (`stbbr_for_query`). Instead of tying a response to a predicted id we now send the required server stanzas explicitly after initiating actions. Example (see `tests/functionaltests/proftest.c`):
|
||||||
|
|
||||||
```bash
|
|
||||||
make check # Run all tests (unit + functional)
|
|
||||||
make check-functional-parallel # Run functional tests in parallel (~3x faster)
|
|
||||||
./tests/functionaltests/functionaltests # Run all functional tests sequentially
|
|
||||||
./tests/functionaltests/functionaltests 1 # Run specific group (1-4)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Test groups
|
|
||||||
|
|
||||||
Tests are organized into 4 groups for parallel execution:
|
|
||||||
|
|
||||||
| Group | Description |
|
|
||||||
|-------|-------------|
|
|
||||||
| 1 | Connect, Ping, Rooms, Software |
|
|
||||||
| 2 | Message, Receipts, Roster, Chat Session |
|
|
||||||
| 3 | Presence, Disconnect |
|
|
||||||
| 4 | MUC, Carbons |
|
|
||||||
|
|
||||||
To add a new group:
|
|
||||||
1. Define the test array in `functionaltests.c`
|
|
||||||
2. Add entry to `groups[]` array
|
|
||||||
3. Update `FUNC_TEST_GROUPS` in `Makefile.am`
|
|
||||||
|
|
||||||
#### Writing functional tests
|
|
||||||
|
|
||||||
Use content-based stubbing with stabber:
|
|
||||||
|
|
||||||
```c
|
```c
|
||||||
// Use stbbr_for_query for IQ queries (roster, disco, etc.)
|
// Old brittle approach
|
||||||
stbbr_for_query("jabber:iq:roster", "<iq type='result'>...</iq>");
|
stbbr_for_id("prof_presence_1", "<presence id='prof_presence_1' ...>");
|
||||||
|
|
||||||
// Use stbbr_send for presence, message, and push-style stanzas
|
// New approach: after authentication, send presence directly
|
||||||
stbbr_send("<presence from='buddy@localhost'>...</presence>");
|
stbbr_send("<presence from='stabber@localhost/profanity' to='stabber@localhost/profanity'>...caps...</presence>");
|
||||||
```
|
```
|
||||||
|
|
||||||
Guidelines:
|
Benefits:
|
||||||
|
|
||||||
1. Use `stbbr_for_query(namespace, xml)` for IQ queries where the namespace is stable.
|
* Eliminates dependency on internal id sequencing.
|
||||||
2. Use `stbbr_send(xml)` for presence, message, and other push-style stanzas.
|
* Clearer intent inside test code ("send presence now" vs "register hook and hope client triggers it").
|
||||||
3. Keep assertions tolerant of ordering when possible; use `prof_output_regex()` for flexible matching.
|
* Simplifies adding new tests—no need to inspect logs for generated ids.
|
||||||
4. If timing issues appear, use `prof_timeout()` around critical expectations and reset afterwards.
|
|
||||||
5. When adding new tests, place them in the appropriate group based on functionality.
|
Guidelines when writing new functional tests:
|
||||||
|
|
||||||
|
1. Prefer `stbbr_for_query(namespace, xml)` for IQ roster or disco queries where the namespace is stable.
|
||||||
|
2. Use `stbbr_send(xml)` for presence, message, and other push style stanzas.
|
||||||
|
3. Avoid `stbbr_for_id` unless the protocol flow genuinely requires correlating a specific request/response pair not covered by a namespace query.
|
||||||
|
4. Keep assertions tolerant of ordering when possible; rely on `prof_output_regex()` matches rather than hard-coded positions.
|
||||||
|
5. If timing flakiness appears, temporarily raise `prof_timeout()` around the critical expectation and reset it immediately afterwards.
|
||||||
|
|
||||||
|
The migration from `stbbr_for_id` is complete. All functional tests now use content-based stubbing. When adding new tests, follow the guidelines above.
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
FROM archlinux:latest
|
FROM archlinux
|
||||||
|
|
||||||
ENV TERM=xterm
|
ENV TERM=xterm
|
||||||
ENV CC="ccache gcc"
|
|
||||||
|
|
||||||
RUN pacman -Syyu --noconfirm
|
|
||||||
|
|
||||||
|
RUN pacman -Syu --noconfirm
|
||||||
# reflector is optional - if it fails due to network issues, continue with default mirrorlist
|
# reflector is optional - if it fails due to network issues, continue with default mirrorlist
|
||||||
RUN pacman -S --needed --noconfirm reflector && \
|
RUN pacman -S --needed --noconfirm reflector && \
|
||||||
(reflector --latest 20 --protocol https --sort rate --save /etc/pacman.d/mirrorlist || true)
|
(reflector --latest 20 --protocol https --sort rate --save /etc/pacman.d/mirrorlist || true)
|
||||||
@@ -14,12 +12,9 @@ RUN pacman -S --needed --noconfirm \
|
|||||||
autoconf-archive \
|
autoconf-archive \
|
||||||
automake \
|
automake \
|
||||||
base-devel \
|
base-devel \
|
||||||
ca-certificates \
|
|
||||||
ccache \
|
|
||||||
check \
|
check \
|
||||||
cmake \
|
cmake \
|
||||||
cmocka \
|
cmocka \
|
||||||
lcov \
|
|
||||||
curl \
|
curl \
|
||||||
debuginfod \
|
debuginfod \
|
||||||
doxygen \
|
doxygen \
|
||||||
@@ -66,12 +61,12 @@ USER root
|
|||||||
RUN pacman -U --noconfirm libstrophe-git/libstrophe-git-*.pkg.tar.zst
|
RUN pacman -U --noconfirm libstrophe-git/libstrophe-git-*.pkg.tar.zst
|
||||||
|
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
RUN git clone --depth 1 https://git.jabber.space/devs/stabber
|
RUN git clone -c http.sslverify=false https://git.jabber.space/devs/stabber
|
||||||
|
|
||||||
WORKDIR /usr/src/stabber
|
WORKDIR /usr/src/stabber
|
||||||
RUN ./bootstrap.sh
|
RUN ./bootstrap.sh
|
||||||
RUN ./configure --prefix=/usr --disable-dependency-tracking
|
RUN ./configure --prefix=/usr --disable-dependency-tracking
|
||||||
RUN make -j$(nproc)
|
RUN make
|
||||||
RUN make install
|
RUN make install
|
||||||
|
|
||||||
WORKDIR /usr/src/profanity
|
WORKDIR /usr/src/profanity
|
||||||
|
|||||||
@@ -3,17 +3,13 @@ FROM debian:testing
|
|||||||
|
|
||||||
ENV DEBIAN_FRONTEND="noninteractive"
|
ENV DEBIAN_FRONTEND="noninteractive"
|
||||||
ENV TERM=xterm
|
ENV TERM=xterm
|
||||||
ENV CC="ccache gcc"
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
autoconf \
|
autoconf \
|
||||||
autoconf-archive \
|
autoconf-archive \
|
||||||
automake \
|
automake \
|
||||||
ca-certificates \
|
|
||||||
ccache \
|
|
||||||
gcc \
|
gcc \
|
||||||
git \
|
git \
|
||||||
lcov \
|
|
||||||
libcmocka-dev \
|
libcmocka-dev \
|
||||||
libcurl3-dev \
|
libcurl3-dev \
|
||||||
libgcrypt-dev \
|
libgcrypt-dev \
|
||||||
@@ -41,19 +37,19 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
RUN mkdir -p /usr/src/{stabber,libstrophe,profanity}
|
RUN mkdir -p /usr/src/{stabber,libstrophe,profanity}
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
|
|
||||||
RUN git clone --depth 1 https://git.jabber.space/devs/stabber
|
RUN git clone -c http.sslverify=false https://git.jabber.space/devs/stabber
|
||||||
RUN git clone --depth 1 https://github.com/strophe/libstrophe
|
RUN git clone -c http.sslverify=false https://github.com/strophe/libstrophe
|
||||||
|
|
||||||
WORKDIR /usr/src/stabber
|
WORKDIR /usr/src/stabber
|
||||||
RUN ./bootstrap.sh
|
RUN ./bootstrap.sh
|
||||||
RUN ./configure --prefix=/usr --disable-dependency-tracking
|
RUN ./configure --prefix=/usr --disable-dependency-tracking
|
||||||
RUN make -j$(nproc)
|
RUN make
|
||||||
RUN make install
|
RUN make install
|
||||||
|
|
||||||
WORKDIR /usr/src/libstrophe
|
WORKDIR /usr/src/libstrophe
|
||||||
RUN ./bootstrap.sh
|
RUN ./bootstrap.sh
|
||||||
RUN ./configure --prefix=/usr
|
RUN ./configure --prefix=/usr
|
||||||
RUN make -j$(nproc)
|
RUN make
|
||||||
RUN make install
|
RUN make install
|
||||||
|
|
||||||
WORKDIR /usr/src/profanity
|
WORKDIR /usr/src/profanity
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
# Build the latest Fedora image
|
# Build the latest Fedora image
|
||||||
FROM fedora:latest
|
FROM fedora:latest
|
||||||
|
|
||||||
ENV TERM=xterm
|
|
||||||
ENV CC="ccache gcc"
|
|
||||||
|
|
||||||
# libmicrohttpd - for stabber
|
# libmicrohttpd - for stabber
|
||||||
# glibc-locale - to have en_US locale
|
# glibc-locale - to have en_US locale
|
||||||
RUN dnf install -y \
|
RUN dnf install -y \
|
||||||
@@ -11,11 +8,8 @@ RUN dnf install -y \
|
|||||||
autoconf-archive \
|
autoconf-archive \
|
||||||
automake \
|
automake \
|
||||||
awk \
|
awk \
|
||||||
ca-certificates \
|
|
||||||
ccache \
|
|
||||||
gcc \
|
gcc \
|
||||||
git \
|
git \
|
||||||
lcov \
|
|
||||||
glib2-devel \
|
glib2-devel \
|
||||||
glibc-all-langpacks \
|
glibc-all-langpacks \
|
||||||
gtk2-devel \
|
gtk2-devel \
|
||||||
@@ -51,20 +45,20 @@ ENV TERM=xterm
|
|||||||
RUN mkdir -p /usr/src
|
RUN mkdir -p /usr/src
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
|
|
||||||
RUN git clone --depth 1 https://git.jabber.space/devs/stabber
|
RUN git clone -c http.sslverify=false https://git.jabber.space/devs/stabber
|
||||||
WORKDIR /usr/src/stabber
|
WORKDIR /usr/src/stabber
|
||||||
RUN ./bootstrap.sh
|
RUN ./bootstrap.sh
|
||||||
RUN ./configure --prefix=/usr --disable-dependency-tracking
|
RUN ./configure --prefix=/usr --disable-dependency-tracking
|
||||||
RUN make -j$(nproc)
|
RUN make
|
||||||
RUN make install
|
RUN make install
|
||||||
|
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
RUN mkdir -p /usr/src/libstrophe
|
RUN mkdir -p /usr/src/libstrophe
|
||||||
RUN git clone --depth 1 https://github.com/strophe/libstrophe
|
RUN git clone -c http.sslverify=false https://github.com/strophe/libstrophe
|
||||||
WORKDIR /usr/src/libstrophe
|
WORKDIR /usr/src/libstrophe
|
||||||
RUN ./bootstrap.sh
|
RUN ./bootstrap.sh
|
||||||
RUN ./configure --prefix=/usr
|
RUN ./configure --prefix=/usr
|
||||||
RUN make -j$(nproc)
|
RUN make
|
||||||
RUN make install
|
RUN make install
|
||||||
|
|
||||||
RUN mkdir -p /usr/src/profanity
|
RUN mkdir -p /usr/src/profanity
|
||||||
|
|||||||
@@ -1,20 +1,14 @@
|
|||||||
# Build the latest openSUSE Tumbleweed image
|
# Build the latest openSUSE Tumbleweed image
|
||||||
FROM opensuse/tumbleweed
|
FROM opensuse/tumbleweed
|
||||||
|
|
||||||
ENV TERM=xterm
|
|
||||||
ENV CC="ccache gcc"
|
|
||||||
|
|
||||||
# libmicrohttpd - for stabber
|
# libmicrohttpd - for stabber
|
||||||
# glibc-locale - to have en_US locale
|
# glibc-locale - to have en_US locale
|
||||||
RUN zypper --non-interactive in --no-recommends \
|
RUN zypper --non-interactive in --no-recommends \
|
||||||
autoconf \
|
autoconf \
|
||||||
autoconf-archive \
|
autoconf-archive \
|
||||||
automake \
|
automake \
|
||||||
ca-certificates \
|
|
||||||
ccache \
|
|
||||||
gcc \
|
gcc \
|
||||||
git \
|
git \
|
||||||
lcov \
|
|
||||||
glib2-devel \
|
glib2-devel \
|
||||||
glibc-locale \
|
glibc-locale \
|
||||||
gtk2-devel \
|
gtk2-devel \
|
||||||
@@ -50,11 +44,11 @@ ENV TERM=xterm
|
|||||||
RUN mkdir -p /usr/src
|
RUN mkdir -p /usr/src
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
|
|
||||||
RUN git clone --depth 1 https://git.jabber.space/devs/stabber
|
RUN git clone -c http.sslverify=false https://git.jabber.space/devs/stabber
|
||||||
WORKDIR /usr/src/stabber
|
WORKDIR /usr/src/stabber
|
||||||
RUN ./bootstrap.sh
|
RUN ./bootstrap.sh
|
||||||
RUN ./configure --prefix=/usr --disable-dependency-tracking
|
RUN ./configure --prefix=/usr --disable-dependency-tracking
|
||||||
RUN make -j$(nproc)
|
RUN make
|
||||||
RUN make install
|
RUN make install
|
||||||
|
|
||||||
RUN mkdir -p /usr/src/profanity
|
RUN mkdir -p /usr/src/profanity
|
||||||
|
|||||||
@@ -2,17 +2,13 @@ FROM ubuntu:latest
|
|||||||
|
|
||||||
ENV DEBIAN_FRONTEND="noninteractive"
|
ENV DEBIAN_FRONTEND="noninteractive"
|
||||||
ENV TERM=xterm
|
ENV TERM=xterm
|
||||||
ENV CC="ccache gcc"
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
autoconf \
|
autoconf \
|
||||||
autoconf-archive \
|
autoconf-archive \
|
||||||
automake \
|
automake \
|
||||||
ca-certificates \
|
|
||||||
ccache \
|
|
||||||
gcc \
|
gcc \
|
||||||
git \
|
git \
|
||||||
lcov \
|
|
||||||
libcmocka-dev \
|
libcmocka-dev \
|
||||||
libcurl3-dev \
|
libcurl3-dev \
|
||||||
libgcrypt-dev \
|
libgcrypt-dev \
|
||||||
@@ -40,19 +36,19 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
RUN mkdir -p /usr/src/{stabber,libstrophe,profanity}
|
RUN mkdir -p /usr/src/{stabber,libstrophe,profanity}
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
|
|
||||||
RUN git clone --depth 1 https://git.jabber.space/devs/stabber
|
RUN git clone -c http.sslverify=false https://git.jabber.space/devs/stabber
|
||||||
RUN git clone --depth 1 https://github.com/strophe/libstrophe
|
RUN git clone -c http.sslverify=false https://github.com/strophe/libstrophe
|
||||||
|
|
||||||
WORKDIR /usr/src/stabber
|
WORKDIR /usr/src/stabber
|
||||||
RUN ./bootstrap.sh
|
RUN ./bootstrap.sh
|
||||||
RUN ./configure --prefix=/usr --disable-dependency-tracking
|
RUN ./configure --prefix=/usr --disable-dependency-tracking
|
||||||
RUN make -j$(nproc)
|
RUN make
|
||||||
RUN make install
|
RUN make install
|
||||||
|
|
||||||
WORKDIR /usr/src/libstrophe
|
WORKDIR /usr/src/libstrophe
|
||||||
RUN ./bootstrap.sh
|
RUN ./bootstrap.sh
|
||||||
RUN ./configure --prefix=/usr
|
RUN ./configure --prefix=/usr
|
||||||
RUN make -j$(nproc)
|
RUN make
|
||||||
RUN make install
|
RUN make install
|
||||||
|
|
||||||
WORKDIR /usr/src/profanity
|
WORKDIR /usr/src/profanity
|
||||||
|
|||||||
49
Makefile.am
49
Makefile.am
@@ -185,7 +185,6 @@ functionaltest_sources = \
|
|||||||
tests/functionaltests/test_software.c tests/functionaltests/test_software.h \
|
tests/functionaltests/test_software.c tests/functionaltests/test_software.h \
|
||||||
tests/functionaltests/test_muc.c tests/functionaltests/test_muc.h \
|
tests/functionaltests/test_muc.c tests/functionaltests/test_muc.h \
|
||||||
tests/functionaltests/test_disconnect.c tests/functionaltests/test_disconnect.h \
|
tests/functionaltests/test_disconnect.c tests/functionaltests/test_disconnect.h \
|
||||||
tests/functionaltests/test_lastactivity.c tests/functionaltests/test_lastactivity.h \
|
|
||||||
tests/functionaltests/functionaltests.c
|
tests/functionaltests/functionaltests.c
|
||||||
|
|
||||||
main_source = src/main.c
|
main_source = src/main.c
|
||||||
@@ -287,7 +286,7 @@ endif
|
|||||||
|
|
||||||
TESTS = tests/unittests/unittests
|
TESTS = tests/unittests/unittests
|
||||||
check_PROGRAMS = tests/unittests/unittests
|
check_PROGRAMS = tests/unittests/unittests
|
||||||
tests_unittests_unittests_CPPFLAGS = -I$(srcdir)/tests
|
tests_unittests_unittests_CPPFLAGS = -Itests/
|
||||||
tests_unittests_unittests_SOURCES = $(unittest_sources)
|
tests_unittests_unittests_SOURCES = $(unittest_sources)
|
||||||
tests_unittests_unittests_LDADD = -lcmocka
|
tests_unittests_unittests_LDADD = -lcmocka
|
||||||
|
|
||||||
@@ -303,32 +302,9 @@ if HAVE_FORKPTY
|
|||||||
TESTS += tests/functionaltests/functionaltests
|
TESTS += tests/functionaltests/functionaltests
|
||||||
check_PROGRAMS += tests/functionaltests/functionaltests
|
check_PROGRAMS += tests/functionaltests/functionaltests
|
||||||
tests_functionaltests_functionaltests_SOURCES = $(functionaltest_sources)
|
tests_functionaltests_functionaltests_SOURCES = $(functionaltest_sources)
|
||||||
tests_functionaltests_functionaltests_CPPFLAGS = -I$(srcdir)/tests
|
tests_functionaltests_functionaltests_CPPFLAGS = -Itests/
|
||||||
tests_functionaltests_functionaltests_CFLAGS = $(AM_CFLAGS)
|
tests_functionaltests_functionaltests_CFLAGS = $(AM_CFLAGS)
|
||||||
tests_functionaltests_functionaltests_LDADD = -lcmocka -lstabber @FORKPTY_LIB@
|
tests_functionaltests_functionaltests_LDADD = -lcmocka -lstabber @FORKPTY_LIB@
|
||||||
|
|
||||||
# Parallel functional tests target (~3x faster than sequential)
|
|
||||||
# Usage: make check-functional-parallel
|
|
||||||
# To add more groups: increase FUNC_TEST_GROUPS and add group in functionaltests.c
|
|
||||||
FUNC_TEST_GROUPS = 1 2 3 4
|
|
||||||
|
|
||||||
check-functional-parallel: tests/functionaltests/functionaltests
|
|
||||||
@echo "Running functional tests in parallel ($(words $(FUNC_TEST_GROUPS)) groups)..."
|
|
||||||
@mkdir -p $(builddir)/test-logs $(builddir)/test-files
|
|
||||||
@pids=""; \
|
|
||||||
for g in $(FUNC_TEST_GROUPS); do \
|
|
||||||
./tests/functionaltests/functionaltests $$g > $(builddir)/test-logs/group$$g.log 2>&1 & \
|
|
||||||
pids="$$pids $$!"; \
|
|
||||||
done; \
|
|
||||||
failed=0; i=1; \
|
|
||||||
for pid in $$pids; do \
|
|
||||||
wait $$pid || { echo "Group $$i FAILED"; cat $(builddir)/test-logs/group$$i.log; failed=1; }; \
|
|
||||||
i=$$((i + 1)); \
|
|
||||||
done; \
|
|
||||||
echo "=== Test Results Summary ==="; \
|
|
||||||
grep -E 'PASSED|FAILED|Running' $(builddir)/test-logs/group*.log || true; \
|
|
||||||
if [ $$failed -ne 0 ]; then echo "FUNCTIONAL TESTS FAILED"; exit 1; fi; \
|
|
||||||
echo "All functional test groups passed!"
|
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -373,10 +349,7 @@ clean-local:
|
|||||||
rm -f $(git_include) $(git_include).in
|
rm -f $(git_include) $(git_include).in
|
||||||
endif
|
endif
|
||||||
|
|
||||||
clean-functional-tests:
|
.PHONY: my-prof.supp
|
||||||
rm -rf $(builddir)/test-files $(builddir)/test-logs
|
|
||||||
|
|
||||||
.PHONY: my-prof.supp clean-functional-tests
|
|
||||||
my-prof.supp:
|
my-prof.supp:
|
||||||
@sed '/^# AUTO-GENERATED START/q' prof.supp > $@
|
@sed '/^# AUTO-GENERATED START/q' prof.supp > $@
|
||||||
@printf "\n\n# glib\n" >> $@
|
@printf "\n\n# glib\n" >> $@
|
||||||
@@ -392,21 +365,7 @@ check-unit: tests/unittests/unittests
|
|||||||
tests/unittests/unittests
|
tests/unittests/unittests
|
||||||
|
|
||||||
@VALGRIND_CHECK_RULES@
|
@VALGRIND_CHECK_RULES@
|
||||||
VALGRIND_SUPPRESSIONS_FILES=$(srcdir)/prof.supp
|
VALGRIND_SUPPRESSIONS_FILES=prof.supp
|
||||||
|
|
||||||
# Code coverage targets (requires --enable-coverage)
|
|
||||||
coverage-clean:
|
|
||||||
find . -name '*.gcda' -delete
|
|
||||||
find . -name '*.gcno' -delete
|
|
||||||
rm -rf coverage-html coverage.info
|
|
||||||
|
|
||||||
coverage-report: check
|
|
||||||
lcov --capture --directory . --output-file coverage.info --ignore-errors inconsistent
|
|
||||||
lcov --remove coverage.info '/usr/*' '*/tests/*' --output-file coverage.info --ignore-errors inconsistent
|
|
||||||
genhtml coverage.info --output-directory coverage-html
|
|
||||||
@echo "Coverage report generated in coverage-html/index.html"
|
|
||||||
|
|
||||||
.PHONY: coverage-clean coverage-report
|
|
||||||
|
|
||||||
format: $(all_c_sources)
|
format: $(all_c_sources)
|
||||||
clang-format -i $(all_c_sources)
|
clang-format -i $(all_c_sources)
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# check-cwe134.sh - Static analysis for CWE-134 format string vulnerabilities
|
|
||||||
#
|
|
||||||
# This script detects potentially unsafe usage of format string functions
|
|
||||||
# where user-controlled data may be passed without "%s" wrapper.
|
|
||||||
#
|
|
||||||
# Usage: ./check-cwe134.sh [directory]
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
DIR="${1:-src}"
|
|
||||||
|
|
||||||
echo "=== CWE-134 Format String Vulnerability Check ==="
|
|
||||||
echo "Scanning: $DIR"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Functions that accept format strings
|
|
||||||
FORMAT_FUNCS="cons_show|cons_debug|cons_show_error|log_info|log_error|log_warning|log_debug|win_println|win_print"
|
|
||||||
|
|
||||||
ERRORS=0
|
|
||||||
|
|
||||||
echo "Checking for unsafe format string usage..."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Pattern 1: function call with single variable argument (no format string)
|
|
||||||
# Example: cons_show(variable); - BAD
|
|
||||||
# Example: cons_show("%s", variable); - OK
|
|
||||||
# Matches: func(identifier) or func(identifier->member) or func(identifier[index])
|
|
||||||
RESULTS=$(grep -rn --include="*.c" -P "($FORMAT_FUNCS)\s*\(\s*[a-zA-Z_][a-zA-Z0-9_]*(\s*->\s*\w+|\s*\[\s*\w+\s*\])?\s*\)\s*;" "$DIR" 2>/dev/null || true)
|
|
||||||
|
|
||||||
# Filter out function definitions, declarations, and safe api_* wrappers
|
|
||||||
RESULTS=$(echo "$RESULTS" | grep -v "const char\|void \|^[^:]*:[0-9]*:[a-z_]*(\|api_cons_show\|api_log_" || true)
|
|
||||||
|
|
||||||
if [ -n "$RESULTS" ]; then
|
|
||||||
echo "❌ POTENTIAL CWE-134 VULNERABILITIES FOUND:"
|
|
||||||
echo ""
|
|
||||||
echo "$RESULTS"
|
|
||||||
echo ""
|
|
||||||
ERRORS=$(echo "$RESULTS" | wc -l)
|
|
||||||
else
|
|
||||||
echo "✅ No obvious CWE-134 issues found."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Additional check: GString->str passed directly (not as %s argument)
|
|
||||||
echo ""
|
|
||||||
echo "Checking for GString->str passed to format functions..."
|
|
||||||
|
|
||||||
GSTRING_RESULTS=$(grep -rn --include="*.c" -P "($FORMAT_FUNCS)\s*\([^)]*->str\s*\)" "$DIR" 2>/dev/null | grep -v '"%s"' || true)
|
|
||||||
|
|
||||||
if [ -n "$GSTRING_RESULTS" ]; then
|
|
||||||
echo "⚠️ GString->str passed without \"%s\" (review manually):"
|
|
||||||
echo ""
|
|
||||||
echo "$GSTRING_RESULTS"
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "=== Summary ==="
|
|
||||||
echo "Critical issues: $ERRORS"
|
|
||||||
|
|
||||||
if [ "$ERRORS" -gt 0 ]; then
|
|
||||||
echo ""
|
|
||||||
echo "Fix by adding \"%s\" format specifier:"
|
|
||||||
echo " BAD: cons_show(variable);"
|
|
||||||
echo " GOOD: cons_show(\"%s\", variable);"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
405
ci-build.sh
405
ci-build.sh
@@ -15,144 +15,14 @@ error_handler()
|
|||||||
log_content ./test-suite.log
|
log_content ./test-suite.log
|
||||||
log_content ./test-suite-memcheck.log
|
log_content ./test-suite-memcheck.log
|
||||||
|
|
||||||
echo >&2
|
echo
|
||||||
echo "Error ${ERR_CODE} with command '${BASH_COMMAND}' on line ${BASH_LINENO[0]}. Exiting." >&2
|
echo "Error ${ERR_CODE} with command '${BASH_COMMAND}' on line ${BASH_LINENO[0]}. Exiting."
|
||||||
echo >&2
|
echo
|
||||||
exit ${ERR_CODE}
|
exit ${ERR_CODE}
|
||||||
}
|
}
|
||||||
|
|
||||||
trap error_handler ERR
|
trap error_handler ERR
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Constants
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Number of parallel build configurations
|
|
||||||
readonly TEST_BUILDS=4
|
|
||||||
|
|
||||||
# Human-readable names for each build configuration
|
|
||||||
readonly BUILD_NAMES=(
|
|
||||||
"Full" # 1. All features enabled
|
|
||||||
"Minimal" # 2. All optional features disabled
|
|
||||||
"NoEncrypt" # 3. No encryption (otr, pgp, omemo disabled)
|
|
||||||
"Default" # 4. Default ./configure options
|
|
||||||
)
|
|
||||||
|
|
||||||
# Regex patterns for parsing test output
|
|
||||||
readonly CMOCKA_PASSED_PATTERN='^\[ PASSED \] [0-9]+ test'
|
|
||||||
readonly CMOCKA_FAILED_PATTERN='^\[ FAILED \] [0-9]+ test'
|
|
||||||
|
|
||||||
# Coverage extraction patterns (matches both Docker and CI paths)
|
|
||||||
readonly COVERAGE_PATTERNS='*/profanity/src/* */src/src/*'
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Helper Functions
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Parse STATS line from build log and set global variables
|
|
||||||
# Usage: parse_build_stats <log_file>
|
|
||||||
parse_build_stats() {
|
|
||||||
local log_file="$1"
|
|
||||||
local stats_line
|
|
||||||
stats_line=$(grep "^STATS:" "$log_file" 2>/dev/null | tail -1)
|
|
||||||
|
|
||||||
STAT_UNIT_P=$(echo "$stats_line" | grep -oE "unit_passed=[0-9]+" | cut -d= -f2)
|
|
||||||
STAT_UNIT_F=$(echo "$stats_line" | grep -oE "unit_failed=[0-9]+" | cut -d= -f2)
|
|
||||||
STAT_FUNC_P=$(echo "$stats_line" | grep -oE "func_passed=[0-9]+" | cut -d= -f2)
|
|
||||||
STAT_FUNC_F=$(echo "$stats_line" | grep -oE "func_failed=[0-9]+" | cut -d= -f2)
|
|
||||||
STAT_COV_LINES=$(echo "$stats_line" | grep -oE "cov_lines=[0-9.]+%|cov_lines=n/a" | cut -d= -f2)
|
|
||||||
STAT_COV_FUNCS=$(echo "$stats_line" | grep -oE "cov_funcs=[0-9.]+%|cov_funcs=n/a" | cut -d= -f2)
|
|
||||||
STAT_COV_BRANCHES=$(echo "$stats_line" | grep -oE "cov_branches=[0-9.]+%|cov_branches=n/a" | cut -d= -f2)
|
|
||||||
STAT_TIME=$(echo "$stats_line" | grep -oE "time=[0-9]+m[0-9]+s" | cut -d= -f2)
|
|
||||||
|
|
||||||
: "${STAT_UNIT_P:=0}"
|
|
||||||
: "${STAT_UNIT_F:=0}"
|
|
||||||
: "${STAT_FUNC_P:=0}"
|
|
||||||
: "${STAT_FUNC_F:=0}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Extract test count from log file
|
|
||||||
# Usage: extract_test_count <log_file> <pattern>
|
|
||||||
extract_test_count() {
|
|
||||||
grep -E "$2" "$1" 2>/dev/null | grep -oE "[0-9]+" | head -1
|
|
||||||
}
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Test Verification
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Verify that test failures are properly detected
|
|
||||||
# This is a meta-test: it runs a deliberately failing test
|
|
||||||
# and checks that the test framework reports the failure correctly
|
|
||||||
verify_test_failure_detection()
|
|
||||||
{
|
|
||||||
echo "==> Verifying test failure detection..."
|
|
||||||
|
|
||||||
# Create a simple failing test
|
|
||||||
cat > /tmp/test_must_fail.c << 'EOF'
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <setjmp.h>
|
|
||||||
#include <cmocka.h>
|
|
||||||
|
|
||||||
static void test_that_must_fail(void **state) {
|
|
||||||
(void)state;
|
|
||||||
assert_true(0); // This MUST fail
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void) {
|
|
||||||
const struct CMUnitTest tests[] = {
|
|
||||||
cmocka_unit_test(test_that_must_fail),
|
|
||||||
};
|
|
||||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Compile the failing test
|
|
||||||
if ! gcc -o /tmp/test_must_fail /tmp/test_must_fail.c -lcmocka 2>/dev/null; then
|
|
||||||
echo "Warning: Could not compile test failure verification (cmocka not available?)"
|
|
||||||
echo "Skipping test failure detection verification"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test 1: Single failing test detection
|
|
||||||
echo " Testing single test failure detection..."
|
|
||||||
if /tmp/test_must_fail > /tmp/test_must_fail.log 2>&1; then
|
|
||||||
echo "ERROR: Test that should fail returned success (exit code 0)" >&2
|
|
||||||
echo "This means the test framework is NOT detecting failures correctly!" >&2
|
|
||||||
echo "--- Test output ---" >&2
|
|
||||||
cat /tmp/test_must_fail.log >&2
|
|
||||||
echo "--- End output ---" >&2
|
|
||||||
rm -f /tmp/test_must_fail /tmp/test_must_fail.c /tmp/test_must_fail.log
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo " ✓ Single test failure correctly detected"
|
|
||||||
|
|
||||||
# Test 2: Parallel failure detection (simulates check-functional-parallel)
|
|
||||||
echo " Testing parallel test failure detection..."
|
|
||||||
failed=0
|
|
||||||
/tmp/test_must_fail > /tmp/p1.log 2>&1 & pid1=$!
|
|
||||||
true > /tmp/p2.log 2>&1 & pid2=$! # This passes
|
|
||||||
/tmp/test_must_fail > /tmp/p3.log 2>&1 & pid3=$!
|
|
||||||
true > /tmp/p4.log 2>&1 & pid4=$! # This passes
|
|
||||||
|
|
||||||
wait $pid1 || failed=$((failed + 1))
|
|
||||||
wait $pid2 || failed=$((failed + 1))
|
|
||||||
wait $pid3 || failed=$((failed + 1))
|
|
||||||
wait $pid4 || failed=$((failed + 1))
|
|
||||||
|
|
||||||
if [ $failed -ne 2 ]; then
|
|
||||||
echo "ERROR: Expected 2 failures in parallel tests, got $failed" >&2
|
|
||||||
echo "Parallel failure detection is broken!" >&2
|
|
||||||
rm -f /tmp/test_must_fail /tmp/test_must_fail.c /tmp/test_must_fail.log /tmp/p?.log
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo " ✓ Parallel test failures correctly detected (2 of 4 failed as expected)"
|
|
||||||
|
|
||||||
rm -f /tmp/test_must_fail /tmp/test_must_fail.c /tmp/test_must_fail.log /tmp/p?.log
|
|
||||||
echo "✓ Test failure detection verified"
|
|
||||||
}
|
|
||||||
|
|
||||||
num_cores()
|
num_cores()
|
||||||
{
|
{
|
||||||
# Check for cores, for systems with:
|
# Check for cores, for systems with:
|
||||||
@@ -164,20 +34,6 @@ num_cores()
|
|||||||
|| getconf _NPROCESSORS_ONLN 2>/dev/null
|
|| getconf _NPROCESSORS_ONLN 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
# Run test failure detection verification first
|
|
||||||
verify_test_failure_detection
|
|
||||||
|
|
||||||
# Parse arguments
|
|
||||||
COVERAGE_ONLY=no
|
|
||||||
for arg in "$@"; do
|
|
||||||
case "$arg" in
|
|
||||||
--coverage-only)
|
|
||||||
COVERAGE_ONLY=yes
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
./bootstrap.sh
|
./bootstrap.sh
|
||||||
|
|
||||||
tests=()
|
tests=()
|
||||||
@@ -188,7 +44,7 @@ ARCH="$(uname | tr '[:upper:]' '[:lower:]')"
|
|||||||
|
|
||||||
case "$ARCH" in
|
case "$ARCH" in
|
||||||
linux*)
|
linux*)
|
||||||
# 4 configurations for parallel CI
|
# Reduced set of configurations for faster CI
|
||||||
tests=(
|
tests=(
|
||||||
# 1. Full build (all features enabled)
|
# 1. Full build (all features enabled)
|
||||||
"--enable-notifications --enable-icons-and-clipboard --enable-otr --enable-pgp
|
"--enable-notifications --enable-icons-and-clipboard --enable-otr --enable-pgp
|
||||||
@@ -200,13 +56,15 @@ case "$ARCH" in
|
|||||||
--disable-python-plugins --without-xscreensaver --disable-omemo-qrcode --disable-gdk-pixbuf"
|
--disable-python-plugins --without-xscreensaver --disable-omemo-qrcode --disable-gdk-pixbuf"
|
||||||
# 3. No encryption (disable otr, pgp, omemo)
|
# 3. No encryption (disable otr, pgp, omemo)
|
||||||
"--disable-pgp --disable-otr --disable-omemo --disable-omemo-qrcode"
|
"--disable-pgp --disable-otr --disable-omemo --disable-omemo-qrcode"
|
||||||
# 4. Default configuration
|
# 4. No plugins
|
||||||
|
"--disable-plugins --disable-c-plugins --disable-python-plugins"
|
||||||
|
# 5. Default configuration
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
source /etc/profile.d/debuginfod.sh 2>/dev/null || true
|
source /etc/profile.d/debuginfod.sh 2>/dev/null || true
|
||||||
;;
|
;;
|
||||||
darwin*)
|
darwin*)
|
||||||
# 4 configurations for parallel CI
|
# Reduced set of configurations for faster CI
|
||||||
tests=(
|
tests=(
|
||||||
# 1. Full build (all features enabled)
|
# 1. Full build (all features enabled)
|
||||||
"--enable-notifications --enable-icons-and-clipboard --enable-otr --enable-pgp
|
"--enable-notifications --enable-icons-and-clipboard --enable-otr --enable-pgp
|
||||||
@@ -218,7 +76,9 @@ case "$ARCH" in
|
|||||||
--disable-python-plugins"
|
--disable-python-plugins"
|
||||||
# 3. No encryption (disable otr, pgp, omemo)
|
# 3. No encryption (disable otr, pgp, omemo)
|
||||||
"--disable-pgp --disable-otr --disable-omemo"
|
"--disable-pgp --disable-otr --disable-omemo"
|
||||||
# 4. Default configuration
|
# 4. No plugins
|
||||||
|
"--disable-plugins --disable-c-plugins --disable-python-plugins"
|
||||||
|
# 5. Default configuration
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
;;
|
;;
|
||||||
@@ -230,7 +90,7 @@ case "$ARCH" in
|
|||||||
# src/event/server_events.c:1454:19: error: universal character names are only valid in C++ and C99
|
# src/event/server_events.c:1454:19: error: universal character names are only valid in C++ and C99
|
||||||
CC="egcc -std=gnu99 -fexec-charset=UTF-8"
|
CC="egcc -std=gnu99 -fexec-charset=UTF-8"
|
||||||
|
|
||||||
# 4 configurations for parallel CI
|
# Reduced set of configurations for faster CI
|
||||||
tests=(
|
tests=(
|
||||||
# 1. Full build (all features enabled)
|
# 1. Full build (all features enabled)
|
||||||
"--enable-notifications --enable-icons-and-clipboard --enable-otr --enable-pgp
|
"--enable-notifications --enable-icons-and-clipboard --enable-otr --enable-pgp
|
||||||
@@ -242,230 +102,45 @@ case "$ARCH" in
|
|||||||
--disable-python-plugins"
|
--disable-python-plugins"
|
||||||
# 3. No encryption (disable otr, pgp, omemo)
|
# 3. No encryption (disable otr, pgp, omemo)
|
||||||
"--disable-pgp --disable-otr --disable-omemo"
|
"--disable-pgp --disable-otr --disable-omemo"
|
||||||
# 4. Default configuration
|
# 4. No plugins
|
||||||
|
"--disable-plugins --disable-c-plugins --disable-python-plugins"
|
||||||
|
# 5. Default configuration
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Function to build and test a single configuration
|
case "$ARCH" in
|
||||||
build_and_test() {
|
linux*)
|
||||||
local features="$1"
|
echo
|
||||||
local extra_args="$2"
|
echo "--> Building with ./configure ${tests[0]} --enable-valgrind $*"
|
||||||
local idx="$3"
|
echo
|
||||||
local run_valgrind="$4"
|
|
||||||
local run_coverage="$5"
|
|
||||||
local build_dir="build-$idx"
|
|
||||||
local log_file="build-$idx.log"
|
|
||||||
|
|
||||||
{
|
# shellcheck disable=SC2086
|
||||||
echo "=== Build $idx started at $(date) ==="
|
./configure ${tests[0]} --enable-valgrind $*
|
||||||
echo "--> Building in $build_dir with ./configure -C $features $extra_args"
|
|
||||||
|
|
||||||
local start_time=$SECONDS
|
$MAKE CC="${CC}"
|
||||||
|
if grep '^ID=' /etc/os-release | grep -q -e debian; then
|
||||||
mkdir -p "$build_dir"
|
$MAKE check-valgrind
|
||||||
cd "$build_dir"
|
|
||||||
|
|
||||||
# shellcheck disable=SC2086
|
|
||||||
if ! ../configure -C $features $extra_args; then
|
|
||||||
echo "ERROR: configure failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! $MAKE CC="${CC}"; then
|
|
||||||
echo "ERROR: make failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Run unit tests
|
|
||||||
local unit_passed=0 unit_failed=0
|
|
||||||
if [ "$run_valgrind" = "yes" ]; then
|
|
||||||
echo "--> Running unit tests under Valgrind..."
|
|
||||||
# Build unit tests first
|
|
||||||
$MAKE tests/unittests/unittests
|
|
||||||
# Run valgrind directly to capture cmocka output
|
|
||||||
valgrind --error-exitcode=1 --leak-check=full \
|
|
||||||
--suppressions=../prof.supp \
|
|
||||||
tests/unittests/unittests 2>&1 | tee unit-tests-output.log
|
|
||||||
valgrind_exit=${PIPESTATUS[0]}
|
|
||||||
if [ $valgrind_exit -ne 0 ]; then
|
|
||||||
echo "ERROR: Valgrind unit tests failed (exit code $valgrind_exit)"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
echo "--> Running unit tests..."
|
$MAKE check-valgrind || log_content ./test-suite-memcheck.log
|
||||||
$MAKE tests/unittests/unittests
|
|
||||||
tests/unittests/unittests 2>&1 | tee unit-tests-output.log
|
|
||||||
if [ ${PIPESTATUS[0]} -ne 0 ]; then
|
|
||||||
echo "ERROR: Unit tests failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
# Extract unit test counts from cmocka output
|
$MAKE distclean
|
||||||
unit_passed=$(extract_test_count unit-tests-output.log "$CMOCKA_PASSED_PATTERN")
|
;;
|
||||||
unit_failed=$(extract_test_count unit-tests-output.log "$CMOCKA_FAILED_PATTERN")
|
esac
|
||||||
: "${unit_passed:=0}" "${unit_failed:=0}"
|
|
||||||
echo "UNIT_TESTS: passed=$unit_passed failed=$unit_failed"
|
|
||||||
|
|
||||||
# Set build index for port allocation: build 1 uses ports 5230-5233,
|
for features in "${tests[@]}"
|
||||||
# build 2 uses 5234-5237, etc. This prevents port conflicts in parallel builds.
|
do
|
||||||
export PROF_BUILD_INDEX=$idx
|
|
||||||
local func_passed=0 func_failed=0
|
|
||||||
if ! $MAKE check-functional-parallel; then
|
|
||||||
echo "ERROR: functional tests failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# Extract functional test counts from group logs
|
|
||||||
echo "=== Functional test results ==="
|
|
||||||
for glog in ./test-logs/group*.log; do
|
|
||||||
if [ -f "$glog" ]; then
|
|
||||||
cnt=$(extract_test_count "$glog" "$CMOCKA_PASSED_PATTERN")
|
|
||||||
[ -n "$cnt" ] && func_passed=$((func_passed + cnt))
|
|
||||||
cnt=$(extract_test_count "$glog" "$CMOCKA_FAILED_PATTERN")
|
|
||||||
[ -n "$cnt" ] && func_failed=$((func_failed + cnt))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "FUNC_TESTS: passed=$func_passed failed=$func_failed"
|
|
||||||
|
|
||||||
# Collect coverage data if enabled (lines, functions, branches)
|
|
||||||
# Must be done BEFORE make clean which removes .gcda files
|
|
||||||
local cov_lines="n/a" cov_funcs="n/a" cov_branches="n/a"
|
|
||||||
if [ "$run_coverage" = "yes" ]; then
|
|
||||||
echo "--> Collecting coverage data..."
|
|
||||||
if command -v lcov >/dev/null 2>&1; then
|
|
||||||
lcov --capture --directory . --output-file coverage-full.info \
|
|
||||||
--rc lcov_branch_coverage=1 --ignore-errors inconsistent 2>&1 || true
|
|
||||||
# Extract only production code from src/ directory, exclude tests
|
|
||||||
# shellcheck disable=SC2086
|
|
||||||
lcov --extract coverage-full.info $COVERAGE_PATTERNS \
|
|
||||||
--output-file coverage.info \
|
|
||||||
--rc lcov_branch_coverage=1 --ignore-errors inconsistent 2>&1 || true
|
|
||||||
if [ -f coverage.info ] && [ -s coverage.info ]; then
|
|
||||||
local summary
|
|
||||||
summary=$(lcov --summary coverage.info \
|
|
||||||
--rc lcov_branch_coverage=1 --ignore-errors inconsistent 2>&1 || true)
|
|
||||||
cov_lines=$(echo "$summary" | grep -E "lines\.*:" | grep -oE "[0-9]+\.[0-9]+%" | head -1)
|
|
||||||
cov_funcs=$(echo "$summary" | grep -E "functions\.*:" | grep -oE "[0-9]+\.[0-9]+%" | head -1)
|
|
||||||
cov_branches=$(echo "$summary" | grep -E "branches\.*:" | grep -oE "[0-9]+\.[0-9]+%" | head -1)
|
|
||||||
[ -z "$cov_lines" ] && cov_lines="n/a"
|
|
||||||
[ -z "$cov_funcs" ] && cov_funcs="n/a"
|
|
||||||
[ -z "$cov_branches" ] && cov_branches="n/a"
|
|
||||||
else
|
|
||||||
echo "WARNING: coverage.info is empty or not created"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "WARNING: lcov not found"
|
|
||||||
fi
|
|
||||||
echo "COVERAGE: lines=$cov_lines funcs=$cov_funcs branches=$cov_branches"
|
|
||||||
fi
|
|
||||||
|
|
||||||
./profanity -v
|
|
||||||
|
|
||||||
# Save coverage.info before cleanup (for CI artifact)
|
|
||||||
# Only copy in CI environment to avoid leaving artifacts during local runs
|
|
||||||
if [ "$run_coverage" = "yes" ] && [ -f coverage.info ] && [ -n "$CI" ]; then
|
|
||||||
cp coverage.info ../coverage.info
|
|
||||||
echo "Coverage report saved to coverage.info"
|
|
||||||
fi
|
|
||||||
|
|
||||||
$MAKE clean
|
|
||||||
|
|
||||||
cd ..
|
|
||||||
rm -rf "$build_dir"
|
|
||||||
|
|
||||||
local elapsed=$((SECONDS - start_time))
|
|
||||||
local mins=$((elapsed / 60))
|
|
||||||
local secs=$((elapsed % 60))
|
|
||||||
|
|
||||||
echo "=== Build $idx completed at $(date) ==="
|
|
||||||
echo "STATS: unit_passed=$unit_passed unit_failed=$unit_failed func_passed=$func_passed func_failed=$func_failed cov_lines=$cov_lines cov_funcs=$cov_funcs cov_branches=$cov_branches time=${mins}m${secs}s"
|
|
||||||
} > "$log_file" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run configurations
|
|
||||||
# Coverage enabled only for build 1 (Full) - it has most code paths
|
|
||||||
echo
|
|
||||||
echo "=== Start build ==="
|
|
||||||
echo
|
|
||||||
|
|
||||||
if [ "$COVERAGE_ONLY" = "yes" ]; then
|
|
||||||
echo "Running coverage-only mode (${BUILD_NAMES[0]} build)..."
|
|
||||||
echo
|
echo
|
||||||
run_valgrind="no"
|
echo "--> Building with ./configure ${features} $*"
|
||||||
run_coverage="yes"
|
|
||||||
extra_flags="--enable-coverage"
|
|
||||||
build_and_test "${tests[0]}" "$* $extra_flags" "1" "$run_valgrind" "$run_coverage" &
|
|
||||||
pids=("$!")
|
|
||||||
echo " → ${BUILD_NAMES[0]}: ${tests[0]} [+Coverage]"
|
|
||||||
else
|
|
||||||
echo "Starting $TEST_BUILDS parallel build configurations..."
|
|
||||||
echo
|
echo
|
||||||
pids=()
|
|
||||||
for idx in $(seq 1 $TEST_BUILDS); do
|
|
||||||
if [ $idx -le ${#tests[@]} ]; then
|
|
||||||
# All builds run Valgrind on Linux
|
|
||||||
if [ "$ARCH" = "linux" ]; then
|
|
||||||
run_valgrind="yes"
|
|
||||||
extra_flags="--enable-valgrind"
|
|
||||||
else
|
|
||||||
run_valgrind="no"
|
|
||||||
extra_flags=""
|
|
||||||
fi
|
|
||||||
run_coverage="no"
|
|
||||||
build_and_test "${tests[$((idx-1))]}" "$* $extra_flags" "$idx" "$run_valgrind" "$run_coverage" &
|
|
||||||
pids+=("$!")
|
|
||||||
flags_desc=""
|
|
||||||
[ "$run_valgrind" = "yes" ] && flags_desc=" [+Valgrind]"
|
|
||||||
echo " → ${BUILD_NAMES[$((idx-1))]}: ${tests[$((idx-1))]}$flags_desc"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
echo
|
|
||||||
|
|
||||||
# Wait for all builds and check exit codes
|
# shellcheck disable=SC2086
|
||||||
echo "Waiting for builds to complete..."
|
./configure $features $*
|
||||||
echo
|
|
||||||
failed_builds=()
|
$MAKE CC="${CC}"
|
||||||
for i in "${!pids[@]}"; do
|
$MAKE check
|
||||||
idx=$((i + 1))
|
|
||||||
if wait "${pids[$i]}"; then
|
./profanity -v
|
||||||
if [ -f "build-$idx.log" ]; then
|
$MAKE clean
|
||||||
parse_build_stats "build-$idx.log"
|
|
||||||
|
|
||||||
echo "✓ ${BUILD_NAMES[$i]} PASSED"
|
|
||||||
echo " Unit tests: $STAT_UNIT_P passed, $STAT_UNIT_F failed"
|
|
||||||
echo " Functional tests: $STAT_FUNC_P passed, $STAT_FUNC_F failed"
|
|
||||||
if [ "$STAT_COV_LINES" != "n/a" ] && [ -n "$STAT_COV_LINES" ]; then
|
|
||||||
echo " Coverage: Lines: $STAT_COV_LINES | Functions: $STAT_COV_FUNCS | Branches: $STAT_COV_BRANCHES"
|
|
||||||
fi
|
|
||||||
echo " Duration: ${STAT_TIME:-?}"
|
|
||||||
else
|
|
||||||
echo "✓ ${BUILD_NAMES[$i]} passed (no stats available)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "✗ ${BUILD_NAMES[$i]} FAILED" >&2
|
|
||||||
failed_builds+=("$idx")
|
|
||||||
fi
|
|
||||||
echo
|
|
||||||
done
|
done
|
||||||
|
|
||||||
# Show failed builds full logs
|
|
||||||
for idx in "${failed_builds[@]}"; do
|
|
||||||
if [ -f "build-$idx.log" ]; then
|
|
||||||
echo "=== ${BUILD_NAMES[$((idx-1))]} FAILURE LOG ===" >&2
|
|
||||||
cat "build-$idx.log" >&2
|
|
||||||
echo >&2
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ ${#failed_builds[@]} -gt 0 ]; then
|
|
||||||
echo "RESULT: FAILED (builds ${failed_builds[*]})" >&2
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
if [ "$COVERAGE_ONLY" = "yes" ]; then
|
|
||||||
echo "RESULT: COVERAGE BUILD PASSED ✓"
|
|
||||||
else
|
|
||||||
echo "RESULT: ALL $TEST_BUILDS BUILDS PASSED ✓"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|||||||
@@ -69,8 +69,6 @@ AC_ARG_ENABLE([gdk-pixbuf],
|
|||||||
[AS_HELP_STRING([--enable-gdk-pixbuf], [enable GDK Pixbuf support to scale avatars before uploading])])
|
[AS_HELP_STRING([--enable-gdk-pixbuf], [enable GDK Pixbuf support to scale avatars before uploading])])
|
||||||
AC_ARG_ENABLE([omemo-qrcode],
|
AC_ARG_ENABLE([omemo-qrcode],
|
||||||
[AS_HELP_STRING([--enable-omemo-qrcode], [enable ability to display omemo qr code])])
|
[AS_HELP_STRING([--enable-omemo-qrcode], [enable ability to display omemo qr code])])
|
||||||
AC_ARG_ENABLE([coverage],
|
|
||||||
[AS_HELP_STRING([--enable-coverage], [enable code coverage analysis])])
|
|
||||||
|
|
||||||
m4_include([m4/ax_valgrind_check.m4])
|
m4_include([m4/ax_valgrind_check.m4])
|
||||||
AX_VALGRIND_DFLT([drd], [off])
|
AX_VALGRIND_DFLT([drd], [off])
|
||||||
@@ -388,11 +386,6 @@ AC_SUBST([FORKPTY_LIB])
|
|||||||
AM_CFLAGS="$AM_CFLAGS -Wall -Wno-deprecated-declarations -std=gnu99 -ggdb3"
|
AM_CFLAGS="$AM_CFLAGS -Wall -Wno-deprecated-declarations -std=gnu99 -ggdb3"
|
||||||
AM_LDFLAGS="$AM_LDFLAGS -export-dynamic"
|
AM_LDFLAGS="$AM_LDFLAGS -export-dynamic"
|
||||||
|
|
||||||
AS_IF([test "x$enable_coverage" = xyes],
|
|
||||||
[AM_CFLAGS="$AM_CFLAGS --coverage -O0"
|
|
||||||
AM_LDFLAGS="$AM_LDFLAGS --coverage"
|
|
||||||
AC_MSG_NOTICE([Code coverage analysis enabled])])
|
|
||||||
|
|
||||||
AS_IF([test "x$PACKAGE_STATUS" = xdevelopment],
|
AS_IF([test "x$PACKAGE_STATUS" = xdevelopment],
|
||||||
[AM_CFLAGS="$AM_CFLAGS -Wunused -Werror"])
|
[AM_CFLAGS="$AM_CFLAGS -Wunused -Werror"])
|
||||||
AS_IF([test "x$PLATFORM" = xosx],
|
AS_IF([test "x$PLATFORM" = xosx],
|
||||||
|
|||||||
23
prof.supp
23
prof.supp
@@ -8,29 +8,6 @@
|
|||||||
# * python suppressions file from https://github.com/python/cpython/blob/main/Misc/valgrind-python.supp
|
# * python suppressions file from https://github.com/python/cpython/blob/main/Misc/valgrind-python.supp
|
||||||
#
|
#
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# glibc AVX2 optimizations (false positives)
|
|
||||||
# See: https://sourceware.org/bugzilla/show_bug.cgi?id=19796
|
|
||||||
# ============================================
|
|
||||||
|
|
||||||
{
|
|
||||||
glibc_wcpncpy_avx2
|
|
||||||
Memcheck:Addr32
|
|
||||||
fun:__wcpncpy_avx2
|
|
||||||
fun:wcsxfrm_l
|
|
||||||
fun:g_utf8_collate_key
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
glibc_wcsxfrm_avx2
|
|
||||||
Memcheck:Addr32
|
|
||||||
...
|
|
||||||
fun:wcsxfrm_l
|
|
||||||
fun:g_utf8_collate_key
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# Functional tests suppressions (stabber/pthread)
|
# Functional tests suppressions (stabber/pthread)
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|||||||
@@ -2825,15 +2825,16 @@ cmd_search_index_any(char* term)
|
|||||||
int terms_len = g_strv_length(processed_terms);
|
int terms_len = g_strv_length(processed_terms);
|
||||||
|
|
||||||
for (int i = 0; i < terms_len; i++) {
|
for (int i = 0; i < terms_len; i++) {
|
||||||
GHashTableIter iter;
|
GList* index_keys = g_hash_table_get_keys(search_index);
|
||||||
gpointer key, value;
|
GList* curr = index_keys;
|
||||||
g_hash_table_iter_init(&iter, search_index);
|
while (curr) {
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
char* index_entry = g_hash_table_lookup(search_index, curr->data);
|
||||||
char* index_entry = (char*)value;
|
|
||||||
if (g_str_match_string(processed_terms[i], index_entry, FALSE)) {
|
if (g_str_match_string(processed_terms[i], index_entry, FALSE)) {
|
||||||
results = g_list_append(results, key);
|
results = g_list_append(results, curr->data);
|
||||||
}
|
}
|
||||||
|
curr = g_list_next(curr);
|
||||||
}
|
}
|
||||||
|
g_list_free(index_keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
@@ -2847,14 +2848,13 @@ cmd_search_index_all(char* term)
|
|||||||
auto_gcharv gchar** terms = g_str_tokenize_and_fold(term, NULL, NULL);
|
auto_gcharv gchar** terms = g_str_tokenize_and_fold(term, NULL, NULL);
|
||||||
int terms_len = g_strv_length(terms);
|
int terms_len = g_strv_length(terms);
|
||||||
|
|
||||||
GHashTableIter iter;
|
GList* commands = g_hash_table_get_keys(search_index);
|
||||||
gpointer key, value;
|
GList* curr = commands;
|
||||||
g_hash_table_iter_init(&iter, search_index);
|
while (curr) {
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
char* command = curr->data;
|
||||||
char* command = (char*)key;
|
|
||||||
char* command_index = (char*)value;
|
|
||||||
int matches = 0;
|
int matches = 0;
|
||||||
for (int i = 0; i < terms_len; i++) {
|
for (int i = 0; i < terms_len; i++) {
|
||||||
|
char* command_index = g_hash_table_lookup(search_index, command);
|
||||||
if (g_str_match_string(terms[i], command_index, FALSE)) {
|
if (g_str_match_string(terms[i], command_index, FALSE)) {
|
||||||
matches++;
|
matches++;
|
||||||
}
|
}
|
||||||
@@ -2862,8 +2862,11 @@ cmd_search_index_all(char* term)
|
|||||||
if (matches == terms_len) {
|
if (matches == terms_len) {
|
||||||
results = g_list_append(results, command);
|
results = g_list_append(results, command);
|
||||||
}
|
}
|
||||||
|
curr = g_list_next(curr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_list_free(commands);
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2986,18 +2989,7 @@ command_docgen(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
FILE* toc_fragment = fopen("toc_fragment.html", "w");
|
FILE* toc_fragment = fopen("toc_fragment.html", "w");
|
||||||
if (!toc_fragment) {
|
|
||||||
log_error("command_docgen(): unable to open toc_fragment.html for writing: %s", g_strerror(errno));
|
|
||||||
g_list_free(cmds);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
FILE* main_fragment = fopen("main_fragment.html", "w");
|
FILE* main_fragment = fopen("main_fragment.html", "w");
|
||||||
if (!main_fragment) {
|
|
||||||
log_error("command_docgen(): unable to open main_fragment.html for writing: %s", g_strerror(errno));
|
|
||||||
fclose(toc_fragment);
|
|
||||||
g_list_free(cmds);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fputs("<ul><li><ul><li>\n", toc_fragment);
|
fputs("<ul><li><ul><li>\n", toc_fragment);
|
||||||
fputs("<hr>\n", main_fragment);
|
fputs("<hr>\n", main_fragment);
|
||||||
@@ -3102,11 +3094,6 @@ command_mangen(void)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
FILE* manpage = fopen(filename, "w");
|
FILE* manpage = fopen(filename, "w");
|
||||||
if (!manpage) {
|
|
||||||
log_error("command_mangen(): unable to open %s for writing: %s", filename, g_strerror(errno));
|
|
||||||
curr = g_list_next(curr);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(manpage, "%s\n", header);
|
fprintf(manpage, "%s\n", header);
|
||||||
fputs(".SH NAME\n", manpage);
|
fputs(".SH NAME\n", manpage);
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ _string_matches_one_of(const char* what, const char* is, bool is_can_be_null, co
|
|||||||
}
|
}
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
if (s > 0)
|
if (s > 0)
|
||||||
cons_show("%s", errmsg);
|
cons_show(errmsg);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -418,7 +418,7 @@ cmd_connect(ProfWin* window, const char* const command, gchar** args)
|
|||||||
auto_char char* err_msg = NULL;
|
auto_char char* err_msg = NULL;
|
||||||
gboolean res = strtoi_range(port_str, &port, 1, 65535, &err_msg);
|
gboolean res = strtoi_range(port_str, &port, 1, 65535, &err_msg);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
cons_show("");
|
cons_show("");
|
||||||
port = 0;
|
port = 0;
|
||||||
options_destroy(options);
|
options_destroy(options);
|
||||||
@@ -711,7 +711,7 @@ _account_set_port(char* account_name, char* port)
|
|||||||
auto_char char* err_msg = NULL;
|
auto_char char* err_msg = NULL;
|
||||||
gboolean res = strtoi_range(port, &porti, 1, 65535, &err_msg);
|
gboolean res = strtoi_range(port, &porti, 1, 65535, &err_msg);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
cons_show("");
|
cons_show("");
|
||||||
} else {
|
} else {
|
||||||
accounts_set_port(account_name, porti);
|
accounts_set_port(account_name, porti);
|
||||||
@@ -903,7 +903,7 @@ _account_set_max_sessions(char* account_name, char* max_sessions_raw)
|
|||||||
auto_char char* err_msg = NULL;
|
auto_char char* err_msg = NULL;
|
||||||
gboolean res = strtoi_range(max_sessions_raw, &max_sessions, 0, INT_MAX, &err_msg);
|
gboolean res = strtoi_range(max_sessions_raw, &max_sessions, 0, INT_MAX, &err_msg);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
cons_show("");
|
cons_show("");
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -924,7 +924,7 @@ _account_set_presence_priority(char* account_name, char* presence, char* priorit
|
|||||||
auto_char char* err_msg = NULL;
|
auto_char char* err_msg = NULL;
|
||||||
gboolean res = strtoi_range(priority, &intval, -128, 127, &err_msg);
|
gboolean res = strtoi_range(priority, &intval, -128, 127, &err_msg);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1580,7 +1580,7 @@ _cmd_list_commands(GList* commands)
|
|||||||
while (curr) {
|
while (curr) {
|
||||||
gchar* cmd = curr->data;
|
gchar* cmd = curr->data;
|
||||||
if (count == 5) {
|
if (count == 5) {
|
||||||
cons_show("%s", cmds->str);
|
cons_show(cmds->str);
|
||||||
g_string_free(cmds, TRUE);
|
g_string_free(cmds, TRUE);
|
||||||
cmds = g_string_new("");
|
cmds = g_string_new("");
|
||||||
count = 0;
|
count = 0;
|
||||||
@@ -1589,7 +1589,7 @@ _cmd_list_commands(GList* commands)
|
|||||||
curr = g_list_next(curr);
|
curr = g_list_next(curr);
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
cons_show("%s", cmds->str);
|
cons_show(cmds->str);
|
||||||
g_string_free(cmds, TRUE);
|
g_string_free(cmds, TRUE);
|
||||||
g_list_free(curr);
|
g_list_free(curr);
|
||||||
|
|
||||||
@@ -2407,7 +2407,7 @@ cmd_roster(ProfWin* window, const char* const command, gchar** args)
|
|||||||
}
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2467,7 +2467,7 @@ cmd_roster(ProfWin* window, const char* const command, gchar** args)
|
|||||||
cons_show("Roster contact indent set to: %d", intval);
|
cons_show("Roster contact indent set to: %d", intval);
|
||||||
rosterwin_roster();
|
rosterwin_roster();
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -2501,7 +2501,7 @@ cmd_roster(ProfWin* window, const char* const command, gchar** args)
|
|||||||
cons_show("Roster resource indent set to: %d", intval);
|
cons_show("Roster resource indent set to: %d", intval);
|
||||||
rosterwin_roster();
|
rosterwin_roster();
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (g_strcmp0(args[1], "join") == 0) {
|
} else if (g_strcmp0(args[1], "join") == 0) {
|
||||||
@@ -2527,7 +2527,7 @@ cmd_roster(ProfWin* window, const char* const command, gchar** args)
|
|||||||
cons_show("Roster presence indent set to: %d", intval);
|
cons_show("Roster presence indent set to: %d", intval);
|
||||||
rosterwin_roster();
|
rosterwin_roster();
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -4371,7 +4371,7 @@ cmd_occupants(ProfWin* window, const char* const command, gchar** args)
|
|||||||
wins_resize_all();
|
wins_resize_all();
|
||||||
return TRUE;
|
return TRUE;
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4391,7 +4391,7 @@ cmd_occupants(ProfWin* window, const char* const command, gchar** args)
|
|||||||
|
|
||||||
occupantswin_occupants_all();
|
occupantswin_occupants_all();
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
}
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -4970,8 +4970,8 @@ cmd_sendfile(ProfWin* window, const char* const command, gchar** args)
|
|||||||
alt_scheme = OMEMO_AESGCM_URL_SCHEME;
|
alt_scheme = OMEMO_AESGCM_URL_SCHEME;
|
||||||
alt_fragment = _add_omemo_stream(&fd, &fh, &err);
|
alt_fragment = _add_omemo_stream(&fd, &fh, &err);
|
||||||
if (err != NULL) {
|
if (err != NULL) {
|
||||||
cons_show_error("%s", err);
|
cons_show_error(err);
|
||||||
win_println(window, THEME_ERROR, "-", "%s", err);
|
win_println(window, THEME_ERROR, "-", err);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -5836,7 +5836,7 @@ cmd_inpblock(ProfWin* window, const char* const command, gchar** args)
|
|||||||
prefs_set_inpblock(intval);
|
prefs_set_inpblock(intval);
|
||||||
inp_nonblocking(FALSE);
|
inp_nonblocking(FALSE);
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@@ -6063,7 +6063,7 @@ cmd_statusbar(ProfWin* window, const char* const command, gchar** args)
|
|||||||
ui_resize();
|
ui_resize();
|
||||||
return TRUE;
|
return TRUE;
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
cons_bad_cmd_usage(command);
|
cons_bad_cmd_usage(command);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -6094,7 +6094,7 @@ cmd_statusbar(ProfWin* window, const char* const command, gchar** args)
|
|||||||
ui_resize();
|
ui_resize();
|
||||||
return TRUE;
|
return TRUE;
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
cons_bad_cmd_usage(command);
|
cons_bad_cmd_usage(command);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -6254,7 +6254,7 @@ cmd_log(ProfWin* window, const char* const command, gchar** args)
|
|||||||
prefs_set_max_log_size(intval);
|
prefs_set_max_log_size(intval);
|
||||||
cons_show("Log maximum size set to %d bytes", intval);
|
cons_show("Log maximum size set to %d bytes", intval);
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
}
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -6304,7 +6304,7 @@ cmd_reconnect(ProfWin* window, const char* const command, gchar** args)
|
|||||||
cons_show("Reconnect interval set to %d seconds.", intval);
|
cons_show("Reconnect interval set to %d seconds.", intval);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
cons_bad_cmd_usage(command);
|
cons_bad_cmd_usage(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6330,7 +6330,7 @@ cmd_autoping(ProfWin* window, const char* const command, gchar** args)
|
|||||||
cons_show("Autoping interval set to %d seconds.", intval);
|
cons_show("Autoping interval set to %d seconds.", intval);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
cons_bad_cmd_usage(command);
|
cons_bad_cmd_usage(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6346,7 +6346,7 @@ cmd_autoping(ProfWin* window, const char* const command, gchar** args)
|
|||||||
cons_show("Autoping timeout set to %d seconds.", intval);
|
cons_show("Autoping timeout set to %d seconds.", intval);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
cons_bad_cmd_usage(command);
|
cons_bad_cmd_usage(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6416,7 +6416,7 @@ cmd_autoaway(ProfWin* window, const char* const command, gchar** args)
|
|||||||
cons_show("Auto away time set to: %d minutes.", minutesval);
|
cons_show("Auto away time set to: %d minutes.", minutesval);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@@ -6439,7 +6439,7 @@ cmd_autoaway(ProfWin* window, const char* const command, gchar** args)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@@ -6506,7 +6506,7 @@ cmd_priority(ProfWin* window, const char* const command, gchar** args)
|
|||||||
cl_ev_presence_send(last_presence, 0);
|
cl_ev_presence_send(last_presence, 0);
|
||||||
cons_show("Priority set to %d.", intval);
|
cons_show("Priority set to %d.", intval);
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@@ -6576,7 +6576,7 @@ cmd_tray(ProfWin* window, const char* const command, gchar** args)
|
|||||||
tray_set_timer(intval);
|
tray_set_timer(intval);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
@@ -7181,21 +7181,22 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args)
|
|||||||
}
|
}
|
||||||
|
|
||||||
cons_show("PGP keys:");
|
cons_show("PGP keys:");
|
||||||
GHashTableIter iter;
|
GList* keylist = g_hash_table_get_keys(keys);
|
||||||
gpointer key, value;
|
GList* curr = keylist;
|
||||||
g_hash_table_iter_init(&iter, keys);
|
while (curr) {
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
ProfPGPKey* key = g_hash_table_lookup(keys, curr->data);
|
||||||
ProfPGPKey* pgp_key = (ProfPGPKey*)value;
|
cons_show(" %s", key->name);
|
||||||
cons_show(" %s", pgp_key->name);
|
cons_show(" ID : %s", key->id);
|
||||||
cons_show(" ID : %s", pgp_key->id);
|
auto_char char* format_fp = p_gpg_format_fp_str(key->fp);
|
||||||
auto_char char* format_fp = p_gpg_format_fp_str(pgp_key->fp);
|
|
||||||
cons_show(" Fingerprint : %s", format_fp);
|
cons_show(" Fingerprint : %s", format_fp);
|
||||||
if (pgp_key->secret) {
|
if (key->secret) {
|
||||||
cons_show(" Type : PUBLIC, PRIVATE");
|
cons_show(" Type : PUBLIC, PRIVATE");
|
||||||
} else {
|
} else {
|
||||||
cons_show(" Type : PUBLIC");
|
cons_show(" Type : PUBLIC");
|
||||||
}
|
}
|
||||||
|
curr = g_list_next(curr);
|
||||||
}
|
}
|
||||||
|
g_list_free(keylist);
|
||||||
p_gpg_free_keys(keys);
|
p_gpg_free_keys(keys);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -7236,24 +7237,25 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
GHashTable* pubkeys = p_gpg_pubkeys();
|
GHashTable* pubkeys = p_gpg_pubkeys();
|
||||||
if (!pubkeys || g_hash_table_size(pubkeys) == 0) {
|
GList* jids = g_hash_table_get_keys(pubkeys);
|
||||||
|
if (!jids) {
|
||||||
cons_show("No contacts found with PGP public keys assigned.");
|
cons_show("No contacts found with PGP public keys assigned.");
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
cons_show("Assigned PGP public keys:");
|
cons_show("Assigned PGP public keys:");
|
||||||
GHashTableIter iter;
|
GList* curr = jids;
|
||||||
gpointer key, value;
|
while (curr) {
|
||||||
g_hash_table_iter_init(&iter, pubkeys);
|
char* jid = curr->data;
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
ProfPGPPubKeyId* pubkeyid = g_hash_table_lookup(pubkeys, jid);
|
||||||
char* jid = (char*)key;
|
|
||||||
ProfPGPPubKeyId* pubkeyid = (ProfPGPPubKeyId*)value;
|
|
||||||
if (pubkeyid->received) {
|
if (pubkeyid->received) {
|
||||||
cons_show(" %s: %s (received)", jid, pubkeyid->id);
|
cons_show(" %s: %s (received)", jid, pubkeyid->id);
|
||||||
} else {
|
} else {
|
||||||
cons_show(" %s: %s (stored)", jid, pubkeyid->id);
|
cons_show(" %s: %s (stored)", jid, pubkeyid->id);
|
||||||
}
|
}
|
||||||
|
curr = g_list_next(curr);
|
||||||
}
|
}
|
||||||
|
g_list_free(jids);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7466,21 +7468,22 @@ cmd_ox(ProfWin* window, const char* const command, gchar** args)
|
|||||||
}
|
}
|
||||||
|
|
||||||
cons_show("OpenPGP keys:");
|
cons_show("OpenPGP keys:");
|
||||||
GHashTableIter iter;
|
GList* keylist = g_hash_table_get_keys(keys);
|
||||||
gpointer key, value;
|
GList* curr = keylist;
|
||||||
g_hash_table_iter_init(&iter, keys);
|
while (curr) {
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
ProfPGPKey* key = g_hash_table_lookup(keys, curr->data);
|
||||||
ProfPGPKey* pgp_key = (ProfPGPKey*)value;
|
cons_show(" %s", key->name);
|
||||||
cons_show(" %s", pgp_key->name);
|
cons_show(" ID : %s", key->id);
|
||||||
cons_show(" ID : %s", pgp_key->id);
|
auto_char char* format_fp = p_gpg_format_fp_str(key->fp);
|
||||||
auto_char char* format_fp = p_gpg_format_fp_str(pgp_key->fp);
|
|
||||||
cons_show(" Fingerprint : %s", format_fp);
|
cons_show(" Fingerprint : %s", format_fp);
|
||||||
if (pgp_key->secret) {
|
if (key->secret) {
|
||||||
cons_show(" Type : PUBLIC, PRIVATE");
|
cons_show(" Type : PUBLIC, PRIVATE");
|
||||||
} else {
|
} else {
|
||||||
cons_show(" Type : PUBLIC");
|
cons_show(" Type : PUBLIC");
|
||||||
}
|
}
|
||||||
|
curr = g_list_next(curr);
|
||||||
}
|
}
|
||||||
|
g_list_free(keylist);
|
||||||
p_gpg_free_keys(keys);
|
p_gpg_free_keys(keys);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -7488,8 +7491,8 @@ cmd_ox(ProfWin* window, const char* const command, gchar** args)
|
|||||||
else if (g_strcmp0(args[0], "contacts") == 0) {
|
else if (g_strcmp0(args[0], "contacts") == 0) {
|
||||||
GHashTable* keys = ox_gpg_public_keys();
|
GHashTable* keys = ox_gpg_public_keys();
|
||||||
cons_show("OpenPGP keys:");
|
cons_show("OpenPGP keys:");
|
||||||
GHashTableIter iter;
|
GList* keylist = g_hash_table_get_keys(keys);
|
||||||
gpointer key, value;
|
GList* curr = keylist;
|
||||||
|
|
||||||
GSList* roster_list = NULL;
|
GSList* roster_list = NULL;
|
||||||
jabber_conn_status_t conn_status = connection_get_status();
|
jabber_conn_status_t conn_status = connection_get_status();
|
||||||
@@ -7499,16 +7502,15 @@ cmd_ox(ProfWin* window, const char* const command, gchar** args)
|
|||||||
roster_list = roster_get_contacts(ROSTER_ORD_NAME);
|
roster_list = roster_get_contacts(ROSTER_ORD_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_hash_table_iter_init(&iter, keys);
|
while (curr) {
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
ProfPGPKey* key = g_hash_table_lookup(keys, curr->data);
|
||||||
ProfPGPKey* pgp_key = (ProfPGPKey*)value;
|
|
||||||
PContact contact = NULL;
|
PContact contact = NULL;
|
||||||
if (roster_list) {
|
if (roster_list) {
|
||||||
GSList* curr_c = roster_list;
|
GSList* curr_c = roster_list;
|
||||||
while (!contact && curr_c) {
|
while (!contact && curr_c) {
|
||||||
contact = curr_c->data;
|
contact = curr_c->data;
|
||||||
auto_gchar gchar* xmppuri = g_strdup_printf("xmpp:%s", p_contact_barejid(contact));
|
auto_gchar gchar* xmppuri = g_strdup_printf("xmpp:%s", p_contact_barejid(contact));
|
||||||
if (g_strcmp0(pgp_key->name, xmppuri)) {
|
if (g_strcmp0(key->name, xmppuri)) {
|
||||||
contact = NULL;
|
contact = NULL;
|
||||||
}
|
}
|
||||||
curr_c = g_slist_next(curr_c);
|
curr_c = g_slist_next(curr_c);
|
||||||
@@ -7516,10 +7518,11 @@ cmd_ox(ProfWin* window, const char* const command, gchar** args)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (contact) {
|
if (contact) {
|
||||||
cons_show("%s - %s", pgp_key->fp, pgp_key->name);
|
cons_show("%s - %s", key->fp, key->name);
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s - %s (not in roster)", pgp_key->fp, pgp_key->name);
|
cons_show("%s - %s (not in roster)", key->fp, key->name);
|
||||||
}
|
}
|
||||||
|
curr = g_list_next(curr);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (g_strcmp0(args[0], "start") == 0) {
|
} else if (g_strcmp0(args[0], "start") == 0) {
|
||||||
@@ -9609,7 +9612,7 @@ cmd_register(ProfWin* window, const char* const command, gchar** args)
|
|||||||
auto_char char* err_msg = NULL;
|
auto_char char* err_msg = NULL;
|
||||||
gboolean res = strtoi_range(port_str, &port, 1, 65535, &err_msg);
|
gboolean res = strtoi_range(port_str, &port, 1, 65535, &err_msg);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
cons_show("");
|
cons_show("");
|
||||||
port = 0;
|
port = 0;
|
||||||
options_destroy(options);
|
options_destroy(options);
|
||||||
@@ -9679,7 +9682,7 @@ cmd_strophe(ProfWin* window, const char* const command, gchar** args)
|
|||||||
prefs_set_string(PREF_STROPHE_VERBOSITY, args[1]);
|
prefs_set_string(PREF_STROPHE_VERBOSITY, args[1]);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
}
|
}
|
||||||
} else if (g_strcmp0(args[0], "sm") == 0) {
|
} else if (g_strcmp0(args[0], "sm") == 0) {
|
||||||
if (g_strcmp0(args[1], "no-resend") == 0) {
|
if (g_strcmp0(args[1], "no-resend") == 0) {
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ auto_close_gfd(gint* fd)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (close(*fd) == EOF)
|
if (close(*fd) == EOF)
|
||||||
log_error("%s", g_strerror(errno));
|
log_error(g_strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -158,7 +158,7 @@ auto_close_FILE(FILE** fd)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (fclose(*fd) == EOF)
|
if (fclose(*fd) == EOF)
|
||||||
log_error("%s", g_strerror(errno));
|
log_error(g_strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
|
|||||||
@@ -65,38 +65,6 @@ static gboolean _check_available_space_for_db_migration(char* path_to_db);
|
|||||||
|
|
||||||
static const int latest_version = 2;
|
static const int latest_version = 2;
|
||||||
|
|
||||||
// Helper: close DB handle (if any), warn on busy, and shutdown SQLite
|
|
||||||
static void
|
|
||||||
_db_teardown(const char* ctx)
|
|
||||||
{
|
|
||||||
if (g_chatlog_database) {
|
|
||||||
int rc = sqlite3_close_v2(g_chatlog_database);
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
log_warning("sqlite3_close_v2 in %s returned %d; database may still have active statements.",
|
|
||||||
ctx ? ctx : "db_teardown", rc);
|
|
||||||
}
|
|
||||||
g_chatlog_database = NULL;
|
|
||||||
}
|
|
||||||
// Safe to call unconditionally; no-op if not initialized.
|
|
||||||
// See: https://www.sqlite.org/c3ref/initialize.html
|
|
||||||
sqlite3_shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: prepare a statement and log a contextual error on failure
|
|
||||||
static gboolean
|
|
||||||
_db_prepare_ctx(const char* query, sqlite3_stmt** stmt, const char* ctx)
|
|
||||||
{
|
|
||||||
int rc = sqlite3_prepare_v2(g_chatlog_database, query, -1, stmt, NULL);
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
log_error("SQLite error in %s: (error code: %d) %s",
|
|
||||||
ctx ? ctx : "sqlite3_prepare_v2",
|
|
||||||
rc,
|
|
||||||
sqlite3_errmsg(g_chatlog_database));
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char*
|
static char*
|
||||||
_db_strdup(const char* str)
|
_db_strdup(const char* str)
|
||||||
{
|
{
|
||||||
@@ -130,19 +98,17 @@ log_database_init(ProfAccount* account)
|
|||||||
|
|
||||||
auto_char char* filename = _get_db_filename(account);
|
auto_char char* filename = _get_db_filename(account);
|
||||||
if (!filename) {
|
if (!filename) {
|
||||||
sqlite3_shutdown();
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = sqlite3_open(filename, &g_chatlog_database);
|
ret = sqlite3_open(filename, &g_chatlog_database);
|
||||||
if (ret != SQLITE_OK) {
|
if (ret != SQLITE_OK) {
|
||||||
const char* err_msg = g_chatlog_database ? sqlite3_errmsg(g_chatlog_database) : "(no handle)";
|
const char* err_msg = sqlite3_errmsg(g_chatlog_database);
|
||||||
log_error("Error opening SQLite database: %s", err_msg);
|
log_error("Error opening SQLite database: %s", err_msg);
|
||||||
_db_teardown("log_database_init(open)");
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* err_msg = NULL;
|
char* err_msg;
|
||||||
|
|
||||||
int db_version = _get_db_version();
|
int db_version = _get_db_version();
|
||||||
if (db_version == latest_version) {
|
if (db_version == latest_version) {
|
||||||
@@ -250,7 +216,6 @@ out:
|
|||||||
} else {
|
} else {
|
||||||
log_error("Unknown SQLite error in log_database_init().");
|
log_error("Unknown SQLite error in log_database_init().");
|
||||||
}
|
}
|
||||||
_db_teardown("log_database_init(out)");
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +223,11 @@ void
|
|||||||
log_database_close(void)
|
log_database_close(void)
|
||||||
{
|
{
|
||||||
log_debug("log_database_close() called");
|
log_debug("log_database_close() called");
|
||||||
_db_teardown("log_database_close");
|
if (g_chatlog_database) {
|
||||||
|
sqlite3_close(g_chatlog_database);
|
||||||
|
sqlite3_shutdown();
|
||||||
|
g_chatlog_database = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -312,15 +281,8 @@ log_database_get_limits_info(const gchar* const contact_barejid, gboolean is_las
|
|||||||
{
|
{
|
||||||
sqlite3_stmt* stmt = NULL;
|
sqlite3_stmt* stmt = NULL;
|
||||||
const Jid* myjid = connection_get_jid();
|
const Jid* myjid = connection_get_jid();
|
||||||
// Always return a valid ProfMessage to avoid NULL dereferences in callers
|
if (!myjid->str)
|
||||||
ProfMessage* msg = message_init();
|
return NULL;
|
||||||
if (!myjid || !myjid->str) {
|
|
||||||
// If caller requested the last message and we have no context, fall back to now
|
|
||||||
if (is_last) {
|
|
||||||
msg->timestamp = g_date_time_new_now_utc();
|
|
||||||
}
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* order = is_last ? "DESC" : "ASC";
|
const char* order = is_last ? "DESC" : "ASC";
|
||||||
auto_sqlite char* query = sqlite3_mprintf("SELECT `archive_id`, `timestamp` FROM `ChatLogs` WHERE "
|
auto_sqlite char* query = sqlite3_mprintf("SELECT `archive_id`, `timestamp` FROM `ChatLogs` WHERE "
|
||||||
@@ -331,19 +293,17 @@ log_database_get_limits_info(const gchar* const contact_barejid, gboolean is_las
|
|||||||
|
|
||||||
if (!query) {
|
if (!query) {
|
||||||
log_error("Could not allocate memory for SQL query in log_database_get_limits_info()");
|
log_error("Could not allocate memory for SQL query in log_database_get_limits_info()");
|
||||||
if (is_last) {
|
return NULL;
|
||||||
msg->timestamp = g_date_time_new_now_utc();
|
|
||||||
}
|
|
||||||
return msg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_db_prepare_ctx(query, &stmt, "log_database_get_limits_info()")) {
|
int rc = sqlite3_prepare_v2(g_chatlog_database, query, -1, &stmt, NULL);
|
||||||
if (is_last) {
|
if (rc != SQLITE_OK) {
|
||||||
msg->timestamp = g_date_time_new_now_utc();
|
log_error("Unknown SQLite error in log_database_get_last_info().");
|
||||||
}
|
return NULL;
|
||||||
return msg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProfMessage* msg = message_init();
|
||||||
|
|
||||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
char* archive_id = (char*)sqlite3_column_text(stmt, 0);
|
char* archive_id = (char*)sqlite3_column_text(stmt, 0);
|
||||||
char* date = (char*)sqlite3_column_text(stmt, 1);
|
char* date = (char*)sqlite3_column_text(stmt, 1);
|
||||||
@@ -353,11 +313,6 @@ log_database_get_limits_info(const gchar* const contact_barejid, gboolean is_las
|
|||||||
}
|
}
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
// If nothing was found and caller expects the last message, provide a sane default
|
|
||||||
if (!msg->timestamp && is_last) {
|
|
||||||
msg->timestamp = g_date_time_new_now_utc();
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,7 +357,9 @@ log_database_get_previous_chat(const gchar* const contact_barejid, const gchar*
|
|||||||
return DB_RESPONSE_ERROR;
|
return DB_RESPONSE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_db_prepare_ctx(query, &stmt, "log_database_get_previous_chat()")) {
|
int rc = sqlite3_prepare_v2(g_chatlog_database, query, -1, &stmt, NULL);
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
log_error("SQLite error in log_database_get_previous_chat(): (error code: %d) %s", rc, sqlite3_errmsg(g_chatlog_database));
|
||||||
return DB_RESPONSE_ERROR;
|
return DB_RESPONSE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,7 +503,9 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji
|
|||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_stmt* lmc_stmt = NULL;
|
sqlite3_stmt* lmc_stmt = NULL;
|
||||||
if (!_db_prepare_ctx(replace_check_query, &lmc_stmt, "_add_to_db(replace_check)")) {
|
|
||||||
|
if (SQLITE_OK != sqlite3_prepare_v2(g_chatlog_database, replace_check_query, -1, &lmc_stmt, NULL)) {
|
||||||
|
log_error("SQLite error in _add_to_db() on selecting original message: %s", sqlite3_errmsg(g_chatlog_database));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,7 +542,8 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji
|
|||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_stmt* stmt;
|
sqlite3_stmt* stmt;
|
||||||
if (_db_prepare_ctx(duplicate_check_query, &stmt, "_add_to_db(duplicate_check)")) {
|
|
||||||
|
if (SQLITE_OK == sqlite3_prepare_v2(g_chatlog_database, duplicate_check_query, -1, &stmt, NULL)) {
|
||||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
log_error("Duplicate stanza-id found for the message. stanza_id: %s; archive_id: %s; sender: %s; content: %s", message->id, message->stanzaid, from_jid->barejid, message->plain);
|
log_error("Duplicate stanza-id found for the message. stanza_id: %s; archive_id: %s; sender: %s; content: %s", message->id, message->stanzaid, from_jid->barejid, message->plain);
|
||||||
cons_show_error("Got a message with duplicate (server-generated) stanza-id from %s.", from_jid->fulljid);
|
cons_show_error("Got a message with duplicate (server-generated) stanza-id from %s.", from_jid->fulljid);
|
||||||
@@ -639,7 +599,8 @@ _get_db_version(void)
|
|||||||
int current_version = -1;
|
int current_version = -1;
|
||||||
const char* query = "SELECT `version` FROM `DbVersion` LIMIT 1";
|
const char* query = "SELECT `version` FROM `DbVersion` LIMIT 1";
|
||||||
sqlite3_stmt* statement;
|
sqlite3_stmt* statement;
|
||||||
if (_db_prepare_ctx(query, &statement, "_get_db_version()")) {
|
|
||||||
|
if (sqlite3_prepare_v2(g_chatlog_database, query, -1, &statement, NULL) == SQLITE_OK) {
|
||||||
if (sqlite3_step(statement) == SQLITE_ROW) {
|
if (sqlite3_step(statement) == SQLITE_ROW) {
|
||||||
current_version = sqlite3_column_int(statement, 0);
|
current_version = sqlite3_column_int(statement, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -532,13 +532,11 @@ omemo_set_device_list(const char* const from, GList* device_list)
|
|||||||
for (device_id = device_list; device_id != NULL; device_id = device_id->next) {
|
for (device_id = device_list; device_id != NULL; device_id = device_id->next) {
|
||||||
GHashTable* known_identities = g_hash_table_lookup(omemo_ctx.known_devices, jid->barejid);
|
GHashTable* known_identities = g_hash_table_lookup(omemo_ctx.known_devices, jid->barejid);
|
||||||
if (known_identities) {
|
if (known_identities) {
|
||||||
GHashTableIter iter;
|
GList* fp = NULL;
|
||||||
gpointer key, value;
|
for (fp = g_hash_table_get_keys(known_identities); fp != NULL; fp = fp->next) {
|
||||||
g_hash_table_iter_init(&iter, known_identities);
|
if (device_id->data == g_hash_table_lookup(known_identities, fp->data)) {
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
cons_show("OMEMO: Adding firstusage trust for %s device %d - Fingerprint %s", jid->barejid, device_id->data, omemo_format_fingerprint(fp->data));
|
||||||
if (device_id->data == value) {
|
omemo_trust(jid->barejid, omemo_format_fingerprint(fp->data));
|
||||||
cons_show("OMEMO: Adding firstusage trust for %s device %d - Fingerprint %s", jid->barejid, device_id->data, omemo_format_fingerprint(key));
|
|
||||||
omemo_trust(jid->barejid, omemo_format_fingerprint(key));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -345,13 +345,14 @@ p_gpg_list_keys(void)
|
|||||||
|
|
||||||
// TODO: move autocomplete in other place
|
// TODO: move autocomplete in other place
|
||||||
autocomplete_clear(key_ac);
|
autocomplete_clear(key_ac);
|
||||||
GHashTableIter iter;
|
GList* ids = g_hash_table_get_keys(result);
|
||||||
gpointer key, value;
|
GList* curr = ids;
|
||||||
g_hash_table_iter_init(&iter, result);
|
while (curr) {
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
ProfPGPKey* key = g_hash_table_lookup(result, curr->data);
|
||||||
ProfPGPKey* pgp_key = (ProfPGPKey*)value;
|
autocomplete_add(key_ac, key->id);
|
||||||
autocomplete_add(key_ac, pgp_key->id);
|
curr = curr->next;
|
||||||
}
|
}
|
||||||
|
g_list_free(ids);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,16 +141,18 @@ autocompleters_complete(const char* const input, gboolean previous)
|
|||||||
while (curr_hash) {
|
while (curr_hash) {
|
||||||
GHashTable* key_to_ac = curr_hash->data;
|
GHashTable* key_to_ac = curr_hash->data;
|
||||||
|
|
||||||
GHashTableIter iter;
|
GList* keys = g_hash_table_get_keys(key_to_ac);
|
||||||
gpointer key, value;
|
GList* curr = keys;
|
||||||
g_hash_table_iter_init(&iter, key_to_ac);
|
while (curr) {
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
result = autocomplete_param_with_ac(input, curr->data, g_hash_table_lookup(key_to_ac, curr->data), TRUE, previous);
|
||||||
result = autocomplete_param_with_ac(input, key, value, TRUE, previous);
|
|
||||||
if (result) {
|
if (result) {
|
||||||
g_list_free(ac_hashes);
|
g_list_free(ac_hashes);
|
||||||
|
g_list_free(keys);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
curr = g_list_next(curr);
|
||||||
}
|
}
|
||||||
|
g_list_free(keys);
|
||||||
|
|
||||||
curr_hash = g_list_next(curr_hash);
|
curr_hash = g_list_next(curr_hash);
|
||||||
}
|
}
|
||||||
@@ -160,19 +162,22 @@ autocompleters_complete(const char* const input, gboolean previous)
|
|||||||
curr_hash = filepath_hashes;
|
curr_hash = filepath_hashes;
|
||||||
while (curr_hash) {
|
while (curr_hash) {
|
||||||
GHashTable* prefixes_hash = curr_hash->data;
|
GHashTable* prefixes_hash = curr_hash->data;
|
||||||
GHashTableIter iter;
|
GList* prefixes = g_hash_table_get_keys(prefixes_hash);
|
||||||
gpointer key, value;
|
GList* curr_prefix = prefixes;
|
||||||
g_hash_table_iter_init(&iter, prefixes_hash);
|
while (curr_prefix) {
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
char* prefix = curr_prefix->data;
|
||||||
char* prefix = (char*)key;
|
|
||||||
if (g_str_has_prefix(input, prefix)) {
|
if (g_str_has_prefix(input, prefix)) {
|
||||||
result = cmd_ac_complete_filepath(input, prefix, previous);
|
result = cmd_ac_complete_filepath(input, prefix, previous);
|
||||||
if (result) {
|
if (result) {
|
||||||
g_list_free(filepath_hashes);
|
g_list_free(filepath_hashes);
|
||||||
|
g_list_free(prefixes);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
curr_prefix = g_list_next(curr_prefix);
|
||||||
}
|
}
|
||||||
|
g_list_free(prefixes);
|
||||||
|
|
||||||
curr_hash = g_list_next(curr_hash);
|
curr_hash = g_list_next(curr_hash);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,14 +149,15 @@ callbacks_remove(const char* const plugin_name)
|
|||||||
{
|
{
|
||||||
GHashTable* command_hash = g_hash_table_lookup(p_commands, plugin_name);
|
GHashTable* command_hash = g_hash_table_lookup(p_commands, plugin_name);
|
||||||
if (command_hash) {
|
if (command_hash) {
|
||||||
GHashTableIter iter;
|
GList* commands = g_hash_table_get_keys(command_hash);
|
||||||
gpointer key, value;
|
GList* curr = commands;
|
||||||
g_hash_table_iter_init(&iter, command_hash);
|
while (curr) {
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
char* command = curr->data;
|
||||||
char* command = (char*)key;
|
|
||||||
cmd_ac_remove(command);
|
cmd_ac_remove(command);
|
||||||
cmd_ac_remove_help(&command[1]);
|
cmd_ac_remove_help(&command[1]);
|
||||||
|
curr = g_list_next(curr);
|
||||||
}
|
}
|
||||||
|
g_list_free(commands);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_hash_table_remove(p_commands, plugin_name);
|
g_hash_table_remove(p_commands, plugin_name);
|
||||||
@@ -164,12 +165,13 @@ callbacks_remove(const char* const plugin_name)
|
|||||||
|
|
||||||
GHashTable* tag_to_win_cb_hash = g_hash_table_lookup(p_window_callbacks, plugin_name);
|
GHashTable* tag_to_win_cb_hash = g_hash_table_lookup(p_window_callbacks, plugin_name);
|
||||||
if (tag_to_win_cb_hash) {
|
if (tag_to_win_cb_hash) {
|
||||||
GHashTableIter iter;
|
GList* tags = g_hash_table_get_keys(tag_to_win_cb_hash);
|
||||||
gpointer key, value;
|
GList* curr = tags;
|
||||||
g_hash_table_iter_init(&iter, tag_to_win_cb_hash);
|
while (curr) {
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
wins_close_plugin(curr->data);
|
||||||
wins_close_plugin(key);
|
curr = g_list_next(curr);
|
||||||
}
|
}
|
||||||
|
g_list_free(tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_hash_table_remove(p_window_callbacks, plugin_name);
|
g_hash_table_remove(p_window_callbacks, plugin_name);
|
||||||
|
|||||||
@@ -107,11 +107,10 @@ disco_remove_features(const char* plugin_name)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GHashTableIter iter;
|
GList* plugin_feature_list = g_hash_table_get_keys(plugin_features_set);
|
||||||
gpointer key, value;
|
GList* curr = plugin_feature_list;
|
||||||
g_hash_table_iter_init(&iter, plugin_features_set);
|
while (curr) {
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
char* feature = curr->data;
|
||||||
char* feature = (char*)key;
|
|
||||||
if (g_hash_table_contains(features, feature)) {
|
if (g_hash_table_contains(features, feature)) {
|
||||||
void* refcountp = g_hash_table_lookup(features, feature);
|
void* refcountp = g_hash_table_lookup(features, feature);
|
||||||
int refcount = GPOINTER_TO_INT(refcountp);
|
int refcount = GPOINTER_TO_INT(refcountp);
|
||||||
@@ -122,7 +121,10 @@ disco_remove_features(const char* plugin_name)
|
|||||||
g_hash_table_replace(features, strdup(feature), GINT_TO_POINTER(refcount));
|
g_hash_table_replace(features, strdup(feature), GINT_TO_POINTER(refcount));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
curr = g_list_next(curr);
|
||||||
}
|
}
|
||||||
|
g_list_free(plugin_feature_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
GList*
|
GList*
|
||||||
|
|||||||
@@ -887,8 +887,8 @@ _python_undefined_error(ProfPlugin* plugin, char* hook, char* type)
|
|||||||
g_string_append(err_msg, hook);
|
g_string_append(err_msg, hook);
|
||||||
g_string_append(err_msg, "(): return value undefined, expected ");
|
g_string_append(err_msg, "(): return value undefined, expected ");
|
||||||
g_string_append(err_msg, type);
|
g_string_append(err_msg, type);
|
||||||
log_error("%s", err_msg->str);
|
log_error(err_msg->str);
|
||||||
cons_show_error("%s", err_msg->str);
|
cons_show_error(err_msg->str);
|
||||||
g_string_free(err_msg, TRUE);
|
g_string_free(err_msg, TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -901,8 +901,8 @@ _python_type_error(ProfPlugin* plugin, char* hook, char* type)
|
|||||||
g_string_append(err_msg, hook);
|
g_string_append(err_msg, hook);
|
||||||
g_string_append(err_msg, "(): incorrect return type, expected ");
|
g_string_append(err_msg, "(): incorrect return type, expected ");
|
||||||
g_string_append(err_msg, type);
|
g_string_append(err_msg, type);
|
||||||
log_error("%s", err_msg->str);
|
log_error(err_msg->str);
|
||||||
cons_show_error("%s", err_msg->str);
|
cons_show_error(err_msg->str);
|
||||||
g_string_free(err_msg, TRUE);
|
g_string_free(err_msg, TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ prof_run(gchar* log_level, gchar* account_name, gchar* config_file, gchar* log_f
|
|||||||
*/
|
*/
|
||||||
min_runtime += waittime;
|
min_runtime += waittime;
|
||||||
} else {
|
} else {
|
||||||
log_error("%s", err_msg);
|
log_error(err_msg);
|
||||||
g_free(err_msg);
|
g_free(err_msg);
|
||||||
commands = NULL;
|
commands = NULL;
|
||||||
}
|
}
|
||||||
@@ -245,7 +245,7 @@ _init(char* log_level, char* config_file, char* log_file, char* theme_name)
|
|||||||
if (prof_log_level == PROF_LEVEL_DEBUG) {
|
if (prof_log_level == PROF_LEVEL_DEBUG) {
|
||||||
ProfWin* console = wins_get_console();
|
ProfWin* console = wins_get_console();
|
||||||
win_println(console, THEME_DEFAULT, "-", "Debug mode enabled! Logging to: ");
|
win_println(console, THEME_DEFAULT, "-", "Debug mode enabled! Logging to: ");
|
||||||
win_println(console, THEME_DEFAULT, "-", "%s", get_log_file_location());
|
win_println(console, THEME_DEFAULT, "-", get_log_file_location());
|
||||||
}
|
}
|
||||||
session_init();
|
session_init();
|
||||||
cmd_init();
|
cmd_init();
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ http_file_put(void* userdata)
|
|||||||
}
|
}
|
||||||
win_update_entry_message(upload->window, upload->put_url, err_msg);
|
win_update_entry_message(upload->window, upload->put_url, err_msg);
|
||||||
}
|
}
|
||||||
cons_show_error("%s", err_msg);
|
cons_show_error(err_msg);
|
||||||
} else {
|
} else {
|
||||||
if (!upload->cancel) {
|
if (!upload->cancel) {
|
||||||
auto_gchar gchar* status_msg = g_strdup_printf("Uploading '%s': 100%%", upload->filename);
|
auto_gchar gchar* status_msg = g_strdup_printf("Uploading '%s': 100%%", upload->filename);
|
||||||
@@ -327,7 +327,7 @@ http_file_put(void* userdata)
|
|||||||
if (!fail_msg) {
|
if (!fail_msg) {
|
||||||
fail_msg = g_strdup(FALLBACK_MSG);
|
fail_msg = g_strdup(FALLBACK_MSG);
|
||||||
}
|
}
|
||||||
cons_show_error("%s", fail_msg);
|
cons_show_error(fail_msg);
|
||||||
} else {
|
} else {
|
||||||
switch (upload->window->type) {
|
switch (upload->window->type) {
|
||||||
case WIN_CHAT:
|
case WIN_CHAT:
|
||||||
|
|||||||
@@ -140,10 +140,6 @@ void
|
|||||||
buffer_remove_entry(ProfBuff buffer, int entry)
|
buffer_remove_entry(ProfBuff buffer, int entry)
|
||||||
{
|
{
|
||||||
GSList* node = g_slist_nth(buffer->entries, entry);
|
GSList* node = g_slist_nth(buffer->entries, entry);
|
||||||
if (node == NULL) {
|
|
||||||
// Index out of range; nothing to remove
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ProfBuffEntry* e = node->data;
|
ProfBuffEntry* e = node->data;
|
||||||
buffer->lines -= e->_lines;
|
buffer->lines -= e->_lines;
|
||||||
_free_entry(e);
|
_free_entry(e);
|
||||||
@@ -172,9 +168,6 @@ ProfBuffEntry*
|
|||||||
buffer_get_entry(ProfBuff buffer, int entry)
|
buffer_get_entry(ProfBuff buffer, int entry)
|
||||||
{
|
{
|
||||||
GSList* node = g_slist_nth(buffer->entries, entry);
|
GSList* node = g_slist_nth(buffer->entries, entry);
|
||||||
if (node == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return node->data;
|
return node->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +189,6 @@ buffer_get_entry_by_id(ProfBuff buffer, const char* const id)
|
|||||||
static ProfBuffEntry*
|
static ProfBuffEntry*
|
||||||
_create_entry(const char* show_char, int pad_indent, GDateTime* time, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message, DeliveryReceipt* receipt, const char* const id, int y_start_pos, int y_end_pos)
|
_create_entry(const char* show_char, int pad_indent, GDateTime* time, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message, DeliveryReceipt* receipt, const char* const id, int y_start_pos, int y_end_pos)
|
||||||
{
|
{
|
||||||
assert(time != NULL);
|
|
||||||
ProfBuffEntry* e = malloc(sizeof(struct prof_buff_entry_t));
|
ProfBuffEntry* e = malloc(sizeof(struct prof_buff_entry_t));
|
||||||
e->show_char = STRDUP_OR_NULL(show_char);
|
e->show_char = STRDUP_OR_NULL(show_char);
|
||||||
e->pad_indent = pad_indent;
|
e->pad_indent = pad_indent;
|
||||||
|
|||||||
@@ -592,11 +592,8 @@ chatwin_db_history(ProfChatWin* chatwin, const gchar* start_time, const gchar* e
|
|||||||
{
|
{
|
||||||
auto_gchar gchar* _end_time = NULL;
|
auto_gchar gchar* _end_time = NULL;
|
||||||
if (!end_time && buffer_size(((ProfWin*)chatwin)->layout->buffer) > 0) {
|
if (!end_time && buffer_size(((ProfWin*)chatwin)->layout->buffer) > 0) {
|
||||||
ProfBuffEntry* first = buffer_get_entry(((ProfWin*)chatwin)->layout->buffer, 0);
|
_end_time = g_date_time_format_iso8601(buffer_get_entry(((ProfWin*)chatwin)->layout->buffer, 0)->time);
|
||||||
if (first && first->time) {
|
end_time = _end_time;
|
||||||
_end_time = g_date_time_format_iso8601(first->time);
|
|
||||||
end_time = _end_time;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GSList* history = NULL;
|
GSList* history = NULL;
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ cons_bad_cmd_usage(const char* const cmd)
|
|||||||
g_string_printf(msg, "Invalid usage, see '/help %s' for details.", &cmd[1]);
|
g_string_printf(msg, "Invalid usage, see '/help %s' for details.", &cmd[1]);
|
||||||
|
|
||||||
cons_show("");
|
cons_show("");
|
||||||
cons_show("%s", msg->str);
|
cons_show(msg->str);
|
||||||
|
|
||||||
g_string_free(msg, TRUE);
|
g_string_free(msg, TRUE);
|
||||||
}
|
}
|
||||||
@@ -773,7 +773,7 @@ cons_show_disco_info(const char* jid, GSList* identities, GSList* features)
|
|||||||
if (identity->category) {
|
if (identity->category) {
|
||||||
identity_str = g_string_append(identity_str, identity->category);
|
identity_str = g_string_append(identity_str, identity->category);
|
||||||
}
|
}
|
||||||
cons_show("%s", identity_str->str);
|
cons_show(identity_str->str);
|
||||||
g_string_free(identity_str, TRUE);
|
g_string_free(identity_str, TRUE);
|
||||||
identities = g_slist_next(identities);
|
identities = g_slist_next(identities);
|
||||||
}
|
}
|
||||||
@@ -938,7 +938,7 @@ cons_show_account_list(gchar** accounts)
|
|||||||
theme_item_t presence_colour = theme_main_presence_attrs(string_from_resource_presence(presence));
|
theme_item_t presence_colour = theme_main_presence_attrs(string_from_resource_presence(presence));
|
||||||
win_println(console, presence_colour, "-", "%s", accounts[i]);
|
win_println(console, presence_colour, "-", "%s", accounts[i]);
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", accounts[i]);
|
cons_show(accounts[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cons_show("");
|
cons_show("");
|
||||||
@@ -1019,7 +1019,7 @@ cons_show_account(ProfAccount* account)
|
|||||||
}
|
}
|
||||||
curr = curr->next;
|
curr = curr->next;
|
||||||
}
|
}
|
||||||
cons_show("%s", manual->str);
|
cons_show(manual->str);
|
||||||
g_string_free(manual, TRUE);
|
g_string_free(manual, TRUE);
|
||||||
}
|
}
|
||||||
if (g_list_length(account->otr_opportunistic) > 0) {
|
if (g_list_length(account->otr_opportunistic) > 0) {
|
||||||
@@ -1032,7 +1032,7 @@ cons_show_account(ProfAccount* account)
|
|||||||
}
|
}
|
||||||
curr = curr->next;
|
curr = curr->next;
|
||||||
}
|
}
|
||||||
cons_show("%s", opportunistic->str);
|
cons_show(opportunistic->str);
|
||||||
g_string_free(opportunistic, TRUE);
|
g_string_free(opportunistic, TRUE);
|
||||||
}
|
}
|
||||||
if (g_list_length(account->otr_always) > 0) {
|
if (g_list_length(account->otr_always) > 0) {
|
||||||
@@ -1045,7 +1045,7 @@ cons_show_account(ProfAccount* account)
|
|||||||
}
|
}
|
||||||
curr = curr->next;
|
curr = curr->next;
|
||||||
}
|
}
|
||||||
cons_show("%s", always->str);
|
cons_show(always->str);
|
||||||
g_string_free(always, TRUE);
|
g_string_free(always, TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2297,7 +2297,7 @@ cons_show_themes(GSList* themes)
|
|||||||
} else {
|
} else {
|
||||||
cons_show("Available themes:");
|
cons_show("Available themes:");
|
||||||
while (themes) {
|
while (themes) {
|
||||||
cons_show("%s", themes->data);
|
cons_show(themes->data);
|
||||||
themes = g_slist_next(themes);
|
themes = g_slist_next(themes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2315,7 +2315,7 @@ cons_show_scripts(GSList* scripts)
|
|||||||
} else {
|
} else {
|
||||||
cons_show("Scripts:");
|
cons_show("Scripts:");
|
||||||
while (scripts) {
|
while (scripts) {
|
||||||
cons_show("%s", scripts->data);
|
cons_show(scripts->data);
|
||||||
scripts = g_slist_next(scripts);
|
scripts = g_slist_next(scripts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ ui_handle_error(const char* const err_msg)
|
|||||||
GString* msg = g_string_new("");
|
GString* msg = g_string_new("");
|
||||||
g_string_printf(msg, "Error %s", err_msg);
|
g_string_printf(msg, "Error %s", err_msg);
|
||||||
|
|
||||||
cons_show_error("%s", msg->str);
|
cons_show_error(msg->str);
|
||||||
|
|
||||||
g_string_free(msg, TRUE);
|
g_string_free(msg, TRUE);
|
||||||
}
|
}
|
||||||
@@ -461,7 +461,7 @@ ui_invalid_command_usage(const char* const cmd, void (*setting_func)(void))
|
|||||||
(*setting_func)();
|
(*setting_func)();
|
||||||
} else {
|
} else {
|
||||||
cons_show("");
|
cons_show("");
|
||||||
cons_show("%s", msg->str);
|
cons_show(msg->str);
|
||||||
ProfWin* current = wins_get_current();
|
ProfWin* current = wins_get_current();
|
||||||
if (current->type == WIN_CHAT) {
|
if (current->type == WIN_CHAT) {
|
||||||
win_println(current, THEME_DEFAULT, "-", "%s", msg->str);
|
win_println(current, THEME_DEFAULT, "-", "%s", msg->str);
|
||||||
|
|||||||
@@ -144,14 +144,10 @@ void
|
|||||||
create_input_window(void)
|
create_input_window(void)
|
||||||
{
|
{
|
||||||
/* MB_CUR_MAX is evaluated at runtime depending on the current
|
/* MB_CUR_MAX is evaluated at runtime depending on the current
|
||||||
* locale; ensure our own compiled-in maximum is sufficient.
|
* locale, therefore we check that our own version is big enough
|
||||||
* Fail gracefully instead of aborting in production.
|
* and bail out if it isn't.
|
||||||
*/
|
*/
|
||||||
if (MB_CUR_MAX > PROF_MB_CUR_MAX) {
|
assert(MB_CUR_MAX <= PROF_MB_CUR_MAX);
|
||||||
log_error("Locale MB_CUR_MAX (%zu) exceeds compiled limit (%d)", (size_t)MB_CUR_MAX, PROF_MB_CUR_MAX);
|
|
||||||
cons_show_error("Unsupported locale. Before running, execute in terminal: export LC_ALL=C.UTF-8");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#ifdef NCURSES_REENTRANT
|
#ifdef NCURSES_REENTRANT
|
||||||
set_escdelay(25);
|
set_escdelay(25);
|
||||||
#else
|
#else
|
||||||
@@ -167,10 +163,6 @@ create_input_window(void)
|
|||||||
rl_callback_handler_install(NULL, _inp_rl_linehandler);
|
rl_callback_handler_install(NULL, _inp_rl_linehandler);
|
||||||
|
|
||||||
inp_win = newpad(1, INP_WIN_MAX);
|
inp_win = newpad(1, INP_WIN_MAX);
|
||||||
if (!inp_win) {
|
|
||||||
log_error("Failed to allocate input window pad");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
wbkgd(inp_win, theme_attrs(THEME_INPUT_TEXT));
|
wbkgd(inp_win, theme_attrs(THEME_INPUT_TEXT));
|
||||||
keypad(inp_win, TRUE);
|
keypad(inp_win, TRUE);
|
||||||
wmove(inp_win, 0, 0);
|
wmove(inp_win, 0, 0);
|
||||||
@@ -246,9 +238,6 @@ inp_readline(void)
|
|||||||
void
|
void
|
||||||
inp_win_resize(void)
|
inp_win_resize(void)
|
||||||
{
|
{
|
||||||
if (!inp_win) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int col = getcurx(inp_win);
|
int col = getcurx(inp_win);
|
||||||
int wcols = getmaxx(stdscr);
|
int wcols = getmaxx(stdscr);
|
||||||
|
|
||||||
@@ -296,10 +285,8 @@ void
|
|||||||
inp_close(void)
|
inp_close(void)
|
||||||
{
|
{
|
||||||
rl_callback_handler_remove();
|
rl_callback_handler_remove();
|
||||||
if (inp_win) {
|
delwin(inp_win);
|
||||||
delwin(inp_win);
|
inp_win = NULL;
|
||||||
inp_win = NULL;
|
|
||||||
}
|
|
||||||
fclose(discard);
|
fclose(discard);
|
||||||
discard = NULL;
|
discard = NULL;
|
||||||
}
|
}
|
||||||
@@ -307,9 +294,6 @@ inp_close(void)
|
|||||||
char*
|
char*
|
||||||
inp_get_line(void)
|
inp_get_line(void)
|
||||||
{
|
{
|
||||||
if (!inp_win) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
werase(inp_win);
|
werase(inp_win);
|
||||||
wmove(inp_win, 0, 0);
|
wmove(inp_win, 0, 0);
|
||||||
_inp_win_update_virtual();
|
_inp_win_update_virtual();
|
||||||
@@ -334,9 +318,6 @@ inp_set_line(const char* const new_line)
|
|||||||
char*
|
char*
|
||||||
inp_get_password(void)
|
inp_get_password(void)
|
||||||
{
|
{
|
||||||
if (!inp_win) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
werase(inp_win);
|
werase(inp_win);
|
||||||
wmove(inp_win, 0, 0);
|
wmove(inp_win, 0, 0);
|
||||||
_inp_win_update_virtual();
|
_inp_win_update_virtual();
|
||||||
|
|||||||
@@ -40,8 +40,6 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "log.h"
|
|
||||||
|
|
||||||
#ifdef HAVE_NCURSESW_NCURSES_H
|
#ifdef HAVE_NCURSESW_NCURSES_H
|
||||||
#include <ncursesw/ncurses.h>
|
#include <ncursesw/ncurses.h>
|
||||||
#elif HAVE_NCURSES_H
|
#elif HAVE_NCURSES_H
|
||||||
@@ -113,24 +111,16 @@ status_bar_init(void)
|
|||||||
|
|
||||||
int row = screen_statusbar_row();
|
int row = screen_statusbar_row();
|
||||||
int cols = getmaxx(stdscr);
|
int cols = getmaxx(stdscr);
|
||||||
if (cols <= 0) {
|
|
||||||
log_warning("status_bar_init: invalid cols %d, defaulting to 1", cols);
|
|
||||||
cols = 1;
|
|
||||||
}
|
|
||||||
statusbar_win = newwin(1, cols, row, 0);
|
statusbar_win = newwin(1, cols, row, 0);
|
||||||
|
|
||||||
if (statusbar_win) {
|
status_bar_draw();
|
||||||
status_bar_draw();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
status_bar_close(void)
|
status_bar_close(void)
|
||||||
{
|
{
|
||||||
if (statusbar_win) {
|
delwin(statusbar_win);
|
||||||
delwin(statusbar_win);
|
statusbar_win = NULL;
|
||||||
statusbar_win = NULL;
|
|
||||||
}
|
|
||||||
if (statusbar) {
|
if (statusbar) {
|
||||||
if (statusbar->time) {
|
if (statusbar->time) {
|
||||||
g_free(statusbar->time);
|
g_free(statusbar->time);
|
||||||
@@ -155,14 +145,7 @@ status_bar_close(void)
|
|||||||
void
|
void
|
||||||
status_bar_resize(void)
|
status_bar_resize(void)
|
||||||
{
|
{
|
||||||
if (!statusbar_win) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int cols = getmaxx(stdscr);
|
int cols = getmaxx(stdscr);
|
||||||
if (cols <= 0) {
|
|
||||||
log_warning("status_bar_resize: invalid cols %d, defaulting to 1", cols);
|
|
||||||
cols = 1;
|
|
||||||
}
|
|
||||||
werase(statusbar_win);
|
werase(statusbar_win);
|
||||||
int row = screen_statusbar_row();
|
int row = screen_statusbar_row();
|
||||||
wresize(statusbar_win, 1, cols);
|
wresize(statusbar_win, 1, cols);
|
||||||
@@ -302,9 +285,6 @@ status_bar_clear_fulljid(void)
|
|||||||
void
|
void
|
||||||
status_bar_draw(void)
|
status_bar_draw(void)
|
||||||
{
|
{
|
||||||
if (!statusbar_win) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
werase(statusbar_win);
|
werase(statusbar_win);
|
||||||
wbkgd(statusbar_win, theme_attrs(THEME_STATUS_TEXT));
|
wbkgd(statusbar_win, theme_attrs(THEME_STATUS_TEXT));
|
||||||
|
|
||||||
@@ -694,13 +674,8 @@ _display_name(StatusBarTab* tab)
|
|||||||
fullname = g_strconcat(mucwin_title, " conf", NULL);
|
fullname = g_strconcat(mucwin_title, " conf", NULL);
|
||||||
} else if (tab->window_type == WIN_PRIVATE) {
|
} else if (tab->window_type == WIN_PRIVATE) {
|
||||||
auto_jid Jid* jid = jid_create(tab->identifier);
|
auto_jid Jid* jid = jid_create(tab->identifier);
|
||||||
if (jid) {
|
auto_gchar gchar* mucwin_title = mucwin_generate_title(jid->barejid, PREF_STATUSBAR_ROOM_TITLE);
|
||||||
auto_gchar gchar* mucwin_title = mucwin_generate_title(jid->barejid, PREF_STATUSBAR_ROOM_TITLE);
|
fullname = g_strconcat(mucwin_title, "/", jid->resourcepart, NULL);
|
||||||
fullname = g_strconcat(mucwin_title, "/", jid->resourcepart, NULL);
|
|
||||||
} else {
|
|
||||||
// Fallback: use identifier directly if JID parsing failed
|
|
||||||
fullname = strdup(tab->identifier);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
fullname = strdup("window");
|
fullname = strdup("window");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,16 +73,9 @@ void
|
|||||||
create_title_bar(void)
|
create_title_bar(void)
|
||||||
{
|
{
|
||||||
int cols = getmaxx(stdscr);
|
int cols = getmaxx(stdscr);
|
||||||
if (cols <= 0) {
|
|
||||||
cols = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int row = screen_titlebar_row();
|
int row = screen_titlebar_row();
|
||||||
win = newwin(1, cols, row, 0);
|
win = newwin(1, cols, row, 0);
|
||||||
if (!win) {
|
|
||||||
// Failed to create title bar window; skip initialization to avoid NULL deref
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
wbkgd(win, theme_attrs(THEME_TITLE_TEXT));
|
wbkgd(win, theme_attrs(THEME_TITLE_TEXT));
|
||||||
title_bar_console();
|
title_bar_console();
|
||||||
title_bar_set_presence(CONTACT_OFFLINE);
|
title_bar_set_presence(CONTACT_OFFLINE);
|
||||||
@@ -95,18 +88,13 @@ create_title_bar(void)
|
|||||||
void
|
void
|
||||||
free_title_bar(void)
|
free_title_bar(void)
|
||||||
{
|
{
|
||||||
if (win) {
|
delwin(win);
|
||||||
delwin(win);
|
win = NULL;
|
||||||
win = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
title_bar_update_virtual(void)
|
title_bar_update_virtual(void)
|
||||||
{
|
{
|
||||||
if (!win) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ProfWin* window = wins_get_current();
|
ProfWin* window = wins_get_current();
|
||||||
if (window->type != WIN_CONSOLE) {
|
if (window->type != WIN_CONSOLE) {
|
||||||
if (typing_elapsed) {
|
if (typing_elapsed) {
|
||||||
@@ -126,13 +114,7 @@ title_bar_update_virtual(void)
|
|||||||
void
|
void
|
||||||
title_bar_resize(void)
|
title_bar_resize(void)
|
||||||
{
|
{
|
||||||
if (!win) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int cols = getmaxx(stdscr);
|
int cols = getmaxx(stdscr);
|
||||||
if (cols <= 0) {
|
|
||||||
cols = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
werase(win);
|
werase(win);
|
||||||
|
|
||||||
@@ -149,9 +131,6 @@ title_bar_resize(void)
|
|||||||
void
|
void
|
||||||
title_bar_console(void)
|
title_bar_console(void)
|
||||||
{
|
{
|
||||||
if (!win) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
werase(win);
|
werase(win);
|
||||||
if (typing_elapsed) {
|
if (typing_elapsed) {
|
||||||
g_timer_destroy(typing_elapsed);
|
g_timer_destroy(typing_elapsed);
|
||||||
@@ -213,9 +192,6 @@ title_bar_set_typing(gboolean is_typing)
|
|||||||
static void
|
static void
|
||||||
_title_bar_draw(void)
|
_title_bar_draw(void)
|
||||||
{
|
{
|
||||||
if (!win) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int pos;
|
int pos;
|
||||||
int maxrightpos;
|
int maxrightpos;
|
||||||
ProfWin* current = wins_get_current();
|
ProfWin* current = wins_get_current();
|
||||||
@@ -292,11 +268,7 @@ _show_scrolled(ProfWin* current)
|
|||||||
wattroff(win, bracket_attrs);
|
wattroff(win, bracket_attrs);
|
||||||
|
|
||||||
wattron(win, scrolled_attrs);
|
wattron(win, scrolled_attrs);
|
||||||
if (current->layout->unread_msg == 0) {
|
wprintw(win, "SCROLLED");
|
||||||
wprintw(win, "SCROLLED");
|
|
||||||
} else {
|
|
||||||
wprintw(win, "SCROLLED, NEW MESSAGES");
|
|
||||||
}
|
|
||||||
wattroff(win, scrolled_attrs);
|
wattroff(win, scrolled_attrs);
|
||||||
|
|
||||||
wattron(win, bracket_attrs);
|
wattron(win, bracket_attrs);
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ typedef struct prof_layout_t
|
|||||||
ProfBuff buffer;
|
ProfBuff buffer;
|
||||||
int y_pos;
|
int y_pos;
|
||||||
int paged;
|
int paged;
|
||||||
int unread_msg;
|
|
||||||
} ProfLayout;
|
} ProfLayout;
|
||||||
|
|
||||||
typedef struct prof_layout_simple_t
|
typedef struct prof_layout_simple_t
|
||||||
|
|||||||
@@ -75,22 +75,12 @@ static void _win_print_internal(ProfWin* window, const char* show_char, int pad_
|
|||||||
int flags, theme_item_t theme_item, const char* const from, const char* const message, DeliveryReceipt* receipt);
|
int flags, theme_item_t theme_item, const char* const from, const char* const message, DeliveryReceipt* receipt);
|
||||||
static void _win_print_wrapped(WINDOW* win, const char* const message, size_t indent, int pad_indent);
|
static void _win_print_wrapped(WINDOW* win, const char* const message, size_t indent, int pad_indent);
|
||||||
|
|
||||||
// Helper: clamp a subwindow width to a sane range [1, cols-1] if possible
|
|
||||||
static int
|
|
||||||
_check_subwin_width(int cols, int width)
|
|
||||||
{
|
|
||||||
return cols <= 1 ? 1 : CLAMP(width, 1, cols - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
int
|
||||||
win_roster_cols(void)
|
win_roster_cols(void)
|
||||||
{
|
{
|
||||||
int roster_win_percent = prefs_get_roster_size();
|
int roster_win_percent = prefs_get_roster_size();
|
||||||
int cols = getmaxx(stdscr);
|
int cols = getmaxx(stdscr);
|
||||||
int width = CEILING((((double)cols) / 100) * roster_win_percent);
|
return CEILING((((double)cols) / 100) * roster_win_percent);
|
||||||
// Clamp to a sane range to avoid zero/full-width pads
|
|
||||||
width = _check_subwin_width(cols, width);
|
|
||||||
return width;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
@@ -98,10 +88,7 @@ win_occpuants_cols(void)
|
|||||||
{
|
{
|
||||||
int occupants_win_percent = prefs_get_occupants_size();
|
int occupants_win_percent = prefs_get_occupants_size();
|
||||||
int cols = getmaxx(stdscr);
|
int cols = getmaxx(stdscr);
|
||||||
int width = CEILING((((double)cols) / 100) * occupants_win_percent);
|
return CEILING((((double)cols) / 100) * occupants_win_percent);
|
||||||
// Clamp to a sane range to avoid zero/full-width pads
|
|
||||||
width = _check_subwin_width(cols, width);
|
|
||||||
return width;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ProfLayout*
|
static ProfLayout*
|
||||||
@@ -116,7 +103,6 @@ _win_create_simple_layout(void)
|
|||||||
layout->base.buffer = buffer_create();
|
layout->base.buffer = buffer_create();
|
||||||
layout->base.y_pos = 0;
|
layout->base.y_pos = 0;
|
||||||
layout->base.paged = 0;
|
layout->base.paged = 0;
|
||||||
layout->base.unread_msg = 0;
|
|
||||||
scrollok(layout->base.win, TRUE);
|
scrollok(layout->base.win, TRUE);
|
||||||
|
|
||||||
return &layout->base;
|
return &layout->base;
|
||||||
@@ -134,7 +120,6 @@ _win_create_split_layout(void)
|
|||||||
layout->base.buffer = buffer_create();
|
layout->base.buffer = buffer_create();
|
||||||
layout->base.y_pos = 0;
|
layout->base.y_pos = 0;
|
||||||
layout->base.paged = 0;
|
layout->base.paged = 0;
|
||||||
layout->base.unread_msg = 0;
|
|
||||||
scrollok(layout->base.win, TRUE);
|
scrollok(layout->base.win, TRUE);
|
||||||
layout->subwin = NULL;
|
layout->subwin = NULL;
|
||||||
layout->sub_y_pos = 0;
|
layout->sub_y_pos = 0;
|
||||||
@@ -157,7 +142,6 @@ win_create_console(void)
|
|||||||
ProfWin*
|
ProfWin*
|
||||||
win_create_chat(const char* const barejid)
|
win_create_chat(const char* const barejid)
|
||||||
{
|
{
|
||||||
assert(barejid != NULL);
|
|
||||||
ProfChatWin* new_win = malloc(sizeof(ProfChatWin));
|
ProfChatWin* new_win = malloc(sizeof(ProfChatWin));
|
||||||
new_win->window.type = WIN_CHAT;
|
new_win->window.type = WIN_CHAT;
|
||||||
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
||||||
@@ -189,7 +173,6 @@ win_create_chat(const char* const barejid)
|
|||||||
ProfWin*
|
ProfWin*
|
||||||
win_create_muc(const char* const roomjid)
|
win_create_muc(const char* const roomjid)
|
||||||
{
|
{
|
||||||
assert(roomjid != NULL);
|
|
||||||
ProfMucWin* new_win = malloc(sizeof(ProfMucWin));
|
ProfMucWin* new_win = malloc(sizeof(ProfMucWin));
|
||||||
int cols = getmaxx(stdscr);
|
int cols = getmaxx(stdscr);
|
||||||
|
|
||||||
@@ -214,7 +197,6 @@ win_create_muc(const char* const roomjid)
|
|||||||
layout->base.buffer = buffer_create();
|
layout->base.buffer = buffer_create();
|
||||||
layout->base.y_pos = 0;
|
layout->base.y_pos = 0;
|
||||||
layout->base.paged = 0;
|
layout->base.paged = 0;
|
||||||
layout->base.unread_msg = 0;
|
|
||||||
scrollok(layout->base.win, TRUE);
|
scrollok(layout->base.win, TRUE);
|
||||||
new_win->window.layout = (ProfLayout*)layout;
|
new_win->window.layout = (ProfLayout*)layout;
|
||||||
|
|
||||||
@@ -248,8 +230,6 @@ win_create_muc(const char* const roomjid)
|
|||||||
ProfWin*
|
ProfWin*
|
||||||
win_create_config(const char* const roomjid, DataForm* form, ProfConfWinCallback submit, ProfConfWinCallback cancel, const void* userdata)
|
win_create_config(const char* const roomjid, DataForm* form, ProfConfWinCallback submit, ProfConfWinCallback cancel, const void* userdata)
|
||||||
{
|
{
|
||||||
assert(roomjid != NULL);
|
|
||||||
assert(form != NULL);
|
|
||||||
ProfConfWin* new_win = malloc(sizeof(ProfConfWin));
|
ProfConfWin* new_win = malloc(sizeof(ProfConfWin));
|
||||||
new_win->window.type = WIN_CONFIG;
|
new_win->window.type = WIN_CONFIG;
|
||||||
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
||||||
@@ -268,7 +248,6 @@ win_create_config(const char* const roomjid, DataForm* form, ProfConfWinCallback
|
|||||||
ProfWin*
|
ProfWin*
|
||||||
win_create_private(const char* const fulljid)
|
win_create_private(const char* const fulljid)
|
||||||
{
|
{
|
||||||
assert(fulljid != NULL);
|
|
||||||
ProfPrivateWin* new_win = malloc(sizeof(ProfPrivateWin));
|
ProfPrivateWin* new_win = malloc(sizeof(ProfPrivateWin));
|
||||||
new_win->window.type = WIN_PRIVATE;
|
new_win->window.type = WIN_PRIVATE;
|
||||||
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
||||||
@@ -299,8 +278,6 @@ win_create_xmlconsole(void)
|
|||||||
ProfWin*
|
ProfWin*
|
||||||
win_create_plugin(const char* const plugin_name, const char* const tag)
|
win_create_plugin(const char* const plugin_name, const char* const tag)
|
||||||
{
|
{
|
||||||
assert(plugin_name != NULL);
|
|
||||||
assert(tag != NULL);
|
|
||||||
ProfPluginWin* new_win = malloc(sizeof(ProfPluginWin));
|
ProfPluginWin* new_win = malloc(sizeof(ProfPluginWin));
|
||||||
new_win->window.type = WIN_PLUGIN;
|
new_win->window.type = WIN_PLUGIN;
|
||||||
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
||||||
@@ -317,7 +294,6 @@ win_create_plugin(const char* const plugin_name, const char* const tag)
|
|||||||
ProfWin*
|
ProfWin*
|
||||||
win_create_vcard(vCard* vcard)
|
win_create_vcard(vCard* vcard)
|
||||||
{
|
{
|
||||||
assert(vcard != NULL);
|
|
||||||
ProfVcardWin* new_win = malloc(sizeof(ProfVcardWin));
|
ProfVcardWin* new_win = malloc(sizeof(ProfVcardWin));
|
||||||
new_win->window.type = WIN_VCARD;
|
new_win->window.type = WIN_VCARD;
|
||||||
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
new_win->window.scroll_state = WIN_SCROLL_INNER;
|
||||||
@@ -369,7 +345,7 @@ win_get_title(ProfWin* window)
|
|||||||
const ProfConfWin* confwin = (ProfConfWin*)window;
|
const ProfConfWin* confwin = (ProfConfWin*)window;
|
||||||
assert(confwin->memcheck == PROFCONFWIN_MEMCHECK);
|
assert(confwin->memcheck == PROFCONFWIN_MEMCHECK);
|
||||||
auto_gchar gchar* mucwin_title = mucwin_generate_title(confwin->roomjid, PREF_TITLEBAR_MUC_TITLE);
|
auto_gchar gchar* mucwin_title = mucwin_generate_title(confwin->roomjid, PREF_TITLEBAR_MUC_TITLE);
|
||||||
if (confwin->form && confwin->form->modified) {
|
if (confwin->form->modified) {
|
||||||
return g_strconcat(mucwin_title, " config *", NULL);
|
return g_strconcat(mucwin_title, " config *", NULL);
|
||||||
}
|
}
|
||||||
return g_strconcat(mucwin_title, " config", NULL);
|
return g_strconcat(mucwin_title, " config", NULL);
|
||||||
@@ -577,25 +553,7 @@ win_show_subwin(ProfWin* window)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout;
|
ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout;
|
||||||
// If a subwindow already exists (e.g. repeated call), destroy it to avoid leaks
|
|
||||||
if (layout->subwin) {
|
|
||||||
delwin(layout->subwin);
|
|
||||||
layout->subwin = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure minimum width to avoid creating a zero-width pad
|
|
||||||
if (subwin_cols <= 0) {
|
|
||||||
subwin_cols = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
layout->subwin = newpad(PAD_SIZE, subwin_cols);
|
layout->subwin = newpad(PAD_SIZE, subwin_cols);
|
||||||
if (layout->subwin == NULL) {
|
|
||||||
// Failed to allocate subwindow; keep base window resized to full width
|
|
||||||
log_error("Failed to create subwindow pad (cols=%d)", subwin_cols);
|
|
||||||
wresize(layout->base.win, PAD_SIZE, cols);
|
|
||||||
win_redraw(window);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
wbkgd(layout->subwin, theme_attrs(THEME_TEXT));
|
wbkgd(layout->subwin, theme_attrs(THEME_TEXT));
|
||||||
wresize(layout->base.win, PAD_SIZE, cols - subwin_cols);
|
wresize(layout->base.win, PAD_SIZE, cols - subwin_cols);
|
||||||
win_redraw(window);
|
win_redraw(window);
|
||||||
@@ -740,11 +698,9 @@ void
|
|||||||
win_page_down(ProfWin* window, int scroll_size)
|
win_page_down(ProfWin* window, int scroll_size)
|
||||||
{
|
{
|
||||||
int total_rows = getcury(window->layout->win);
|
int total_rows = getcury(window->layout->win);
|
||||||
int total_rows_with_unread = total_rows + window->layout->unread_msg;
|
|
||||||
int* page_start = &(window->layout->y_pos);
|
int* page_start = &(window->layout->y_pos);
|
||||||
int page_space = getmaxy(stdscr) - 4;
|
int page_space = getmaxy(stdscr) - 4;
|
||||||
int page_start_initial = *page_start;
|
int page_start_initial = *page_start;
|
||||||
|
|
||||||
if (scroll_size == 0)
|
if (scroll_size == 0)
|
||||||
scroll_size = page_space;
|
scroll_size = page_space;
|
||||||
win_scroll_state_t* scroll_state = &window->scroll_state;
|
win_scroll_state_t* scroll_state = &window->scroll_state;
|
||||||
@@ -753,11 +709,7 @@ win_page_down(ProfWin* window, int scroll_size)
|
|||||||
*page_start += scroll_size;
|
*page_start += scroll_size;
|
||||||
|
|
||||||
// Scrolled down after reaching the bottom of the page
|
// Scrolled down after reaching the bottom of the page
|
||||||
gboolean past_bottom = *page_start > total_rows_with_unread - page_space;
|
if ((*page_start > total_rows - page_space || (*page_start == page_space && *page_start >= total_rows)) && window->type == WIN_CHAT) {
|
||||||
gboolean at_page_space_and_past_unread = (*page_start == page_space && *page_start >= total_rows_with_unread);
|
|
||||||
gboolean is_chat = window->type == WIN_CHAT;
|
|
||||||
|
|
||||||
if ((past_bottom || at_page_space_and_past_unread) && is_chat) {
|
|
||||||
int bf_size = buffer_size(window->layout->buffer);
|
int bf_size = buffer_size(window->layout->buffer);
|
||||||
if (bf_size > 0 && *scroll_state != WIN_SCROLL_REACHED_BOTTOM) {
|
if (bf_size > 0 && *scroll_state != WIN_SCROLL_REACHED_BOTTOM) {
|
||||||
// How many lines are left until end of the screen
|
// How many lines are left until end of the screen
|
||||||
@@ -791,16 +743,13 @@ win_page_down(ProfWin* window, int scroll_size)
|
|||||||
window->layout->paged = 1;
|
window->layout->paged = 1;
|
||||||
|
|
||||||
// update only if position has changed
|
// update only if position has changed
|
||||||
if ((page_start_initial != *page_start) || window->layout->unread_msg) {
|
if (page_start_initial != *page_start) {
|
||||||
win_update_virtual(window);
|
win_update_virtual(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Switch off page if no messages left to read.
|
// switch off page if last line and space line visible
|
||||||
* TODO: update buffer end handling to check messages just after last entry.
|
if (total_rows - *page_start == page_space) {
|
||||||
*/
|
|
||||||
if (*scroll_state == WIN_SCROLL_REACHED_BOTTOM) {
|
|
||||||
window->layout->paged = 0;
|
window->layout->paged = 0;
|
||||||
window->layout->unread_msg = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -861,7 +810,6 @@ win_clear(ProfWin* window)
|
|||||||
int* page_start = &(window->layout->y_pos);
|
int* page_start = &(window->layout->y_pos);
|
||||||
*page_start = y;
|
*page_start = y;
|
||||||
window->layout->paged = 1;
|
window->layout->paged = 1;
|
||||||
window->layout->unread_msg = 0;
|
|
||||||
win_update_virtual(window);
|
win_update_virtual(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -949,11 +897,6 @@ win_refresh_with_subwin(ProfWin* window)
|
|||||||
int row_end = screen_mainwin_row_end();
|
int row_end = screen_mainwin_row_end();
|
||||||
ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout;
|
ProfLayoutSplit* layout = (ProfLayoutSplit*)window->layout;
|
||||||
|
|
||||||
// Safety: if subwindow is not active, nothing to refresh
|
|
||||||
if (layout == NULL || layout->subwin == NULL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window->type == WIN_MUC) {
|
if (window->type == WIN_MUC) {
|
||||||
subwin_cols = win_occpuants_cols();
|
subwin_cols = win_occpuants_cols();
|
||||||
} else if (window->type == WIN_CONSOLE) {
|
} else if (window->type == WIN_CONSOLE) {
|
||||||
@@ -971,7 +914,6 @@ void
|
|||||||
win_move_to_end(ProfWin* window)
|
win_move_to_end(ProfWin* window)
|
||||||
{
|
{
|
||||||
window->layout->paged = 0;
|
window->layout->paged = 0;
|
||||||
window->layout->unread_msg = 0;
|
|
||||||
|
|
||||||
int rows = getmaxy(stdscr);
|
int rows = getmaxy(stdscr);
|
||||||
int y = getcury(window->layout->win);
|
int y = getcury(window->layout->win);
|
||||||
@@ -1754,13 +1696,6 @@ win_newline(ProfWin* window)
|
|||||||
static void
|
static void
|
||||||
_win_printf(ProfWin* window, const char* show_char, int pad_indent, GDateTime* timestamp, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message_id, const char* const message, ...)
|
_win_printf(ProfWin* window, const char* show_char, int pad_indent, GDateTime* timestamp, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message_id, const char* const message, ...)
|
||||||
{
|
{
|
||||||
|
|
||||||
/* Prevent printing and buffer update when user is viewing message history [SCROLLING]*/
|
|
||||||
if (window->layout->paged && wins_is_current(window)) {
|
|
||||||
window->layout->unread_msg++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timestamp == NULL) {
|
if (timestamp == NULL) {
|
||||||
timestamp = g_date_time_new_now_local();
|
timestamp = g_date_time_new_now_local();
|
||||||
} else {
|
} else {
|
||||||
@@ -2082,14 +2017,7 @@ win_print_loading_history(ProfWin* window)
|
|||||||
gboolean is_buffer_empty = buffer_size(window->layout->buffer) == 0;
|
gboolean is_buffer_empty = buffer_size(window->layout->buffer) == 0;
|
||||||
|
|
||||||
if (!is_buffer_empty) {
|
if (!is_buffer_empty) {
|
||||||
ProfBuffEntry* first = buffer_get_entry(window->layout->buffer, 0);
|
timestamp = buffer_get_entry(window->layout->buffer, 0)->time;
|
||||||
if (first && first->time) {
|
|
||||||
timestamp = first->time;
|
|
||||||
} else {
|
|
||||||
// Fallback to current time if entry/time is unavailable
|
|
||||||
timestamp = g_date_time_new_now_local();
|
|
||||||
is_buffer_empty = TRUE; // ensure we unref fallback timestamp below
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
timestamp = g_date_time_new_now_local();
|
timestamp = g_date_time_new_now_local();
|
||||||
}
|
}
|
||||||
@@ -2289,7 +2217,7 @@ void
|
|||||||
win_handle_command_exec_result_note(ProfWin* window, const char* const type, const char* const value)
|
win_handle_command_exec_result_note(ProfWin* window, const char* const type, const char* const value)
|
||||||
{
|
{
|
||||||
assert(window != NULL);
|
assert(window != NULL);
|
||||||
win_println(window, THEME_DEFAULT, "!", "%s", value);
|
win_println(window, THEME_DEFAULT, "!", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -407,10 +407,8 @@ wins_get_by_string(const char* str)
|
|||||||
if (barejid) {
|
if (barejid) {
|
||||||
ProfChatWin* chatwin = wins_get_chat(barejid);
|
ProfChatWin* chatwin = wins_get_chat(barejid);
|
||||||
if (chatwin) {
|
if (chatwin) {
|
||||||
free(barejid);
|
|
||||||
return (ProfWin*)chatwin;
|
return (ProfWin*)chatwin;
|
||||||
}
|
}
|
||||||
free(barejid);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,9 +603,6 @@ wins_new_xmlconsole(void)
|
|||||||
{
|
{
|
||||||
int result = _wins_get_next_available_num(keys);
|
int result = _wins_get_next_available_num(keys);
|
||||||
ProfWin* newwin = win_create_xmlconsole();
|
ProfWin* newwin = win_create_xmlconsole();
|
||||||
if (!newwin) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
||||||
autocomplete_add(wins_ac, "xmlconsole");
|
autocomplete_add(wins_ac, "xmlconsole");
|
||||||
autocomplete_add(wins_close_ac, "xmlconsole");
|
autocomplete_add(wins_close_ac, "xmlconsole");
|
||||||
@@ -619,9 +614,6 @@ wins_new_chat(const char* const barejid)
|
|||||||
{
|
{
|
||||||
int result = _wins_get_next_available_num(keys);
|
int result = _wins_get_next_available_num(keys);
|
||||||
ProfWin* newwin = win_create_chat(barejid);
|
ProfWin* newwin = win_create_chat(barejid);
|
||||||
if (!newwin) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
||||||
|
|
||||||
autocomplete_add(wins_ac, barejid);
|
autocomplete_add(wins_ac, barejid);
|
||||||
@@ -645,9 +637,6 @@ wins_new_muc(const char* const roomjid)
|
|||||||
{
|
{
|
||||||
int result = _wins_get_next_available_num(keys);
|
int result = _wins_get_next_available_num(keys);
|
||||||
ProfWin* newwin = win_create_muc(roomjid);
|
ProfWin* newwin = win_create_muc(roomjid);
|
||||||
if (!newwin) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
||||||
autocomplete_add(wins_ac, roomjid);
|
autocomplete_add(wins_ac, roomjid);
|
||||||
autocomplete_add(wins_close_ac, roomjid);
|
autocomplete_add(wins_close_ac, roomjid);
|
||||||
@@ -662,9 +651,6 @@ wins_new_config(const char* const roomjid, DataForm* form, ProfConfWinCallback s
|
|||||||
{
|
{
|
||||||
int result = _wins_get_next_available_num(keys);
|
int result = _wins_get_next_available_num(keys);
|
||||||
ProfWin* newwin = win_create_config(roomjid, form, submit, cancel, userdata);
|
ProfWin* newwin = win_create_config(roomjid, form, submit, cancel, userdata);
|
||||||
if (!newwin) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
||||||
|
|
||||||
return newwin;
|
return newwin;
|
||||||
@@ -675,9 +661,6 @@ wins_new_private(const char* const fulljid)
|
|||||||
{
|
{
|
||||||
int result = _wins_get_next_available_num(keys);
|
int result = _wins_get_next_available_num(keys);
|
||||||
ProfWin* newwin = win_create_private(fulljid);
|
ProfWin* newwin = win_create_private(fulljid);
|
||||||
if (!newwin) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
||||||
autocomplete_add(wins_ac, fulljid);
|
autocomplete_add(wins_ac, fulljid);
|
||||||
autocomplete_add(wins_close_ac, fulljid);
|
autocomplete_add(wins_close_ac, fulljid);
|
||||||
@@ -692,9 +675,6 @@ wins_new_plugin(const char* const plugin_name, const char* const tag)
|
|||||||
{
|
{
|
||||||
int result = _wins_get_next_available_num(keys);
|
int result = _wins_get_next_available_num(keys);
|
||||||
ProfWin* newwin = win_create_plugin(plugin_name, tag);
|
ProfWin* newwin = win_create_plugin(plugin_name, tag);
|
||||||
if (!newwin) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
||||||
autocomplete_add(wins_ac, tag);
|
autocomplete_add(wins_ac, tag);
|
||||||
autocomplete_add(wins_close_ac, tag);
|
autocomplete_add(wins_close_ac, tag);
|
||||||
@@ -706,9 +686,6 @@ wins_new_vcard(vCard* vcard)
|
|||||||
{
|
{
|
||||||
int result = _wins_get_next_available_num(keys);
|
int result = _wins_get_next_available_num(keys);
|
||||||
ProfWin* newwin = win_create_vcard(vcard);
|
ProfWin* newwin = win_create_vcard(vcard);
|
||||||
if (!newwin) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
_wins_htable_insert(windows, GINT_TO_POINTER(result), newwin);
|
||||||
|
|
||||||
return newwin;
|
return newwin;
|
||||||
|
|||||||
@@ -171,15 +171,16 @@ caps_get_features(void)
|
|||||||
{
|
{
|
||||||
GList* result = NULL;
|
GList* result = NULL;
|
||||||
|
|
||||||
GHashTableIter iter;
|
GList* features_as_list = g_hash_table_get_keys(prof_features);
|
||||||
gpointer key, value;
|
GList* curr = features_as_list;
|
||||||
g_hash_table_iter_init(&iter, prof_features);
|
while (curr) {
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
result = g_list_append(result, strdup(curr->data));
|
||||||
result = g_list_append(result, strdup((char*)key));
|
curr = g_list_next(curr);
|
||||||
}
|
}
|
||||||
|
g_list_free(features_as_list);
|
||||||
|
|
||||||
GList* plugin_features = plugins_get_disco_features();
|
GList* plugin_features = plugins_get_disco_features();
|
||||||
GList* curr = plugin_features;
|
curr = plugin_features;
|
||||||
while (curr) {
|
while (curr) {
|
||||||
result = g_list_append(result, strdup(curr->data));
|
result = g_list_append(result, strdup(curr->data));
|
||||||
curr = g_list_next(curr);
|
curr = g_list_next(curr);
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ connection_init(void)
|
|||||||
if (string_to_verbosity(v, &verbosity, &err_msg)) {
|
if (string_to_verbosity(v, &verbosity, &err_msg)) {
|
||||||
xmpp_ctx_set_verbosity(conn.xmpp_ctx, verbosity);
|
xmpp_ctx_set_verbosity(conn.xmpp_ctx, verbosity);
|
||||||
} else {
|
} else {
|
||||||
cons_show("%s", err_msg);
|
cons_show(err_msg);
|
||||||
}
|
}
|
||||||
conn.xmpp_conn = xmpp_conn_new(conn.xmpp_ctx);
|
conn.xmpp_conn = xmpp_conn_new(conn.xmpp_ctx);
|
||||||
|
|
||||||
@@ -638,18 +638,22 @@ gboolean
|
|||||||
connection_supports(const char* const feature)
|
connection_supports(const char* const feature)
|
||||||
{
|
{
|
||||||
gboolean ret = FALSE;
|
gboolean ret = FALSE;
|
||||||
GHashTableIter iter;
|
GList* jids = g_hash_table_get_keys(conn.features_by_jid);
|
||||||
gpointer key, value;
|
|
||||||
|
|
||||||
g_hash_table_iter_init(&iter, conn.features_by_jid);
|
GList* curr = jids;
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
while (curr) {
|
||||||
GHashTable* features = (GHashTable*)value;
|
char* jid = curr->data;
|
||||||
|
GHashTable* features = g_hash_table_lookup(conn.features_by_jid, jid);
|
||||||
if (features && g_hash_table_lookup(features, feature)) {
|
if (features && g_hash_table_lookup(features, feature)) {
|
||||||
ret = TRUE;
|
ret = TRUE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
curr = g_list_next(curr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_list_free(jids);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,17 +664,22 @@ connection_jid_for_feature(const char* const feature)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
GHashTableIter iter;
|
GList* jids = g_hash_table_get_keys(conn.features_by_jid);
|
||||||
gpointer key, value;
|
|
||||||
|
|
||||||
g_hash_table_iter_init(&iter, conn.features_by_jid);
|
GList* curr = jids;
|
||||||
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
while (curr) {
|
||||||
GHashTable* features = (GHashTable*)value;
|
char* jid = curr->data;
|
||||||
|
GHashTable* features = g_hash_table_lookup(conn.features_by_jid, jid);
|
||||||
if (features && g_hash_table_lookup(features, feature)) {
|
if (features && g_hash_table_lookup(features, feature)) {
|
||||||
return (const char*)key;
|
g_list_free(jids);
|
||||||
|
return jid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
curr = g_list_next(curr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_list_free(jids);
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1025,7 +1034,7 @@ _connection_handler(xmpp_conn_t* const xmpp_conn, const xmpp_conn_event_t status
|
|||||||
conn.sm_state = xmpp_conn_get_sm_state(conn.xmpp_conn);
|
conn.sm_state = xmpp_conn_get_sm_state(conn.xmpp_conn);
|
||||||
if (send_queue_len > 0 && prefs_get_boolean(PREF_STROPHE_SM_RESEND)) {
|
if (send_queue_len > 0 && prefs_get_boolean(PREF_STROPHE_SM_RESEND)) {
|
||||||
conn.queued_messages = calloc(send_queue_len + 1, sizeof(*conn.queued_messages));
|
conn.queued_messages = calloc(send_queue_len + 1, sizeof(*conn.queued_messages));
|
||||||
for (int n = 0; n < send_queue_len; ++n) {
|
for (int n = 0; n < send_queue_len && conn.queued_messages[n]; ++n) {
|
||||||
conn.queued_messages[n] = xmpp_conn_send_queue_drop_element(conn.xmpp_conn, XMPP_QUEUE_OLDEST);
|
conn.queued_messages[n] = xmpp_conn_send_queue_drop_element(conn.xmpp_conn, XMPP_QUEUE_OLDEST);
|
||||||
}
|
}
|
||||||
} else if (send_queue_len > 0) {
|
} else if (send_queue_len > 0) {
|
||||||
@@ -1180,13 +1189,12 @@ connection_debug_print_features()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
GHashTableIter feature_iter;
|
GList* feature_keys = g_hash_table_get_keys(features);
|
||||||
gpointer feature_key, feature_value;
|
for (GList* l = feature_keys; l != NULL; l = l->next) {
|
||||||
g_hash_table_iter_init(&feature_iter, features);
|
const char* feature = (const char*)l->data;
|
||||||
while (g_hash_table_iter_next(&feature_iter, &feature_key, &feature_value)) {
|
|
||||||
const char* feature = (const char*)feature_key;
|
|
||||||
log_debug("%s:\t%s", jid, feature);
|
log_debug("%s:\t%s", jid, feature);
|
||||||
}
|
}
|
||||||
|
g_list_free(feature_keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
log_debug("=== End of Features ===");
|
log_debug("=== End of Features ===");
|
||||||
|
|||||||
@@ -437,7 +437,18 @@ form_get_form_type_field(DataForm* form)
|
|||||||
gboolean
|
gboolean
|
||||||
form_tag_exists(DataForm* form, const char* const tag)
|
form_tag_exists(DataForm* form, const char* const tag)
|
||||||
{
|
{
|
||||||
return g_hash_table_contains(form->tag_to_var, tag);
|
GList* tags = g_hash_table_get_keys(form->tag_to_var);
|
||||||
|
GList* curr = tags;
|
||||||
|
while (curr) {
|
||||||
|
if (g_strcmp0(curr->data, tag) == 0) {
|
||||||
|
g_list_free(tags);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
curr = g_list_next(curr);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_list_free(tags);
|
||||||
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
form_field_type_t
|
form_field_type_t
|
||||||
|
|||||||
@@ -2532,6 +2532,9 @@ _disco_items_result_handler(xmpp_stanza_t* const stanza)
|
|||||||
}
|
}
|
||||||
|
|
||||||
xmpp_stanza_t* child = xmpp_stanza_get_children(query);
|
xmpp_stanza_t* child = xmpp_stanza_get_children(query);
|
||||||
|
if (child == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
while (child) {
|
while (child) {
|
||||||
const char* stanza_name = xmpp_stanza_get_name(child);
|
const char* stanza_name = xmpp_stanza_get_name(child);
|
||||||
|
|||||||
@@ -868,7 +868,7 @@ _handle_error(xmpp_stanza_t* const stanza)
|
|||||||
g_string_append(log_msg, " error=");
|
g_string_append(log_msg, " error=");
|
||||||
g_string_append(log_msg, err_msg);
|
g_string_append(log_msg, err_msg);
|
||||||
|
|
||||||
log_info("%s", log_msg->str);
|
log_info(log_msg->str);
|
||||||
|
|
||||||
g_string_free(log_msg, TRUE);
|
g_string_free(log_msg, TRUE);
|
||||||
|
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ _ox_metadata_result(xmpp_stanza_t* const stanza, void* const userdata)
|
|||||||
|
|
||||||
if (fingerprint) {
|
if (fingerprint) {
|
||||||
if (strlen(fingerprint) == KEYID_LENGTH) {
|
if (strlen(fingerprint) == KEYID_LENGTH) {
|
||||||
cons_show("%s", fingerprint);
|
cons_show(fingerprint);
|
||||||
} else {
|
} else {
|
||||||
cons_show("OX: Wrong char size of public key");
|
cons_show("OX: Wrong char size of public key");
|
||||||
log_error("[OX] Wrong chat size of public key %s", fingerprint);
|
log_error("[OX] Wrong chat size of public key %s", fingerprint);
|
||||||
|
|||||||
@@ -455,7 +455,7 @@ _presence_error_handler(xmpp_stanza_t* const stanza)
|
|||||||
g_string_append(log_msg, " error=");
|
g_string_append(log_msg, " error=");
|
||||||
g_string_append(log_msg, err_msg);
|
g_string_append(log_msg, err_msg);
|
||||||
|
|
||||||
log_info("%s", log_msg->str);
|
log_info(log_msg->str);
|
||||||
|
|
||||||
g_string_free(log_msg, TRUE);
|
g_string_free(log_msg, TRUE);
|
||||||
|
|
||||||
|
|||||||
@@ -14,31 +14,21 @@
|
|||||||
* flaky tests caused by leftover state. The overhead is acceptable since
|
* flaky tests caused by leftover state. The overhead is acceptable since
|
||||||
* functional tests run less frequently than unit tests.
|
* functional tests run less frequently than unit tests.
|
||||||
*
|
*
|
||||||
* Tests are organized into groups for better maintainability and parallel execution:
|
* Tests are organized into groups for better maintainability:
|
||||||
* Group 1: Connect, Ping, Rooms, Software
|
* Group 1: Connection, Ping, Rooms, Presence
|
||||||
* Group 2: Message, Receipts, Roster, Chat Session
|
* Group 2: Messages, Receipts, Roster management
|
||||||
* Group 3: Presence, Disconnect
|
* Group 3: MUC (Multi-User Chat) functionality
|
||||||
* Group 4: MUC, Carbons
|
* Group 4: Carbons, Chat sessions, Software version, Disconnect
|
||||||
*
|
|
||||||
* Parallel execution:
|
|
||||||
* ./functionaltests - run all tests sequentially
|
|
||||||
* ./functionaltests N - run group N only (N = 1..num_groups)
|
|
||||||
*
|
|
||||||
* For parallel execution, run multiple groups simultaneously:
|
|
||||||
* ./functionaltests 1 & ./functionaltests 2 & ./functionaltests 3 & ./functionaltests 4 & wait
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <string.h>
|
|
||||||
#include "prof_cmocka.h"
|
#include "prof_cmocka.h"
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
#include "proftest.h"
|
#include "proftest.h"
|
||||||
#include "test_connect.h"
|
#include "test_connect.h"
|
||||||
#include "test_ping.h"
|
#include "test_ping.h"
|
||||||
@@ -52,7 +42,6 @@
|
|||||||
#include "test_software.h"
|
#include "test_software.h"
|
||||||
#include "test_muc.h"
|
#include "test_muc.h"
|
||||||
#include "test_disconnect.h"
|
#include "test_disconnect.h"
|
||||||
#include "test_lastactivity.h"
|
|
||||||
|
|
||||||
/* Macro to wrap each test with setup/teardown functions */
|
/* Macro to wrap each test with setup/teardown functions */
|
||||||
#define PROF_FUNC_TEST(test) cmocka_unit_test_setup_teardown(test, init_prof_test, close_prof_test)
|
#define PROF_FUNC_TEST(test) cmocka_unit_test_setup_teardown(test, init_prof_test, close_prof_test)
|
||||||
@@ -60,27 +49,13 @@
|
|||||||
int
|
int
|
||||||
main(int argc, char* argv[])
|
main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
int group = 0; /* 0 = all groups */
|
const struct CMUnitTest all_tests[] = {
|
||||||
if (argc > 1) {
|
|
||||||
group = atoi(argv[1]);
|
|
||||||
if (group < 1 || group > 4) {
|
|
||||||
fprintf(stderr, "Usage: %s [group]\n", argv[0]);
|
|
||||||
fprintf(stderr, " group: 1-4 to run specific group, or omit for all\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char group_env[16];
|
/* ============================================================
|
||||||
snprintf(group_env, sizeof(group_env), "%d", group);
|
* GROUP 1: Connect, Ping, Rooms, Presence
|
||||||
setenv("PROF_TEST_GROUP", group_env, 1);
|
* Basic XMPP session establishment and presence management
|
||||||
|
* ============================================================ */
|
||||||
fprintf(stderr, "[PROF_TEST] Starting functional tests, group=%d\n", group);
|
|
||||||
|
|
||||||
/* ============================================================
|
|
||||||
* GROUP 1: Connect, Ping, Rooms, Software
|
|
||||||
* Basic XMPP session establishment and server queries
|
|
||||||
* ============================================================ */
|
|
||||||
const struct CMUnitTest group1_tests[] = {
|
|
||||||
/* Connection tests - verify login, roster, bookmarks */
|
/* Connection tests - verify login, roster, bookmarks */
|
||||||
PROF_FUNC_TEST(connect_jid_requests_roster),
|
PROF_FUNC_TEST(connect_jid_requests_roster),
|
||||||
PROF_FUNC_TEST(connect_jid_sends_presence_after_receiving_roster),
|
PROF_FUNC_TEST(connect_jid_sends_presence_after_receiving_roster),
|
||||||
@@ -98,24 +73,28 @@ main(int argc, char* argv[])
|
|||||||
/* Room discovery - XEP-0045 */
|
/* Room discovery - XEP-0045 */
|
||||||
PROF_FUNC_TEST(rooms_query),
|
PROF_FUNC_TEST(rooms_query),
|
||||||
|
|
||||||
/* Software Version - XEP-0092 */
|
/* Presence tests - online/away/xa/dnd/chat status */
|
||||||
PROF_FUNC_TEST(send_software_version_request),
|
PROF_FUNC_TEST(presence_online),
|
||||||
PROF_FUNC_TEST(display_software_version_result),
|
PROF_FUNC_TEST(presence_online_with_message),
|
||||||
PROF_FUNC_TEST(shows_message_when_software_version_error),
|
PROF_FUNC_TEST(presence_away),
|
||||||
PROF_FUNC_TEST(display_software_version_result_when_from_domainpart),
|
PROF_FUNC_TEST(presence_away_with_message),
|
||||||
PROF_FUNC_TEST(show_message_in_chat_window_when_no_resource),
|
PROF_FUNC_TEST(presence_xa),
|
||||||
PROF_FUNC_TEST(display_software_version_result_in_chat),
|
PROF_FUNC_TEST(presence_xa_with_message),
|
||||||
|
PROF_FUNC_TEST(presence_dnd),
|
||||||
|
PROF_FUNC_TEST(presence_dnd_with_message),
|
||||||
|
PROF_FUNC_TEST(presence_chat),
|
||||||
|
PROF_FUNC_TEST(presence_chat_with_message),
|
||||||
|
PROF_FUNC_TEST(presence_set_priority),
|
||||||
|
PROF_FUNC_TEST(presence_includes_priority),
|
||||||
|
PROF_FUNC_TEST(presence_keeps_status),
|
||||||
|
PROF_FUNC_TEST(presence_received),
|
||||||
|
PROF_FUNC_TEST(presence_missing_resource_defaults),
|
||||||
|
|
||||||
/* Last Activity - XEP-0012 */
|
/* ============================================================
|
||||||
PROF_FUNC_TEST(responds_to_last_activity_request),
|
* GROUP 2: Message, Receipts, Roster
|
||||||
PROF_FUNC_TEST(last_activity_request_to_contact),
|
* Core messaging and contact management
|
||||||
};
|
* ============================================================ */
|
||||||
|
|
||||||
/* ============================================================
|
|
||||||
* GROUP 2: Message, Receipts, Roster, Chat Session
|
|
||||||
* Core messaging and contact management
|
|
||||||
* ============================================================ */
|
|
||||||
const struct CMUnitTest group2_tests[] = {
|
|
||||||
/* Basic message send/receive */
|
/* Basic message send/receive */
|
||||||
PROF_FUNC_TEST(message_send),
|
PROF_FUNC_TEST(message_send),
|
||||||
PROF_FUNC_TEST(message_receive_console),
|
PROF_FUNC_TEST(message_receive_console),
|
||||||
@@ -133,54 +112,21 @@ main(int argc, char* argv[])
|
|||||||
PROF_FUNC_TEST(sends_remove_item_nick),
|
PROF_FUNC_TEST(sends_remove_item_nick),
|
||||||
PROF_FUNC_TEST(sends_nick_change),
|
PROF_FUNC_TEST(sends_nick_change),
|
||||||
|
|
||||||
/* Chat session management - bare/full JID routing */
|
/* ============================================================
|
||||||
PROF_FUNC_TEST(sends_message_to_barejid_when_contact_offline),
|
* GROUP 3: MUC (Multi-User Chat)
|
||||||
PROF_FUNC_TEST(sends_message_to_barejid_when_contact_online),
|
* XEP-0045 conference room functionality
|
||||||
PROF_FUNC_TEST(sends_message_to_fulljid_when_received_from_fulljid),
|
* ============================================================ */
|
||||||
PROF_FUNC_TEST(sends_subsequent_messages_to_fulljid),
|
|
||||||
PROF_FUNC_TEST(resets_to_barejid_after_presence_received),
|
|
||||||
PROF_FUNC_TEST(new_session_when_message_received_from_different_fulljid),
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ============================================================
|
/* Room join with various options */
|
||||||
* GROUP 3: Presence, Disconnect
|
|
||||||
* Online/away/xa/dnd/chat status management
|
|
||||||
* ============================================================ */
|
|
||||||
const struct CMUnitTest group3_tests[] = {
|
|
||||||
PROF_FUNC_TEST(presence_online),
|
|
||||||
PROF_FUNC_TEST(presence_online_with_message),
|
|
||||||
PROF_FUNC_TEST(presence_away),
|
|
||||||
PROF_FUNC_TEST(presence_away_with_message),
|
|
||||||
PROF_FUNC_TEST(presence_xa),
|
|
||||||
PROF_FUNC_TEST(presence_xa_with_message),
|
|
||||||
PROF_FUNC_TEST(presence_dnd),
|
|
||||||
PROF_FUNC_TEST(presence_dnd_with_message),
|
|
||||||
PROF_FUNC_TEST(presence_chat),
|
|
||||||
PROF_FUNC_TEST(presence_chat_with_message),
|
|
||||||
PROF_FUNC_TEST(presence_set_priority),
|
|
||||||
PROF_FUNC_TEST(presence_includes_priority),
|
|
||||||
PROF_FUNC_TEST(presence_keeps_status),
|
|
||||||
PROF_FUNC_TEST(presence_received),
|
|
||||||
PROF_FUNC_TEST(presence_missing_resource_defaults),
|
|
||||||
|
|
||||||
/* Disconnect - clean session termination */
|
|
||||||
PROF_FUNC_TEST(disconnect_ends_session),
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ============================================================
|
|
||||||
* GROUP 4: MUC, Carbons
|
|
||||||
* Multi-user chat and message synchronization
|
|
||||||
* ============================================================ */
|
|
||||||
const struct CMUnitTest group4_tests[] = {
|
|
||||||
/* MUC room join with various options - XEP-0045 */
|
|
||||||
PROF_FUNC_TEST(sends_room_join),
|
PROF_FUNC_TEST(sends_room_join),
|
||||||
PROF_FUNC_TEST(sends_room_join_with_nick),
|
PROF_FUNC_TEST(sends_room_join_with_nick),
|
||||||
PROF_FUNC_TEST(sends_room_join_with_password),
|
PROF_FUNC_TEST(sends_room_join_with_password),
|
||||||
PROF_FUNC_TEST(sends_room_join_with_nick_and_password),
|
PROF_FUNC_TEST(sends_room_join_with_nick_and_password),
|
||||||
|
|
||||||
/* MUC room information display */
|
/* Room information display */
|
||||||
PROF_FUNC_TEST(shows_role_and_affiliation_on_join),
|
PROF_FUNC_TEST(shows_role_and_affiliation_on_join),
|
||||||
PROF_FUNC_TEST(shows_subject_on_join),
|
PROF_FUNC_TEST(shows_subject_on_join),
|
||||||
|
// PROF_FUNC_TEST(shows_history_message), // temporarily disabled due to timing issues in CI
|
||||||
PROF_FUNC_TEST(shows_occupant_join),
|
PROF_FUNC_TEST(shows_occupant_join),
|
||||||
|
|
||||||
/* MUC messaging */
|
/* MUC messaging */
|
||||||
@@ -188,14 +134,15 @@ main(int argc, char* argv[])
|
|||||||
PROF_FUNC_TEST(shows_me_message_from_occupant),
|
PROF_FUNC_TEST(shows_me_message_from_occupant),
|
||||||
PROF_FUNC_TEST(shows_me_message_from_self),
|
PROF_FUNC_TEST(shows_me_message_from_self),
|
||||||
|
|
||||||
/* MUC console notification settings */
|
/* Console notification settings for MUC */
|
||||||
PROF_FUNC_TEST(shows_all_messages_in_console_when_window_not_focussed),
|
PROF_FUNC_TEST(shows_all_messages_in_console_when_window_not_focussed),
|
||||||
PROF_FUNC_TEST(shows_first_message_in_console_when_window_not_focussed),
|
PROF_FUNC_TEST(shows_first_message_in_console_when_window_not_focussed),
|
||||||
PROF_FUNC_TEST(shows_no_message_in_console_when_window_not_focussed),
|
PROF_FUNC_TEST(shows_no_message_in_console_when_window_not_focussed),
|
||||||
|
|
||||||
/* MUC moderation - XEP-0045 room admin */
|
/* ============================================================
|
||||||
PROF_FUNC_TEST(sends_affiliation_list_request),
|
* GROUP 4: Carbons, Chat Session, Software, Disconnect
|
||||||
PROF_FUNC_TEST(sends_kick_request),
|
* Message synchronization and session management
|
||||||
|
* ============================================================ */
|
||||||
|
|
||||||
/* Message Carbons - XEP-0280 (message sync across devices) */
|
/* Message Carbons - XEP-0280 (message sync across devices) */
|
||||||
PROF_FUNC_TEST(send_enable_carbons),
|
PROF_FUNC_TEST(send_enable_carbons),
|
||||||
@@ -204,34 +151,26 @@ main(int argc, char* argv[])
|
|||||||
PROF_FUNC_TEST(receive_carbon),
|
PROF_FUNC_TEST(receive_carbon),
|
||||||
PROF_FUNC_TEST(receive_self_carbon),
|
PROF_FUNC_TEST(receive_self_carbon),
|
||||||
PROF_FUNC_TEST(receive_private_carbon),
|
PROF_FUNC_TEST(receive_private_carbon),
|
||||||
|
|
||||||
|
/* Chat session management - bare/full JID routing */
|
||||||
|
PROF_FUNC_TEST(sends_message_to_barejid_when_contact_offline),
|
||||||
|
PROF_FUNC_TEST(sends_message_to_barejid_when_contact_online),
|
||||||
|
PROF_FUNC_TEST(sends_message_to_fulljid_when_received_from_fulljid),
|
||||||
|
PROF_FUNC_TEST(sends_subsequent_messages_to_fulljid),
|
||||||
|
PROF_FUNC_TEST(resets_to_barejid_after_presence_received),
|
||||||
|
PROF_FUNC_TEST(new_session_when_message_received_from_different_fulljid),
|
||||||
|
|
||||||
|
/* Software Version - XEP-0092 */
|
||||||
|
PROF_FUNC_TEST(send_software_version_request),
|
||||||
|
PROF_FUNC_TEST(display_software_version_result),
|
||||||
|
PROF_FUNC_TEST(shows_message_when_software_version_error),
|
||||||
|
PROF_FUNC_TEST(display_software_version_result_when_from_domainpart),
|
||||||
|
PROF_FUNC_TEST(show_message_in_chat_window_when_no_resource),
|
||||||
|
PROF_FUNC_TEST(display_software_version_result_in_chat),
|
||||||
|
|
||||||
|
/* Disconnect - clean session termination */
|
||||||
|
PROF_FUNC_TEST(disconnect_ends_session),
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Test group registry for easy extension */
|
return cmocka_run_group_tests(all_tests, NULL, NULL);
|
||||||
struct {
|
|
||||||
const char* name;
|
|
||||||
const struct CMUnitTest* tests;
|
|
||||||
size_t count;
|
|
||||||
} groups[] = {
|
|
||||||
{ "Group 1: Connect/Ping/Rooms/Software", group1_tests, ARRAY_SIZE(group1_tests) },
|
|
||||||
{ "Group 2: Message/Receipts/Roster/Session", group2_tests, ARRAY_SIZE(group2_tests) },
|
|
||||||
{ "Group 3: Presence/Disconnect", group3_tests, ARRAY_SIZE(group3_tests) },
|
|
||||||
{ "Group 4: MUC/Carbons", group4_tests, ARRAY_SIZE(group4_tests) },
|
|
||||||
};
|
|
||||||
const int num_groups = ARRAY_SIZE(groups);
|
|
||||||
|
|
||||||
int result = 0;
|
|
||||||
|
|
||||||
if (group > 0 && group <= num_groups) {
|
|
||||||
/* Run specific group */
|
|
||||||
result = _cmocka_run_group_tests(groups[group - 1].name, groups[group - 1].tests,
|
|
||||||
groups[group - 1].count, NULL, NULL);
|
|
||||||
} else {
|
|
||||||
/* Run all groups sequentially */
|
|
||||||
for (int i = 0; i < num_groups; i++) {
|
|
||||||
result |= _cmocka_run_group_tests(groups[i].name, groups[i].tests,
|
|
||||||
groups[i].count, NULL, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,6 @@
|
|||||||
|
|
||||||
#include "proftest.h"
|
#include "proftest.h"
|
||||||
|
|
||||||
/* Number of parallel test groups for CI builds */
|
|
||||||
#define TEST_GROUPS 4
|
|
||||||
|
|
||||||
char *config_orig;
|
char *config_orig;
|
||||||
char *data_orig;
|
char *data_orig;
|
||||||
|
|
||||||
@@ -27,13 +24,6 @@ int fd = 0;
|
|||||||
int stub_port = 5230;
|
int stub_port = 5230;
|
||||||
pid_t child_pid = 0;
|
pid_t child_pid = 0;
|
||||||
|
|
||||||
/*
|
|
||||||
* Dynamic XDG paths based on stub_port for parallel test execution.
|
|
||||||
* Each test instance gets unique directories to avoid file conflicts.
|
|
||||||
*/
|
|
||||||
char xdg_config_home[256];
|
|
||||||
char xdg_data_home[256];
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Buffer for accumulating output from profanity.
|
* Buffer for accumulating output from profanity.
|
||||||
* 64KB is sufficient for typical test output while keeping memory usage
|
* 64KB is sufficient for typical test output while keeping memory usage
|
||||||
@@ -87,7 +77,7 @@ _mkdir_recursive(const char *dir)
|
|||||||
void
|
void
|
||||||
_create_config_dir(void)
|
_create_config_dir(void)
|
||||||
{
|
{
|
||||||
GString *profanity_dir = g_string_new(xdg_config_home);
|
GString *profanity_dir = g_string_new(XDG_CONFIG_HOME);
|
||||||
g_string_append(profanity_dir, "/profanity");
|
g_string_append(profanity_dir, "/profanity");
|
||||||
|
|
||||||
if (!_mkdir_recursive(profanity_dir->str)) {
|
if (!_mkdir_recursive(profanity_dir->str)) {
|
||||||
@@ -100,7 +90,7 @@ _create_config_dir(void)
|
|||||||
void
|
void
|
||||||
_create_data_dir(void)
|
_create_data_dir(void)
|
||||||
{
|
{
|
||||||
GString *profanity_dir = g_string_new(xdg_data_home);
|
GString *profanity_dir = g_string_new(XDG_DATA_HOME);
|
||||||
g_string_append(profanity_dir, "/profanity");
|
g_string_append(profanity_dir, "/profanity");
|
||||||
|
|
||||||
if (!_mkdir_recursive(profanity_dir->str)) {
|
if (!_mkdir_recursive(profanity_dir->str)) {
|
||||||
@@ -113,7 +103,7 @@ _create_data_dir(void)
|
|||||||
void
|
void
|
||||||
_create_chatlogs_dir(void)
|
_create_chatlogs_dir(void)
|
||||||
{
|
{
|
||||||
GString *chatlogs_dir = g_string_new(xdg_data_home);
|
GString *chatlogs_dir = g_string_new(XDG_DATA_HOME);
|
||||||
g_string_append(chatlogs_dir, "/profanity/chatlogs");
|
g_string_append(chatlogs_dir, "/profanity/chatlogs");
|
||||||
|
|
||||||
if (!_mkdir_recursive(chatlogs_dir->str)) {
|
if (!_mkdir_recursive(chatlogs_dir->str)) {
|
||||||
@@ -126,7 +116,7 @@ _create_chatlogs_dir(void)
|
|||||||
void
|
void
|
||||||
_create_logs_dir(void)
|
_create_logs_dir(void)
|
||||||
{
|
{
|
||||||
GString *logs_dir = g_string_new(xdg_data_home);
|
GString *logs_dir = g_string_new(XDG_DATA_HOME);
|
||||||
g_string_append(logs_dir, "/profanity/logs");
|
g_string_append(logs_dir, "/profanity/logs");
|
||||||
|
|
||||||
if (!_mkdir_recursive(logs_dir->str)) {
|
if (!_mkdir_recursive(logs_dir->str)) {
|
||||||
@@ -139,15 +129,7 @@ _create_logs_dir(void)
|
|||||||
void
|
void
|
||||||
_cleanup_dirs(void)
|
_cleanup_dirs(void)
|
||||||
{
|
{
|
||||||
const char *group_env = getenv("PROF_TEST_GROUP");
|
int res = system("rm -rf ./tests/functionaltests/files");
|
||||||
int group = group_env ? atoi(group_env) : 0;
|
|
||||||
int dir_id = (group >= 1 && group <= TEST_GROUPS) ? group : stub_port;
|
|
||||||
|
|
||||||
printf("[PROF_TEST] Cleaning up directories for group %d (dir_id %d)\n", group, dir_id);
|
|
||||||
|
|
||||||
char cmd[512];
|
|
||||||
snprintf(cmd, sizeof(cmd), "rm -rf ./test-files/%d", dir_id);
|
|
||||||
int res = system(cmd);
|
|
||||||
if (res == -1) {
|
if (res == -1) {
|
||||||
assert_true(FALSE);
|
assert_true(FALSE);
|
||||||
}
|
}
|
||||||
@@ -230,83 +212,33 @@ prof_start(void)
|
|||||||
/* Set non-blocking mode for reading */
|
/* Set non-blocking mode for reading */
|
||||||
int flags = fcntl(fd, F_GETFL, 0);
|
int flags = fcntl(fd, F_GETFL, 0);
|
||||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
|
||||||
/* Brief wait for process to initialize */
|
|
||||||
usleep(50000); /* 50ms */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
init_prof_test(void **state)
|
init_prof_test(void **state)
|
||||||
{
|
{
|
||||||
/* Get test group from environment for static resource allocation */
|
|
||||||
const char *group_env = getenv("PROF_TEST_GROUP");
|
|
||||||
int group = group_env ? atoi(group_env) : 0;
|
|
||||||
|
|
||||||
/* Get build index for port offset (for parallel CI builds) */
|
|
||||||
const char *build_env = getenv("PROF_BUILD_INDEX");
|
|
||||||
int build_idx = build_env ? atoi(build_env) : 0;
|
|
||||||
|
|
||||||
/* Calculate port base: each build uses a different range of TEST_GROUPS ports.
|
|
||||||
* Build 0 (local/default): 5230-5233, Full: 5230-5233, Minimal: 5234-5237, etc.
|
|
||||||
* Build 0 and Full share the same range because build 0 is for local runs or sequential run (no parallel builds),
|
|
||||||
* while Full/Minimal/NoEncrypt/Default are used in CI where they run in parallel. */
|
|
||||||
int port_base = 5230 + ((build_idx > 0 ? build_idx - 1 : 0) * TEST_GROUPS);
|
|
||||||
|
|
||||||
/* Static resource allocation to avoid conflicts in parallel execution.
|
|
||||||
* Group 1-4: use static port assignment.
|
|
||||||
* Group 0 (all groups): use dynamic allocation as fallback. */
|
|
||||||
gboolean started = FALSE;
|
gboolean started = FALSE;
|
||||||
|
for (int p = 5230; p < 5250; ++p) {
|
||||||
if (group >= 1 && group <= TEST_GROUPS) {
|
int ret = stbbr_start(STBBR_LOGDEBUG, p, 0);
|
||||||
/* Static allocation: each group gets a dedicated port */
|
if (ret == 0) {
|
||||||
stub_port = port_base + group - 1;
|
stub_port = p;
|
||||||
printf("[PROF_TEST] Build %d, Group %d: trying port %d\n", build_idx, group, stub_port);
|
|
||||||
|
|
||||||
if (stbbr_start(STBBR_LOGDEBUG, stub_port, 0) == 0) {
|
|
||||||
started = TRUE;
|
started = TRUE;
|
||||||
printf("[PROF_TEST] Started stabber on port %d\n", stub_port);
|
break;
|
||||||
} else {
|
|
||||||
printf("[PROF_TEST] Failed to start stabber on port %d\n", stub_port);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fallback to dynamic allocation if static failed or group=0 */
|
|
||||||
if (!started) {
|
if (!started) {
|
||||||
printf("[PROF_TEST] Using dynamic port allocation\n");
|
assert_true(FALSE); // could not start stabber on any port in range
|
||||||
for (int p = port_base; p < port_base + 20; ++p) {
|
|
||||||
if (stbbr_start(STBBR_LOGDEBUG, p, 0) == 0) {
|
|
||||||
stub_port = p;
|
|
||||||
started = TRUE;
|
|
||||||
printf("[PROF_TEST] Started stabber on port %d\n", stub_port);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!started) {
|
|
||||||
fprintf(stderr, "[PROF_TEST] ERROR: could not start stabber on any port\n");
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Generate unique XDG paths based on group for parallel execution.
|
|
||||||
* Use ./test-files/ in current (build) directory for out-of-tree builds compatibility. */
|
|
||||||
int dir_id = (group >= 1 && group <= TEST_GROUPS) ? group : stub_port;
|
|
||||||
snprintf(xdg_config_home, sizeof(xdg_config_home),
|
|
||||||
"./test-files/%d/xdg_config_home", dir_id);
|
|
||||||
snprintf(xdg_data_home, sizeof(xdg_data_home),
|
|
||||||
"./test-files/%d/xdg_data_home", dir_id);
|
|
||||||
|
|
||||||
printf("[PROF_TEST] Group %d using directories: config=%s, data=%s\n",
|
|
||||||
group, xdg_config_home, xdg_data_home);
|
|
||||||
|
|
||||||
// Give stabber server thread time to start listening
|
// Give stabber server thread time to start listening
|
||||||
usleep(100000); // 100ms
|
usleep(100000); // 100ms
|
||||||
|
|
||||||
config_orig = getenv("XDG_CONFIG_HOME");
|
config_orig = getenv("XDG_CONFIG_HOME");
|
||||||
data_orig = getenv("XDG_DATA_HOME");
|
data_orig = getenv("XDG_DATA_HOME");
|
||||||
|
|
||||||
setenv("XDG_CONFIG_HOME", xdg_config_home, 1);
|
setenv("XDG_CONFIG_HOME", XDG_CONFIG_HOME, 1);
|
||||||
setenv("XDG_DATA_HOME", xdg_data_home, 1);
|
setenv("XDG_DATA_HOME", XDG_DATA_HOME, 1);
|
||||||
|
|
||||||
_cleanup_dirs();
|
_cleanup_dirs();
|
||||||
|
|
||||||
@@ -454,16 +386,6 @@ prof_output_regex(const char *pattern)
|
|||||||
usleep(50000); /* 50ms */
|
usleep(50000); /* 50ms */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Timeout reached - log diagnostic info */
|
|
||||||
fprintf(stderr, "Timeout waiting for regex '%s' after %d seconds. Last output:\n", pattern, expect_timeout);
|
|
||||||
size_t len = strlen(output_buffer);
|
|
||||||
if (len > 500) {
|
|
||||||
fprintf(stderr, "...%s", output_buffer + len - 500);
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "%s", output_buffer);
|
|
||||||
}
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
|
|
||||||
regfree(®ex);
|
regfree(®ex);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
#ifndef __H_PROFTEST
|
#ifndef __H_PROFTEST
|
||||||
#define __H_PROFTEST
|
#define __H_PROFTEST
|
||||||
|
|
||||||
/*
|
#define XDG_CONFIG_HOME "./tests/functionaltests/files/xdg_config_home"
|
||||||
* XDG paths are dynamic and generated per-test based on stub_port.
|
#define XDG_DATA_HOME "./tests/functionaltests/files/xdg_data_home"
|
||||||
* Each test instance uses unique directories (./tests/functionaltests/files/{port}/xdg_*)
|
|
||||||
* to allow parallel test execution without file conflicts.
|
|
||||||
*/
|
|
||||||
extern char xdg_config_home[256];
|
|
||||||
extern char xdg_data_home[256];
|
|
||||||
|
|
||||||
extern int stub_port;
|
extern int stub_port;
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
* test_lastactivity.c
|
|
||||||
* Functional tests for Last Activity (XEP-0012)
|
|
||||||
*/
|
|
||||||
#include <glib.h>
|
|
||||||
#include "prof_cmocka.h"
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <stabber.h>
|
|
||||||
|
|
||||||
#include "proftest.h"
|
|
||||||
|
|
||||||
void
|
|
||||||
responds_to_last_activity_request(void **state)
|
|
||||||
{
|
|
||||||
prof_connect();
|
|
||||||
|
|
||||||
// Send incoming last activity request
|
|
||||||
stbbr_send(
|
|
||||||
"<iq id='last1' type='get' to='stabber@localhost/profanity' from='buddy1@localhost/mobile'>"
|
|
||||||
"<query xmlns='jabber:iq:last'/>"
|
|
||||||
"</iq>"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify that CProof responds with last activity info
|
|
||||||
// The 'seconds' attribute indicates idle time
|
|
||||||
assert_true(stbbr_received(
|
|
||||||
"<iq id='last1' type='result' to='buddy1@localhost/mobile'>"
|
|
||||||
"<query xmlns='jabber:iq:last' seconds='*'/>"
|
|
||||||
"</iq>"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
last_activity_request_to_contact(void **state)
|
|
||||||
{
|
|
||||||
prof_connect();
|
|
||||||
|
|
||||||
stbbr_send(
|
|
||||||
"<presence to='stabber@localhost' from='buddy1@localhost/mobile'>"
|
|
||||||
"<priority>10</priority>"
|
|
||||||
"<status>I'm here</status>"
|
|
||||||
"</presence>"
|
|
||||||
);
|
|
||||||
assert_true(prof_output_exact("Buddy1 (mobile) is online, \"I'm here\""));
|
|
||||||
|
|
||||||
// Register response for last activity query
|
|
||||||
stbbr_for_query("jabber:iq:last",
|
|
||||||
"<iq id='*' type='result' from='buddy1@localhost/mobile' to='stabber@localhost/profanity'>"
|
|
||||||
"<query xmlns='jabber:iq:last' seconds='120'/>"
|
|
||||||
"</iq>"
|
|
||||||
);
|
|
||||||
|
|
||||||
prof_input("/lastactivity get buddy1@localhost/mobile");
|
|
||||||
|
|
||||||
// Verify the request was sent
|
|
||||||
assert_true(stbbr_received(
|
|
||||||
"<iq id='*' to='buddy1@localhost/mobile' type='get'>"
|
|
||||||
"<query xmlns='jabber:iq:last'/>"
|
|
||||||
"</iq>"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
/*
|
|
||||||
* test_lastactivity.h
|
|
||||||
* Header for Last Activity tests (XEP-0012)
|
|
||||||
*/
|
|
||||||
|
|
||||||
void responds_to_last_activity_request(void **state);
|
|
||||||
void last_activity_request_to_contact(void **state);
|
|
||||||
@@ -393,83 +393,3 @@ shows_no_message_in_console_when_window_not_focussed(void **state)
|
|||||||
assert_false(prof_output_regex("testroom@conference\\.localhost \\(win 2\\)"));
|
assert_false(prof_output_regex("testroom@conference\\.localhost \\(win 2\\)"));
|
||||||
prof_timeout_reset();
|
prof_timeout_reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
sends_affiliation_list_request(void **state)
|
|
||||||
{
|
|
||||||
prof_connect();
|
|
||||||
|
|
||||||
stbbr_for_presence_to("testroom@conference.localhost/stabber",
|
|
||||||
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
|
|
||||||
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://profanity-im.github.io' ver='*'/>"
|
|
||||||
"<x xmlns='http://jabber.org/protocol/muc#user'>"
|
|
||||||
"<item role='moderator' jid='stabber@localhost/profanity' affiliation='owner'/>"
|
|
||||||
"</x>"
|
|
||||||
"<status code='110'/>"
|
|
||||||
"</presence>"
|
|
||||||
);
|
|
||||||
|
|
||||||
prof_input("/join testroom@conference.localhost");
|
|
||||||
assert_true(prof_output_regex("-> You have joined the room as stabber, role: moderator, affiliation: owner"));
|
|
||||||
|
|
||||||
prof_input("/affiliation owner list");
|
|
||||||
|
|
||||||
assert_true(stbbr_received(
|
|
||||||
"<iq id='*' to='testroom@conference.localhost' type='get'>"
|
|
||||||
"<query xmlns='http://jabber.org/protocol/muc#admin'>"
|
|
||||||
"<item affiliation='owner'/>"
|
|
||||||
"</query>"
|
|
||||||
"</iq>"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sends_kick_request(void **state)
|
|
||||||
{
|
|
||||||
prof_connect();
|
|
||||||
|
|
||||||
// Enable MUC presence messages to see occupant join/leave
|
|
||||||
prof_input("/presence room all");
|
|
||||||
assert_true(prof_output_regex("All presence updates will appear"));
|
|
||||||
|
|
||||||
stbbr_for_presence_to("testroom@conference.localhost/stabber",
|
|
||||||
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
|
|
||||||
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://profanity-im.github.io' ver='*'/>"
|
|
||||||
"<x xmlns='http://jabber.org/protocol/muc#user'>"
|
|
||||||
"<item role='moderator' jid='stabber@localhost/profanity' affiliation='admin'/>"
|
|
||||||
"</x>"
|
|
||||||
"<status code='110'/>"
|
|
||||||
"</presence>"
|
|
||||||
);
|
|
||||||
|
|
||||||
prof_input("/join testroom@conference.localhost");
|
|
||||||
assert_true(prof_output_regex("-> You have joined the room as stabber, role: moderator, affiliation: admin"));
|
|
||||||
|
|
||||||
// Simulate another user in the room
|
|
||||||
stbbr_send(
|
|
||||||
"<presence to='stabber@localhost/profanity' from='testroom@conference.localhost/baduser'>"
|
|
||||||
"<x xmlns='http://jabber.org/protocol/muc#user'>"
|
|
||||||
"<item role='participant' jid='baduser@localhost/phone' affiliation='none'/>"
|
|
||||||
"</x>"
|
|
||||||
"</presence>"
|
|
||||||
);
|
|
||||||
sleep(1);
|
|
||||||
assert_true(prof_output_regex("baduser has joined"));
|
|
||||||
|
|
||||||
// Register success response for kick
|
|
||||||
stbbr_for_query("http://jabber.org/protocol/muc#admin",
|
|
||||||
"<iq id='*' type='result' from='testroom@conference.localhost'/>"
|
|
||||||
);
|
|
||||||
|
|
||||||
prof_input("/kick baduser \"spamming\"");
|
|
||||||
|
|
||||||
assert_true(stbbr_received(
|
|
||||||
"<iq id='*' to='testroom@conference.localhost' type='set'>"
|
|
||||||
"<query xmlns='http://jabber.org/protocol/muc#admin'>"
|
|
||||||
"<item nick='baduser' role='none'>"
|
|
||||||
"<reason>spamming</reason>"
|
|
||||||
"</item>"
|
|
||||||
"</query>"
|
|
||||||
"</iq>"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,5 +12,3 @@ void shows_me_message_from_self(void **state);
|
|||||||
void shows_all_messages_in_console_when_window_not_focussed(void **state);
|
void shows_all_messages_in_console_when_window_not_focussed(void **state);
|
||||||
void shows_first_message_in_console_when_window_not_focussed(void **state);
|
void shows_first_message_in_console_when_window_not_focussed(void **state);
|
||||||
void shows_no_message_in_console_when_window_not_focussed(void **state);
|
void shows_no_message_in_console_when_window_not_focussed(void **state);
|
||||||
void sends_affiliation_list_request(void **state);
|
|
||||||
void sends_kick_request(void **state);
|
|
||||||
|
|||||||
Reference in New Issue
Block a user