Compare commits
3 Commits
feat/disco
...
c9455d27ba
| Author | SHA1 | Date | |
|---|---|---|---|
|
c9455d27ba
|
|||
|
964f73d0ad
|
|||
|
2be16df905
|
44
.github/workflows/docker-publish.yml
vendored
Normal file
44
.github/workflows/docker-publish.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
name: Publish Docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
push_to_registry:
|
||||||
|
name: Push Docker image to Docker Hub
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Check out the repo
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051
|
||||||
|
with:
|
||||||
|
images: cproofdev/cproof
|
||||||
|
tags: |
|
||||||
|
type=raw,value=dev,enable={{is_default_branch}}
|
||||||
|
type=sha,prefix=nightly-,enable={{is_default_branch}}
|
||||||
|
type=ref,event=tag
|
||||||
|
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
id: push
|
||||||
|
uses: docker/build-push-action@2bd26e71295ee32cbf6a73510d165bf7232460f3
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./ci/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
106
ci/Dockerfile
Normal file
106
ci/Dockerfile
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# Stage 1: Build
|
||||||
|
# =============================================================================
|
||||||
|
FROM ubuntu:26.04 AS builder
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
autoconf \
|
||||||
|
automake \
|
||||||
|
autoconf-archive \
|
||||||
|
ca-certificates \
|
||||||
|
libtool \
|
||||||
|
pkg-config \
|
||||||
|
git \
|
||||||
|
wget \
|
||||||
|
libglib2.0-dev \
|
||||||
|
libcurl4-openssl-dev \
|
||||||
|
libsqlite3-dev \
|
||||||
|
libncurses-dev \
|
||||||
|
libreadline-dev \
|
||||||
|
libnotify-dev \
|
||||||
|
python3-dev \
|
||||||
|
python3 \
|
||||||
|
libgpgme-dev \
|
||||||
|
libotr5-dev \
|
||||||
|
libgtk-3-dev \
|
||||||
|
libgdk-pixbuf-2.0-dev \
|
||||||
|
libsignal-protocol-c-dev \
|
||||||
|
libgcrypt20-dev \
|
||||||
|
libqrencode-dev \
|
||||||
|
libxss-dev \
|
||||||
|
libcmocka-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Build libstrophe from source (not available as Ubuntu package)
|
||||||
|
RUN git clone --depth 1 https://git.jabber.space/devs/libstrophe-gh-mirror.git /tmp/libstrophe \
|
||||||
|
&& cd /tmp/libstrophe \
|
||||||
|
&& autoreconf -i \
|
||||||
|
&& ./configure --prefix=/usr \
|
||||||
|
&& make -j$(nproc) \
|
||||||
|
&& make install \
|
||||||
|
&& rm -rf /tmp/libstrophe
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN ./bootstrap.sh \
|
||||||
|
&& ./configure \
|
||||||
|
--enable-notifications \
|
||||||
|
--enable-icons-and-clipboard \
|
||||||
|
--enable-otr \
|
||||||
|
--enable-pgp \
|
||||||
|
--enable-omemo \
|
||||||
|
--enable-plugins \
|
||||||
|
--enable-c-plugins \
|
||||||
|
--enable-python-plugins \
|
||||||
|
--with-xscreensaver \
|
||||||
|
--enable-omemo-qrcode \
|
||||||
|
--enable-gdk-pixbuf \
|
||||||
|
&& make -j$(nproc) \
|
||||||
|
&& make install
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Stage 2: Runtime (minimal)
|
||||||
|
# =============================================================================
|
||||||
|
FROM ubuntu:26.04
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates \
|
||||||
|
libglib2.0-0t64 \
|
||||||
|
libcurl4t64 \
|
||||||
|
libsqlite3-0 \
|
||||||
|
libncursesw6 \
|
||||||
|
libreadline8t64 \
|
||||||
|
libnotify4 \
|
||||||
|
python3 \
|
||||||
|
libpython3.14 \
|
||||||
|
libgpgme45 \
|
||||||
|
libotr5t64 \
|
||||||
|
libgtk-3-0t64 \
|
||||||
|
libgdk-pixbuf-2.0-0 \
|
||||||
|
libsignal-protocol-c2.3.2 \
|
||||||
|
libgcrypt20 \
|
||||||
|
libqrencode4 \
|
||||||
|
libxss1 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& apt-get clean
|
||||||
|
|
||||||
|
COPY --from=builder /usr/local/bin/profanity /usr/local/bin/profanity
|
||||||
|
COPY --from=builder /usr/local/lib/libprofanity* /usr/local/lib/
|
||||||
|
COPY --from=builder /usr/local/include/profapi.h /usr/local/include/
|
||||||
|
COPY --from=builder /usr/local/share/profanity /usr/local/share/profanity
|
||||||
|
COPY --from=builder /usr/lib/libstrophe.so* /usr/lib/
|
||||||
|
|
||||||
|
RUN ldconfig
|
||||||
|
|
||||||
|
RUN ldd /usr/local/bin/profanity | grep 'not found' && exit 1 || true
|
||||||
|
|
||||||
|
RUN mkdir -p /root/.config/profanity
|
||||||
|
|
||||||
|
ENTRYPOINT ["profanity"]
|
||||||
@@ -110,8 +110,6 @@ static char* _software_autocomplete(ProfWin* window, const char* const input, gb
|
|||||||
static char* _url_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
static char* _url_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
||||||
static char* _executable_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
static char* _executable_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
||||||
static char* _lastactivity_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
static char* _lastactivity_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
||||||
static char* _disco_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
|
||||||
static char* _roster_jid_autocomplete(const char* const input, const char* const prefix, gboolean previous);
|
|
||||||
static char* _intype_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
static char* _intype_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
||||||
static char* _mood_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
static char* _mood_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
||||||
static char* _strophe_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
static char* _strophe_autocomplete(ProfWin* window, const char* const input, gboolean previous);
|
||||||
@@ -124,7 +122,6 @@ static char* _ai_autocomplete(ProfWin* window, const char* const input, gboolean
|
|||||||
static char* _script_autocomplete_func(const char* const prefix, gboolean previous, void* context);
|
static char* _script_autocomplete_func(const char* const prefix, gboolean previous, void* context);
|
||||||
|
|
||||||
static char* _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previous);
|
static char* _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previous);
|
||||||
static gboolean _is_connected(void);
|
|
||||||
|
|
||||||
static Autocomplete commands_ac;
|
static Autocomplete commands_ac;
|
||||||
static Autocomplete who_room_ac;
|
static Autocomplete who_room_ac;
|
||||||
@@ -1429,7 +1426,6 @@ cmd_ac_init(void)
|
|||||||
g_hash_table_insert(ac_funcs, "/color", _color_autocomplete);
|
g_hash_table_insert(ac_funcs, "/color", _color_autocomplete);
|
||||||
g_hash_table_insert(ac_funcs, "/connect", _connect_autocomplete);
|
g_hash_table_insert(ac_funcs, "/connect", _connect_autocomplete);
|
||||||
g_hash_table_insert(ac_funcs, "/console", _console_autocomplete);
|
g_hash_table_insert(ac_funcs, "/console", _console_autocomplete);
|
||||||
g_hash_table_insert(ac_funcs, "/disco", _disco_autocomplete);
|
|
||||||
g_hash_table_insert(ac_funcs, "/correct", _correct_autocomplete);
|
g_hash_table_insert(ac_funcs, "/correct", _correct_autocomplete);
|
||||||
g_hash_table_insert(ac_funcs, "/correction", _correction_autocomplete);
|
g_hash_table_insert(ac_funcs, "/correction", _correction_autocomplete);
|
||||||
g_hash_table_insert(ac_funcs, "/executable", _executable_autocomplete);
|
g_hash_table_insert(ac_funcs, "/executable", _executable_autocomplete);
|
||||||
@@ -1866,12 +1862,10 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
|
|||||||
}
|
}
|
||||||
|
|
||||||
gchar* history_jid_subcmds[] = { "/history verify", "/history export", "/history import" };
|
gchar* history_jid_subcmds[] = { "/history verify", "/history export", "/history import" };
|
||||||
if (conn_status == JABBER_CONNECTED) {
|
for (size_t i = 0; i < ARRAY_SIZE(history_jid_subcmds); i++) {
|
||||||
for (size_t i = 0; i < ARRAY_SIZE(history_jid_subcmds); i++) {
|
result = autocomplete_param_with_func(input, history_jid_subcmds[i], roster_barejid_autocomplete, previous, NULL);
|
||||||
result = autocomplete_param_with_func(input, history_jid_subcmds[i], roster_barejid_autocomplete, previous, NULL);
|
if (result) {
|
||||||
if (result) {
|
return result;
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1947,6 +1941,7 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
|
|||||||
Autocomplete completer;
|
Autocomplete completer;
|
||||||
} ac_cmds[] = {
|
} ac_cmds[] = {
|
||||||
{ "/prefs", prefs_ac },
|
{ "/prefs", prefs_ac },
|
||||||
|
{ "/disco", disco_ac },
|
||||||
{ "/room", room_ac },
|
{ "/room", room_ac },
|
||||||
{ "/mainwin", winpos_ac },
|
{ "/mainwin", winpos_ac },
|
||||||
{ "/inputwin", winpos_ac },
|
{ "/inputwin", winpos_ac },
|
||||||
@@ -1996,31 +1991,6 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
|
||||||
_is_connected(void)
|
|
||||||
{
|
|
||||||
return connection_get_status() == JABBER_CONNECTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char*
|
|
||||||
_roster_jid_autocomplete(const char* const input, const char* const prefix, gboolean previous)
|
|
||||||
{
|
|
||||||
if (!_is_connected()) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
char* result = NULL;
|
|
||||||
result = autocomplete_param_with_func(input, prefix, roster_contact_autocomplete, previous, NULL);
|
|
||||||
if (result) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
result = autocomplete_param_with_func(input, prefix, roster_barejid_autocomplete, previous, NULL);
|
|
||||||
if (result) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
result = autocomplete_param_with_func(input, prefix, roster_fulljid_autocomplete, previous, NULL);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char*
|
static char*
|
||||||
_caps_autocomplete(ProfWin* window, const char* const input, gboolean previous)
|
_caps_autocomplete(ProfWin* window, const char* const input, gboolean previous)
|
||||||
{
|
{
|
||||||
@@ -2036,29 +2006,20 @@ _caps_autocomplete(ProfWin* window, const char* const input, gboolean previous)
|
|||||||
if (nick_ac) {
|
if (nick_ac) {
|
||||||
result = autocomplete_param_with_ac(input, "/caps", nick_ac, TRUE, previous);
|
result = autocomplete_param_with_ac(input, "/caps", nick_ac, TRUE, previous);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (connection_get_status() == JABBER_CONNECTED) {
|
||||||
result = _roster_jid_autocomplete(input, "/caps", previous);
|
result = autocomplete_param_with_func(input, "/caps", roster_contact_autocomplete, previous, NULL);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result = autocomplete_param_with_func(input, "/caps", roster_barejid_autocomplete, previous, NULL);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result = autocomplete_param_with_func(input, "/caps", roster_fulljid_autocomplete, previous, NULL);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char*
|
|
||||||
_disco_autocomplete(ProfWin* window, const char* const input, gboolean previous)
|
|
||||||
{
|
|
||||||
char* result = NULL;
|
|
||||||
result = autocomplete_param_with_ac(input, "/disco", disco_ac, TRUE, previous);
|
|
||||||
if (result) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = _roster_jid_autocomplete(input, "/disco info", previous);
|
|
||||||
if (result) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
result = _roster_jid_autocomplete(input, "/disco items", previous);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char*
|
static char*
|
||||||
_sub_autocomplete(ProfWin* window, const char* const input, gboolean previous)
|
_sub_autocomplete(ProfWin* window, const char* const input, gboolean previous)
|
||||||
{
|
{
|
||||||
@@ -3737,7 +3698,7 @@ _win_autocomplete(ProfWin* window, const char* const input, gboolean previous)
|
|||||||
}
|
}
|
||||||
|
|
||||||
char* unquoted = strip_arg_quotes(input);
|
char* unquoted = strip_arg_quotes(input);
|
||||||
result = _roster_jid_autocomplete(input, "/win", previous);
|
result = autocomplete_param_with_func(unquoted, "/win", roster_contact_autocomplete, previous, NULL);
|
||||||
free(unquoted);
|
free(unquoted);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -4071,14 +4032,18 @@ _invite_autocomplete(ProfWin* window, const char* const input, gboolean previous
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = _roster_jid_autocomplete(input, "/invite send", previous);
|
jabber_conn_status_t conn_status = connection_get_status();
|
||||||
if (result) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = autocomplete_param_with_func(input, "/invite decline", muc_invites_find, previous, NULL);
|
if (conn_status == JABBER_CONNECTED) {
|
||||||
if (result) {
|
result = autocomplete_param_with_func(input, "/invite send", roster_contact_autocomplete, previous, NULL);
|
||||||
return result;
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = autocomplete_param_with_func(input, "/invite decline", muc_invites_find, previous, NULL);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -4094,37 +4059,42 @@ _status_autocomplete(ProfWin* window, const char* const input, gboolean previous
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// complete with: online, away etc.
|
jabber_conn_status_t conn_status = connection_get_status();
|
||||||
result = autocomplete_param_with_ac(input, "/status set", account_status_ac, TRUE, previous);
|
|
||||||
if (result) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove quote character before and after names when doing autocomplete
|
if (conn_status == JABBER_CONNECTED) {
|
||||||
char* unquoted = strip_arg_quotes(input);
|
|
||||||
|
|
||||||
// MUC completion with nicknames
|
// complete with: online, away etc.
|
||||||
if (window->type == WIN_MUC) {
|
result = autocomplete_param_with_ac(input, "/status set", account_status_ac, TRUE, previous);
|
||||||
ProfMucWin* mucwin = (ProfMucWin*)window;
|
if (result) {
|
||||||
assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
|
return result;
|
||||||
Autocomplete nick_ac = muc_roster_ac(mucwin->roomjid);
|
}
|
||||||
if (nick_ac) {
|
|
||||||
result = autocomplete_param_with_ac(unquoted, "/status get", nick_ac, TRUE, previous);
|
// Remove quote character before and after names when doing autocomplete
|
||||||
|
char* unquoted = strip_arg_quotes(input);
|
||||||
|
|
||||||
|
// MUC completion with nicknames
|
||||||
|
if (window->type == WIN_MUC) {
|
||||||
|
ProfMucWin* mucwin = (ProfMucWin*)window;
|
||||||
|
assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
|
||||||
|
Autocomplete nick_ac = muc_roster_ac(mucwin->roomjid);
|
||||||
|
if (nick_ac) {
|
||||||
|
result = autocomplete_param_with_ac(unquoted, "/status get", nick_ac, TRUE, previous);
|
||||||
|
if (result) {
|
||||||
|
free(unquoted);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// roster completion
|
||||||
|
} else {
|
||||||
|
result = autocomplete_param_with_func(unquoted, "/status get", roster_contact_autocomplete, previous, NULL);
|
||||||
if (result) {
|
if (result) {
|
||||||
free(unquoted);
|
free(unquoted);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// roster completion
|
|
||||||
} else {
|
|
||||||
result = _roster_jid_autocomplete(input, "/status get", previous);
|
|
||||||
if (result) {
|
|
||||||
free(unquoted);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(unquoted);
|
free(unquoted);
|
||||||
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -4232,12 +4202,14 @@ _software_autocomplete(ProfWin* window, const char* const input, gboolean previo
|
|||||||
{
|
{
|
||||||
char* result = NULL;
|
char* result = NULL;
|
||||||
|
|
||||||
if (window->type == WIN_CHAT && _is_connected()) {
|
if (window->type == WIN_CHAT) {
|
||||||
ProfChatWin* chatwin = (ProfChatWin*)window;
|
ProfChatWin* chatwin = (ProfChatWin*)window;
|
||||||
assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
|
assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
|
||||||
|
|
||||||
auto_gchar gchar* search_str = g_strdup_printf("/software %s", chatwin->barejid);
|
GString* search_str = g_string_new("/software ");
|
||||||
result = autocomplete_param_with_func(search_str, "/software", roster_fulljid_autocomplete, previous, NULL);
|
g_string_append(search_str, chatwin->barejid);
|
||||||
|
result = autocomplete_param_with_func(search_str->str, "/software", roster_fulljid_autocomplete, previous, NULL);
|
||||||
|
g_string_free(search_str, TRUE);
|
||||||
} else if (window->type == WIN_MUC) {
|
} else if (window->type == WIN_MUC) {
|
||||||
ProfMucWin* mucwin = (ProfMucWin*)window;
|
ProfMucWin* mucwin = (ProfMucWin*)window;
|
||||||
assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
|
assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
|
||||||
@@ -4251,7 +4223,10 @@ _software_autocomplete(ProfWin* window, const char* const input, gboolean previo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result = _roster_jid_autocomplete(input, "/software", previous);
|
result = autocomplete_param_with_func(input, "/software", roster_fulljid_autocomplete, previous, NULL);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -4350,11 +4325,16 @@ _lastactivity_autocomplete(ProfWin* window, const char* const input, gboolean pr
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = autocomplete_param_with_func(input, "/lastactivity set", prefs_autocomplete_boolean_choice, previous, NULL);
|
jabber_conn_status_t conn_status = connection_get_status();
|
||||||
if (result) {
|
|
||||||
return result;
|
if (conn_status == JABBER_CONNECTED) {
|
||||||
|
|
||||||
|
result = autocomplete_param_with_func(input, "/lastactivity set", prefs_autocomplete_boolean_choice, previous, NULL);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result = autocomplete_param_with_func(input, "/lastactivity get", roster_barejid_autocomplete, previous, NULL);
|
||||||
}
|
}
|
||||||
result = _roster_jid_autocomplete(input, "/lastactivity get", previous);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -4534,19 +4514,19 @@ _vcard_autocomplete(ProfWin* window, const char* const input, gboolean previous)
|
|||||||
} else {
|
} else {
|
||||||
char* unquoted = strip_arg_quotes(input);
|
char* unquoted = strip_arg_quotes(input);
|
||||||
|
|
||||||
result = _roster_jid_autocomplete(input, "/vcard get", previous);
|
result = autocomplete_param_with_func(unquoted, "/vcard get", roster_contact_autocomplete, previous, NULL);
|
||||||
if (result) {
|
if (result) {
|
||||||
free(unquoted);
|
free(unquoted);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = _roster_jid_autocomplete(input, "/vcard photo open", previous);
|
result = autocomplete_param_with_func(unquoted, "/vcard photo open", roster_contact_autocomplete, previous, NULL);
|
||||||
if (result) {
|
if (result) {
|
||||||
free(unquoted);
|
free(unquoted);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = _roster_jid_autocomplete(input, "/vcard photo save", previous);
|
result = autocomplete_param_with_func(unquoted, "/vcard photo save", roster_contact_autocomplete, previous, NULL);
|
||||||
if (result) {
|
if (result) {
|
||||||
free(unquoted);
|
free(unquoted);
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -110,15 +110,6 @@ static gboolean _cmd_execute_default(ProfWin* window, const char* inp);
|
|||||||
static gboolean _cmd_execute_alias(ProfWin* window, const char* const inp, gboolean* ran);
|
static gboolean _cmd_execute_alias(ProfWin* window, const char* const inp, gboolean* ran);
|
||||||
static gboolean
|
static gboolean
|
||||||
_download_install_plugin(ProfWin* window, gchar* url, gchar* path);
|
_download_install_plugin(ProfWin* window, gchar* url, gchar* path);
|
||||||
static const gchar*
|
|
||||||
_resolve_contact_jid(const char* const name)
|
|
||||||
{
|
|
||||||
char* barejid = roster_barejid_from_name(name);
|
|
||||||
if (barejid == NULL) {
|
|
||||||
barejid = (char*)name;
|
|
||||||
}
|
|
||||||
return barejid;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
_vcard_editor_finished_cb(gchar* message, void* user_data)
|
_vcard_editor_finished_cb(gchar* message, void* user_data)
|
||||||
@@ -3423,7 +3414,7 @@ cmd_caps(ProfWin* window, const char* const command, gchar** args)
|
|||||||
case WIN_CONSOLE:
|
case WIN_CONSOLE:
|
||||||
case WIN_XML:
|
case WIN_XML:
|
||||||
if (args[0]) {
|
if (args[0]) {
|
||||||
auto_jid Jid* jid = jid_create(_resolve_contact_jid(args[0]));
|
auto_jid Jid* jid = jid_create(args[0]);
|
||||||
|
|
||||||
if (jid == NULL || jid->fulljid == NULL) {
|
if (jid == NULL || jid->fulljid == NULL) {
|
||||||
cons_show("You must provide a full jid to the /caps command.");
|
cons_show("You must provide a full jid to the /caps command.");
|
||||||
@@ -3465,7 +3456,7 @@ cmd_caps(ProfWin* window, const char* const command, gchar** args)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
_send_software_version_iq_to_fulljid(const gchar* const request)
|
_send_software_version_iq_to_fulljid(char* request)
|
||||||
{
|
{
|
||||||
auto_jid Jid* jid = jid_create(request);
|
auto_jid Jid* jid = jid_create(request);
|
||||||
|
|
||||||
@@ -3506,7 +3497,7 @@ cmd_software(ProfWin* window, const char* const command, gchar** args)
|
|||||||
break;
|
break;
|
||||||
case WIN_CHAT:
|
case WIN_CHAT:
|
||||||
if (args[0]) {
|
if (args[0]) {
|
||||||
_send_software_version_iq_to_fulljid(_resolve_contact_jid(args[0]));
|
_send_software_version_iq_to_fulljid(args[0]);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
ProfChatWin* chatwin = (ProfChatWin*)window;
|
ProfChatWin* chatwin = (ProfChatWin*)window;
|
||||||
@@ -3530,7 +3521,7 @@ cmd_software(ProfWin* window, const char* const command, gchar** args)
|
|||||||
}
|
}
|
||||||
case WIN_CONSOLE:
|
case WIN_CONSOLE:
|
||||||
if (args[0]) {
|
if (args[0]) {
|
||||||
_send_software_version_iq_to_fulljid(_resolve_contact_jid(args[0]));
|
_send_software_version_iq_to_fulljid(args[0]);
|
||||||
} else {
|
} else {
|
||||||
cons_show("You must provide a jid to the /software command.");
|
cons_show("You must provide a jid to the /software command.");
|
||||||
}
|
}
|
||||||
@@ -4840,7 +4831,7 @@ cmd_disco(ProfWin* window, const char* const command, gchar** args)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto_gchar gchar* jid = g_strdup_printf("%s", args[1] ? _resolve_contact_jid(args[1]) : connection_get_jid()->domainpart);
|
auto_gchar gchar* jid = g_strdup_printf("%s", args[1] ?: connection_get_jid()->domainpart);
|
||||||
|
|
||||||
if (g_strcmp0(args[0], "info") == 0) {
|
if (g_strcmp0(args[0], "info") == 0) {
|
||||||
iq_disco_info_request(jid);
|
iq_disco_info_request(jid);
|
||||||
@@ -5062,7 +5053,7 @@ cmd_lastactivity(ProfWin* window, const char* const command, gchar** args)
|
|||||||
if (args[1] == NULL) {
|
if (args[1] == NULL) {
|
||||||
iq_last_activity_request(connection_get_jid()->domainpart);
|
iq_last_activity_request(connection_get_jid()->domainpart);
|
||||||
} else {
|
} else {
|
||||||
iq_last_activity_request(_resolve_contact_jid(args[1]));
|
iq_last_activity_request(args[1]);
|
||||||
}
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -6415,17 +6406,17 @@ cmd_ping(ProfWin* window, const char* const command, gchar** args)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args[0] != NULL) {
|
if (args[0] != NULL && caps_jid_has_feature(args[0], XMPP_FEATURE_PING) == FALSE) {
|
||||||
const gchar* ping_target = _resolve_contact_jid(args[0]);
|
cons_show("%s does not support ping requests.", args[0]);
|
||||||
if (caps_jid_has_feature(ping_target, XMPP_FEATURE_PING) == FALSE) {
|
return TRUE;
|
||||||
cons_show("%s does not support ping requests.", ping_target);
|
}
|
||||||
return TRUE;
|
|
||||||
}
|
iq_send_ping(args[0]);
|
||||||
iq_send_ping(ping_target);
|
|
||||||
cons_show("Pinged %s…", ping_target);
|
if (args[0] == NULL) {
|
||||||
} else {
|
|
||||||
iq_send_ping(NULL);
|
|
||||||
cons_show("Pinged server…");
|
cons_show("Pinged server…");
|
||||||
|
} else {
|
||||||
|
cons_show("Pinged %s…", args[0]);
|
||||||
}
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
#include "ui/ui.h"
|
#include "ui/ui.h"
|
||||||
#include "xmpp/xmpp.h"
|
#include "xmpp/xmpp.h"
|
||||||
|
|
||||||
|
extern char** environ;
|
||||||
|
|
||||||
typedef struct EditorContext
|
typedef struct EditorContext
|
||||||
{
|
{
|
||||||
gchar* filename;
|
gchar* filename;
|
||||||
@@ -140,6 +142,22 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
|
|||||||
GSource* sigchld_warmup = g_child_watch_source_new(getpid());
|
GSource* sigchld_warmup = g_child_watch_source_new(getpid());
|
||||||
g_source_unref(sigchld_warmup);
|
g_source_unref(sigchld_warmup);
|
||||||
|
|
||||||
|
// Build the editor's env without LINES/COLUMNS pre-fork, so the child only
|
||||||
|
// reassigns environ instead of calling unsetenv() between fork and exec.
|
||||||
|
// The editor's (n)curses then reads the live window via ioctl(TIOCGWINSZ).
|
||||||
|
gsize env_len = 0;
|
||||||
|
while (environ[env_len]) {
|
||||||
|
env_len++;
|
||||||
|
}
|
||||||
|
gchar** editor_env = g_new0(gchar*, env_len + 1);
|
||||||
|
gsize env_kept = 0;
|
||||||
|
for (gsize i = 0; i < env_len; i++) {
|
||||||
|
if (g_str_has_prefix(environ[i], "LINES=") || g_str_has_prefix(environ[i], "COLUMNS=")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
editor_env[env_kept++] = environ[i];
|
||||||
|
}
|
||||||
|
|
||||||
pid_t pid = fork();
|
pid_t pid = fork();
|
||||||
if (pid == -1) {
|
if (pid == -1) {
|
||||||
log_error("[Editor] Failed to fork: %s", strerror(errno));
|
log_error("[Editor] Failed to fork: %s", strerror(errno));
|
||||||
@@ -148,12 +166,14 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
|
|||||||
ui_resize();
|
ui_resize();
|
||||||
cons_show_error("Failed to start editor: %s", strerror(errno));
|
cons_show_error("Failed to start editor: %s", strerror(errno));
|
||||||
g_strfreev(editor_argv);
|
g_strfreev(editor_argv);
|
||||||
g_free(ctx->filename);
|
g_free(editor_env);
|
||||||
g_free(ctx);
|
g_free(ctx);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
} else if (pid == 0) {
|
} else if (pid == 0) {
|
||||||
// Child process: Inherits TTY from parent
|
// Child process: Inherits TTY from parent
|
||||||
|
|
||||||
|
environ = editor_env; // live TIOCGWINSZ size, not the inherited LINES/COLUMNS
|
||||||
|
|
||||||
// SIGTSTP=SIG_DFL lets vim's :stop / Ctrl-Z work; profanity catches
|
// SIGTSTP=SIG_DFL lets vim's :stop / Ctrl-Z work; profanity catches
|
||||||
// the STOPPED state via editor_check_stopped() and drops to the shell.
|
// the STOPPED state via editor_check_stopped() and drops to the shell.
|
||||||
signal(SIGINT, SIG_DFL);
|
signal(SIGINT, SIG_DFL);
|
||||||
@@ -170,6 +190,7 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
|
|||||||
editor_pid = pid;
|
editor_pid = pid;
|
||||||
g_child_watch_add((GPid)pid, _editor_exit_cb, ctx);
|
g_child_watch_add((GPid)pid, _editor_exit_cb, ctx);
|
||||||
g_strfreev(editor_argv);
|
g_strfreev(editor_argv);
|
||||||
|
g_free(editor_env); // array only; strings are borrowed from environ
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -753,7 +753,11 @@ void
|
|||||||
connection_features_received(const char* const jid)
|
connection_features_received(const char* const jid)
|
||||||
{
|
{
|
||||||
log_info("[CONNECTION] connection_features_received %s", jid);
|
log_info("[CONNECTION] connection_features_received %s", jid);
|
||||||
if (g_hash_table_remove(conn.requested_features, jid) && g_hash_table_size(conn.requested_features) == 0) {
|
const char* key = jid ? jid : conn.domain; // g_str_hash crashes on NULL; NULL 'from' means the server (RFC 6120 §8.1.2.1)
|
||||||
|
if (!key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (g_hash_table_remove(conn.requested_features, key) && g_hash_table_size(conn.requested_features) == 0) {
|
||||||
sv_ev_connection_features_received();
|
sv_ev_connection_features_received();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -761,7 +765,11 @@ connection_features_received(const char* const jid)
|
|||||||
GHashTable*
|
GHashTable*
|
||||||
connection_get_features(const char* const jid)
|
connection_get_features(const char* const jid)
|
||||||
{
|
{
|
||||||
return g_hash_table_lookup(conn.features_by_jid, jid);
|
const char* key = jid ? jid : conn.domain;
|
||||||
|
if (!key || !conn.features_by_jid) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return g_hash_table_lookup(conn.features_by_jid, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
GList*
|
GList*
|
||||||
|
|||||||
@@ -2314,6 +2314,7 @@ _disco_info_response_id_handler(xmpp_stanza_t* const stanza, void* const userdat
|
|||||||
log_debug("Received disco#info response from: %s", from);
|
log_debug("Received disco#info response from: %s", from);
|
||||||
} else {
|
} else {
|
||||||
log_debug("Received disco#info response");
|
log_debug("Received disco#info response");
|
||||||
|
from = connection_get_domain(); // RFC 6120 §8.1.2.1: no 'from' means the server itself
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle error responses
|
// handle error responses
|
||||||
@@ -2397,6 +2398,7 @@ _disco_info_response_id_handler_onconnect(xmpp_stanza_t* const stanza, void* con
|
|||||||
log_debug("Received disco#info response from: %s", from);
|
log_debug("Received disco#info response from: %s", from);
|
||||||
} else {
|
} else {
|
||||||
log_debug("Received disco#info response");
|
log_debug("Received disco#info response");
|
||||||
|
from = connection_get_domain(); // RFC 6120 §8.1.2.1: no 'from' means the server itself
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle error responses
|
// handle error responses
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ main(int argc, char* argv[])
|
|||||||
PROF_FUNC_TEST(disco_info_without_name),
|
PROF_FUNC_TEST(disco_info_without_name),
|
||||||
PROF_FUNC_TEST(disco_items_without_name),
|
PROF_FUNC_TEST(disco_items_without_name),
|
||||||
PROF_FUNC_TEST(disco_info_service_unavailable),
|
PROF_FUNC_TEST(disco_info_service_unavailable),
|
||||||
|
PROF_FUNC_TEST(disco_info_result_no_from),
|
||||||
|
|
||||||
/* Roster management - add/remove/rename contacts */
|
/* Roster management - add/remove/rename contacts */
|
||||||
PROF_FUNC_TEST(sends_new_item),
|
PROF_FUNC_TEST(sends_new_item),
|
||||||
|
|||||||
@@ -396,6 +396,35 @@ disco_items_without_name(void **state)
|
|||||||
prof_timeout_reset();
|
prof_timeout_reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
disco_info_result_no_from(void **state)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Test that a disco#info result without a 'from' attribute is treated as
|
||||||
|
* coming from the server itself (RFC 6120 §8.1.2.1). The on-connect
|
||||||
|
* disco#info handler used to crash on such responses (issue #168).
|
||||||
|
*/
|
||||||
|
stbbr_for_query("http://jabber.org/protocol/disco#info",
|
||||||
|
"<iq to='stabber@localhost/profanity' type='result'>"
|
||||||
|
"<query xmlns='http://jabber.org/protocol/disco#info'>"
|
||||||
|
"<identity category='server' type='im' name='NoFromServer'/>"
|
||||||
|
"<feature var='urn:xmpp:ping'/>"
|
||||||
|
"</query>"
|
||||||
|
"</iq>"
|
||||||
|
);
|
||||||
|
|
||||||
|
/* the on-connect disco#info gets the same from-less response */
|
||||||
|
prof_connect();
|
||||||
|
|
||||||
|
prof_input("/disco info");
|
||||||
|
|
||||||
|
prof_timeout(10);
|
||||||
|
/* client survived and attributed the response to the server */
|
||||||
|
assert_true(prof_output_exact("Service discovery info for localhost"));
|
||||||
|
assert_true(prof_output_regex("NoFromServer.*im.*server"));
|
||||||
|
prof_timeout_reset();
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
disco_info_service_unavailable(void **state)
|
disco_info_service_unavailable(void **state)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,3 +17,4 @@ void disco_info_multiple_identities(void **state);
|
|||||||
void disco_info_without_name(void **state);
|
void disco_info_without_name(void **state);
|
||||||
void disco_items_without_name(void **state);
|
void disco_items_without_name(void **state);
|
||||||
void disco_info_service_unavailable(void **state);
|
void disco_info_service_unavailable(void **state);
|
||||||
|
void disco_info_result_no_from(void **state);
|
||||||
|
|||||||
Reference in New Issue
Block a user